connection and cursor class are now written in

native python
This commit is contained in:
Georg Richter
2021-07-12 09:46:05 +02:00
parent 27730cd018
commit 84df56ce40
17 changed files with 1132 additions and 238 deletions

View File

@ -90,9 +90,29 @@ int clock_gettime(int dummy, struct timespec *ct);
#define MAX_TPC_XID_SIZE 64
#define POOL_DEFAULT_SIZE 5
/* Placeholder for missing documentation */
#define MISSING_DOC NULL
/* Magic constant for checking dynamic columns */
#define PYTHON_DYNCOL_VALUE 0xA378BD8E
typedef struct st_lex_str {
char *str;
size_t length;
} MrdbString;
enum enum_binary_command {
SQL_NONE= 0,
SQL_INSERT,
SQL_UPDATE,
SQL_REPLACE,
SQL_DELETE,
SQL_CALL,
SQL_DO,
SQL_SELECT,
SQL_OTHER=255
};
enum enum_extended_field_type
{
EXT_TYPE_NONE=0,
@ -126,18 +146,14 @@ enum enum_tpc_state
enum enum_paramstyle
{
NONE= 0,
QMARK,
FORMAT,
PYFORMAT
QMARK= 1,
FORMAT= 2,
PYFORMAT= 3
};
typedef struct st_lex_str {
char *str;
size_t length;
} MrdbString;
typedef struct st_parser {
MrdbString statement;
MrdbString *keys;
uint8_t in_literal[3];
uint8_t in_comment;
uint8_t in_values;
@ -146,8 +162,9 @@ typedef struct st_parser {
uint32_t param_count;
uint32_t key_count;
char* value_ofs;
PyObject *param_list;
enum enum_paramstyle paramstyle;
MrdbString *keys;
enum enum_binary_command command;
MYSQL *mysql;
} MrdbParser;
@ -170,6 +187,7 @@ typedef struct {
const char *collation;
uint8_t inuse;
uint8_t status;
uint8_t asynchronous;
struct timespec last_used;
/* capabilities */
unsigned long client_capabilities;
@ -222,6 +240,16 @@ typedef struct {
MYSQL_TIME tm;
} MrdbParamValue;
typedef struct {
char *statement;
Py_ssize_t statement_len;
enum enum_paramstyle paramstyle;
uint32_t paramcount;
uint8_t is_text;
PyObject *paramlist;
PyObject *keys;
} MrdbParseInfo;
/* PEP-249: Cursor object */
typedef struct {
PyObject_HEAD
@ -230,7 +258,6 @@ typedef struct {
MYSQL_RES *result;
PyObject *data;
uint32_t array_size;
uint32_t param_count;
uint32_t row_array_size; /* for fetch many */
MrdbParamInfo *paraminfo;
MrdbParamValue *value;
@ -238,10 +265,11 @@ typedef struct {
MYSQL_BIND *bind;
MYSQL_FIELD *fields;
char *statement;
unsigned long statement_len;
size_t statement_len;
PyObject **values;
PyStructSequence_Field *sequence_fields;
PyTypeObject *sequence_type;
MrdbParseInfo parseinfo;
unsigned long prefetch_rows;
unsigned long cursor_type;
int64_t affected_rows;
@ -251,13 +279,12 @@ typedef struct {
unsigned long row_number;
enum enum_result_format result_format;
uint8_t is_prepared;
uint8_t is_buffered;
char is_buffered;
uint8_t fetched;
uint8_t is_closed;
uint8_t is_text;
uint8_t is_binary;
MrdbParser *parser;
uint8_t reprepare;
PyThreadState *thread_state;
enum enum_paramstyle paramstyle;
} MrdbCursor;
typedef struct
@ -309,9 +336,6 @@ mariadb_throw_exception(void *handle,
enum enum_extended_field_type mariadb_extended_field_type(const MYSQL_FIELD *field);
PyObject *
MrdbConnection_affected_rows(MrdbConnection *self);
PyObject *
MrdbConnection_ping(MrdbConnection *self);

View File

@ -8,7 +8,6 @@ Minimum supported Python version is 3.6
'''
from ._mariadb import (
Binary,
DataError,
DatabaseError,
Error,
@ -30,6 +29,7 @@ _POOLS= _CONNECTION_POOLS= {}
from mariadb.dbapi20 import *
from mariadb.connectionpool import *
from mariadb.cursor import Cursor
from mariadb.async_connections import *
from mariadb.release_info import __version__ as __version__
from mariadb.release_info import __version_info__ as __version_info__
from mariadb.release_info import __author__ as __author__
@ -43,14 +43,18 @@ def Binary(obj):
def connect(*args, **kwargs):
from mariadb.connections import Connection
if kwargs and "pool_name" in kwargs:
if kwargs:
if "pool_name" in kwargs:
if not kwargs["pool_name"] in mariadb._CONNECTION_POOLS:
pool= mariadb.ConnectionPool(**kwargs)
else:
pool= mariadb._CONNECTION_POOLS[kwargs["pool_name"]]
c= pool.get_connection()
return c
is_async= kwargs.get("asynchronous", False)
if is_async:
return AsyncConnection(*args, **kwargs)
return Connection(*args, **kwargs)
Connection= connect

Binary file not shown.

View File

@ -0,0 +1,95 @@
#
# Copyright (C) 2021 Georg Richter and MariaDB Corporation AB
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Library General Public License for more details.
# You should have received a copy of the GNU Library General Public
# License along with this library; if not see <http://www.gnu.org/licenses>
# or write to the Free Software Foundation, Inc.,
# 51 Franklin St., Fifth Floor, Boston, MA 02110, USA
#
import mariadb
import socket
import time
from mariadb.constants import STATUS
_DEFAULT_CHARSET = "utf8mb4"
_DEFAULT_COLLATION = "utf8mb4_general_ci"
class AsyncConnection(mariadb._mariadb.connection):
"""MariaDB connection class"""
def __init__(self, *args, **kwargs):
self._socket= None
self.__in_use= 0
self.__pool = None
self.__last_used = 0
super().__init__(*args, **kwargs)
async def _execute_command(self, command):
print("comand: ")
print(command)
super().query(command)
async def _read_response(self):
super()._read_response()
async def query(self, command):
await self._execute_command(command)
await self._read_response()
def cursor(self, **kwargs):
return mariadb.Cursor(self, **kwargs)
def close(self):
if self._Connection__pool:
self._Connection__pool._close_connection(self)
else:
super().close()
def __enter__(self):
"Returns a copy of the connection."
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"Closes connection."
self.close()
@property
def character_set(self):
"""Client character set."""
return _DEFAULT_CHARSET
@property
def collation(self):
"""Client character set collation"""
return _DEFAULT_COLLATION
@property
def server_status(self):
"""Returns server status flags."""
return super()._server_status
@property
def socket(self):
"""Returns the socket used for database connection"""
fno= self.get_socket()
if not self._socket:
self._socket= socket.socket(fileno=fno)
# in case of a possible reconnect, file descriptor has changed
elif fno != self._socket.fileno():
self._socket= socket.socket(fileno=fno)
return self._socket

53
mariadb/codecs.py Normal file
View File

@ -0,0 +1,53 @@
from mariadb.constants import FIELD_TYPE
import decimal
def get_mariadb_type(value):
""" get corresponding MariaDB field type for given Python object """
if value is None:
return FIELD_TYPE.NULL
if isinstance(value, int):
if value.bit_length() < 9:
return FIELD_TYPE.TINY
if value.bit_length() < 17:
return FIELD_TYPE.SHORT
return FIELD_TYPE.LONG
if isinstance(value, float):
return FIELD_TYPE.DOUBLE
if isinstance(value, decimal.Decimal):
return FIELD_TYPE.NEWDECIMAL
if isinstance(value,str):
return FIELD_TYPE.VAR_STRING
if isinstance(value, datetime.date):
return FIELD_TYPE.DATE
if isinstance(value, datetime.time):
return FIELD_TYPE.TIME
if isinstance(value, datetime.datetime):
return FIELD_TYPE.DATETIME
if isinstance(value, bytes):
return FIELD_TYPE.LONG_BLOB
raise mariadb.DataError("Unknown or not supported datatype")
def get_execute_parameter_types(cursor):
"""
returns bytearray with MariaDB parameter types
"""
param_types= bytearray()
if cursor.paramcount == 0:
raise mariadb.DataError("Invalid number of parameters")
for column_value in cursor._data:
param_types.append(get_mariadb_type(column_value))
return param_types

View File

@ -36,7 +36,7 @@ class Connection(mariadb._mariadb.connection):
self.__last_used = 0
# self._autocommit= kwargs.pop("autocommit", True)
# self._converter= kwargs.pop("converter", None)
self._converter= kwargs.pop("converter", None)
super().__init__(*args, **kwargs)

View File

@ -26,3 +26,9 @@ CAN_HANDLE_EXPIRED_PASSWORDS = 1 < 22
SESSION_TRACKING = 1 << 23
SSL_VERIFY_SERVER_CERT = 1 << 30
REMEMBER_OPTIONS = 1 << 31
# MariaDB specific capabilities
PROGRESS = 1 << 32
BULK_OPERATIONS = 1 << 34
EXTENDED_METADATA = 1 << 35
CACHE_METDATA = 1 << 36

View File

@ -0,0 +1,20 @@
''' MariaDB status flags
These flas describe the current status of the database server.
'''
IN_TRANS = 1
AUTOCOMMIT = 2
MORE_RESULTS_EXIST = 8
QUERY_NO_GOOD_INDEX_USED =16
QUERY_NO_INDEX_USED = 32
CURSOR_EXISTS = 64
LAST_ROW_SENT = 128
DB_DROPPED = 256
NO_BACKSLASH_ESCAPES = 512
METADATA_CHANGED = 1024
QUERY_WAS_SLOW = 2048
PS_OUT_PARAMS = 4096
IN_TRANS_READONLY = 8192
SESSION_STATE_CHANGED = 16384
ANSI_QUOTES = 32768

259
mariadb/cursor.py Normal file
View File

@ -0,0 +1,259 @@
#
# Copyright (C) 2020-2021 Georg Richter and MariaDB Corporation AB
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Library General Public License for more details.
# You should have received a copy of the GNU Library General Public
# License along with this library; if not see <http://www.gnu.org/licenses>
# or write to the Free Software Foundation, Inc.,
# 51 Franklin St., Fifth Floor, Boston, MA 02110, USA
#
import mariadb, collections
from mariadb.codecs import *
from numbers import Number
from mariadb.constants import *
PARAMSTYLE_QMARK= 1
PARAMSTYLE_FORMAT= 2
PARAMSTYLE_PYFORMAT= 3
RESULT_TUPLE= 0
RESULT_NAMEDTUPLE= 1
RESULT_DICTIONARY= 2
class Cursor(mariadb._mariadb.cursor):
"""Returns a MariaDB cursor object"""
def __init__(self, connection, **kwargs):
self._dictionary= False
self._named_tuple= False
self._connection= connection
self._resulttype= RESULT_TUPLE
self._description= None
self._transformed_statement= None
self._prepared= False
self._parsed= False
self._prev_stmt= None
self._force_binary= None
self._parseinfo= None
self._data= None
if not connection:
raise mariadb.ProgrammingError("Invalid or no connection provided")
if kwargs:
rtype= kwargs.pop("named_tuple", False)
if rtype:
self._resulttype= RESULT_NAMEDTUPLE
else:
rtype= kwargs.pop("dictionary", False)
if rtype:
self._resulttype= RESULT_DICTIONARY
buffered= kwargs.pop("buffered", False)
self.buffered= buffered
self._prepared= kwargs.pop("prepared", False)
self._force_binary= kwargs.pop("binary", False)
super().__init__(connection, **kwargs)
def _add_text_params(self):
new_stmt= self.statement
replace_diff= 0
for i in range (0,len(self._paramlist)):
if self._paramstyle == PARAMSTYLE_PYFORMAT:
val= self._data[self._parseinfo["keys"][i]]
else:
val= self._data[i]
if val is None:
replace= "NULL";
else:
if isinstance(val, Number):
replace= val.__str__()
else:
replace= "\"%s\"" % self.connection.escape_string(val.__str__())
start= self._paramlist[i] + replace_diff
end= self._paramlist[i] + replace_diff + 1
new_stmt= new_stmt[:start] + replace.__str__() + new_stmt[end:]
replace_diff+= len(replace) - 1
return new_stmt
def _check_execute_params(self):
# check data format
if self._paramstyle == PARAMSTYLE_QMARK or \
self._paramstyle == PARAMSTYLE_FORMAT:
if not isinstance(self._data, (tuple,list)):
raise mariadb.ProgrammingError("Data arguent nust be Tuple or List")
if self._paramstyle == PARAMSTYLE_PYFORMAT and\
not isinstance(self._data, dict):
raise mariadb.ProgrammingError("Data arguent nust be Dictionary")
print(self._paramlist)
# check if number of place holders match the number of
# supplied elements in data tuple
if (not self._data and len(self._paramlist) > 0) or \
(len(self._data) != len(self._paramlist)):
raise mariadb.DataError("Number of parameters in statement (%s)"\
" doesn't match the number of data elements (%s)."\
% (len(self._paramlist), len(self._data)))
def _parse_execute(self, statement, data=()):
"""
Simple SQL statement parsing:
"""
if not statement:
raise mariadb.ProgrammingError("empty statement")
# parse statement
print("statement: %s" % statement)
print("prev statement: %s" % self._prev_stmt)
if self._prev_stmt != statement:
super()._parse(statement)
self._prev_stmt= statement
self._reprepare= True
print("param_count: %s" % self.paramcount)
print("parsed")
else:
self._reprepare= False
print("not parsed")
self._data= data
self._check_execute_params()
if self._text:
# in text mode we need to substitute parameters
# and store transformed statement
if (self.paramcount > 0):
self._transformed_statement= self._add_text_params()
else:
self._transformed_statement= self.statement
def nextset(self):
return super()._nextset()
def execute(self, statement, data=(), buffered=False):
"""
place holder for execute() description
"""
if buffered:
self.buffered= 1
# parse statement and check param style
if not self._parsed or not self._prepared:
self._parse_execute(statement, (data))
self._parsed= True
self._description= None
if (self._paramstyle == 3 and not isinstance(data, dict)):
raise TypeError("Argument 2 must be Dict")
elif (not isinstance(data, (tuple, list))):
raise TypeError("Argument 2 must be Tuple or List")
if len(data):
self._data= data
else:
self._data= None
if self._force_binary:
self._text= False
if self._text:
self._execute_text(self._transformed_statement)
self._readresponse()
else:
self._data= data
self._execute_binary()
self._initresult()
def executemany(self, statement, data=[]):
""" Bulk operations """
# If the server doesn't support bulk operations, we need to emulate
# by looping
if (self.connection.server_capabilities & (CLIENT.BULK_OPERATIONS >> 32)):
for row in data:
self.execute(statement, row)
else:
# parse statement
self._parse_execute(statement, (data))
def _fetch_row(self):
if not self.field_count:
return()
row= super().fetchone()
if self._connection._converter:
l= list(row)
if not self._description:
self._description= super().description
for i,v in enumerate(row):
type= self.description[i][1]
if type in self._connection._converter:
func= self._connection._converter[type]
l[i]= func(v)
else:
l[i]= v
row= tuple(l)
return row
def fetchone(self):
row= self._fetch_row()
if not row:
return row
if self._resulttype == RESULT_DICTIONARY:
ret= dict(zip(list(d[0] for d in self.description),row))
elif self._resulttype == RESULT_NAMEDTUPLE:
ret= collections.namedtuple('Row1', list(d[0] for d in self.description));
ret= ret._make(row)
else:
ret= row
return ret
def fetchall(self):
rows=[];
for row in self:
rows.append((row))
return rows
def close(self):
super().close()
def __enter__(self):
"""Returns a copy of the cursor."""
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Closes cursor."""
self.close()
@property
def rowcount(self):
if not self.statement:
return -1;
if not self.field_count:
return 0;
return super().rowcount
@property
def connection(self):
"""Read-Only attribute which returns the reference to the connection
object on which the cursor was created."""
return self._connection

View File

@ -935,7 +935,7 @@ mariadb_get_parameter(MrdbCursor *self,
/* check if row_nr and column_nr are in the range from
0 to (value - 1) */
if (row_nr > (self->array_size - 1) ||
column_nr > (self->param_count - 1))
column_nr > (self->parseinfo.paramcount - 1))
{
mariadb_throw_exception(self->stmt, Mariadb_DataError, 0,
"Can't access data at row %d, column %d",
@ -953,7 +953,7 @@ mariadb_get_parameter(MrdbCursor *self,
else
row= self->data;
if (self->parser->paramstyle != PYFORMAT)
if (self->paramstyle != PYFORMAT)
{
if (!(column= ListOrTuple_GetItem(row, column_nr)))
{
@ -964,12 +964,13 @@ mariadb_get_parameter(MrdbCursor *self,
}
} else
{
PyObject *key= PyUnicode_FromString(self->parser->keys[column_nr].str);
PyObject *key;
key= PyTuple_GetItem(self->parseinfo.keys, column_nr);
if (!PyDict_Contains(row, key))
{
mariadb_throw_exception(self->stmt, Mariadb_DataError, 0,
"Can't find key '%s' in parameter data",
self->parser->keys[column_nr]);
"Can't find key in parameter data");
goto end;
}
column= PyDict_GetItem(row, key);
@ -1166,7 +1167,7 @@ mariadb_check_bulk_parameters(MrdbCursor *self,
for (i=0; i < self->array_size; i++)
{
PyObject *obj= ListOrTuple_GetItem(data, i);
if (self->parser->paramstyle != PYFORMAT &&
if (self->paramstyle != PYFORMAT &&
(!CHECK_TYPE(obj, &PyTuple_Type) &&
!CHECK_TYPE(obj, &PyList_Type)))
{
@ -1175,7 +1176,7 @@ mariadb_check_bulk_parameters(MrdbCursor *self,
" (Row data must be provided as tuple(s))", i+1);
return 1;
}
if (self->parser->paramstyle == PYFORMAT &&
if (self->paramstyle == PYFORMAT &&
!CHECK_TYPE(obj, &PyDict_Type))
{
mariadb_throw_exception(NULL, Mariadb_DataError, 0,
@ -1184,12 +1185,9 @@ mariadb_check_bulk_parameters(MrdbCursor *self,
return 1;
}
if (!self->param_count && !self->is_prepared)
self->param_count= self->parser->param_count;
if (!self->param_count ||
(self->parser->paramstyle != PYFORMAT &&
self->param_count != ListOrTuple_Size(obj)))
if (!self->parseinfo.paramcount ||
(self->paramstyle != PYFORMAT &&
self->parseinfo.paramcount != ListOrTuple_Size(obj)))
{
mariadb_throw_exception(self->stmt, Mariadb_DataError, 1,
"Invalid number of parameters in row %d", i+1);
@ -1198,25 +1196,25 @@ mariadb_check_bulk_parameters(MrdbCursor *self,
}
if (!self->is_prepared &&
!(self->params= PyMem_RawCalloc(self->param_count,
!(self->params= PyMem_RawCalloc(self->parseinfo.paramcount,
sizeof(MYSQL_BIND))))
{
mariadb_throw_exception(NULL, Mariadb_InterfaceError, 0,
"Not enough memory (tried to allocated %lld bytes)",
self->param_count * sizeof(MYSQL_BIND));
self->parseinfo.paramcount * sizeof(MYSQL_BIND));
goto error;
}
if (!(self->value= PyMem_RawCalloc(self->param_count,
if (!(self->value= PyMem_RawCalloc(self->parseinfo.paramcount,
sizeof(MrdbParamValue))))
{
mariadb_throw_exception(NULL, Mariadb_InterfaceError, 0,
"Not enough memory (tried to allocated %lld bytes)",
self->param_count * sizeof(MrdbParamValue));
self->parseinfo.paramcount * sizeof(MrdbParamValue));
goto error;
}
for (i=0; i < self->param_count; i++)
for (i=0; i < self->parseinfo.paramcount; i++)
{
if (mariadb_get_parameter_info(self, &self->params[i], i))
goto error;
@ -1233,12 +1231,8 @@ mariadb_check_execute_parameters(MrdbCursor *self,
PyObject *data)
{
uint32_t i;
if (!self->param_count)
{
self->param_count= self->parser->param_count;
}
if (!self->param_count)
if (!self->parseinfo.paramcount)
{
mariadb_throw_exception(NULL, Mariadb_DataError, 0,
"Invalid number of parameters");
@ -1246,23 +1240,23 @@ mariadb_check_execute_parameters(MrdbCursor *self,
}
if (!self->params &&
!(self->params= PyMem_RawCalloc(self->param_count, sizeof(MYSQL_BIND))))
!(self->params= PyMem_RawCalloc(self->parseinfo.paramcount, sizeof(MYSQL_BIND))))
{
mariadb_throw_exception(NULL, Mariadb_InterfaceError, 0,
"Not enough memory (tried to allocated %lld bytes)",
self->param_count * sizeof(MYSQL_BIND));
self->parseinfo.paramcount * sizeof(MYSQL_BIND));
goto error;
}
if (!(self->value= PyMem_RawCalloc(self->param_count, sizeof(MrdbParamValue))))
if (!(self->value= PyMem_RawCalloc(self->parseinfo.paramcount, sizeof(MrdbParamValue))))
{
mariadb_throw_exception(NULL, Mariadb_InterfaceError, 0,
"Not enough memory (tried to allocated %lld bytes)",
self->param_count * sizeof(MrdbParamValue));
self->parseinfo.paramcount * sizeof(MrdbParamValue));
goto error;
}
for (i=0; i < self->param_count; i++)
for (i=0; i < self->parseinfo.paramcount; i++)
{
if (mariadb_get_parameter_info(self, &self->params[i], i))
{
@ -1444,7 +1438,7 @@ mariadb_param_update(void *data, MYSQL_BIND *bind, uint32_t row_nr)
MARIADB_UNBLOCK_THREADS(self);
for (i=0; i < self->param_count; i++)
for (i=0; i < self->parseinfo.paramcount; i++)
{
if (mariadb_get_parameter(self, (self->array_size > 0),
row_nr, i, &self->value[i]))

View File

@ -20,6 +20,13 @@
#include "mariadb_python.h"
#include "docs/connection.h"
#define MADB_SET_OPTION(m,o,v)\
if (mysql_optionsv((m), (o), (v)))\
{\
mariadb_throw_exception(self->mysql, NULL, 0, NULL);\
return -1;\
}
char *dsn_keys[]= {
"dsn", "host", "user", "password", "database", "port", "unix_socket",
"connect_timeout", "read_timeout", "write_timeout",
@ -31,7 +38,7 @@ char *dsn_keys[]= {
"client_flag", "pool_name", "pool_size",
"pool_reset_connection", "plugin_dir",
"username", "db", "passwd",
"autocommit", "converter",
"autocommit", "converter", "asynchronous",
NULL
};
@ -47,6 +54,9 @@ static PyObject
static PyObject *
MrdbConnection_exception(PyObject *self, void *closure);
static PyObject *
MrdbConnection_query(MrdbConnection *self, PyObject *args);
#define GETTER_EXCEPTION(name, exception, doc)\
{ name,MrdbConnection_exception, NULL, doc, &exception }
@ -93,6 +103,10 @@ MrdbConnection_get_server_version(MrdbConnection *self);
static PyObject *
MrdbConnection_get_server_status(MrdbConnection *self);
PyObject *
MrdbConnection_executecommand(MrdbConnection *self,
PyObject *args);
static PyGetSetDef
MrdbConnection_sets[]=
{
@ -131,6 +145,9 @@ MrdbConnection_sets[]=
static PyMethodDef
MrdbConnection_Methods[] =
{
{"_query", (PyCFunction)MrdbConnection_query,
METH_VARARGS,
NULL},
/* PEP-249 methods */
{"close", (PyCFunction)MrdbConnection_close,
METH_NOARGS,
@ -209,6 +226,11 @@ MrdbConnection_Methods[] =
METH_VARARGS,
connection_escape_string__doc__
},
/* Internal methods */
{ "_execute_command",
(PyCFunction)MrdbConnection_executecommand,
METH_VARARGS, NULL
},
{NULL} /* always last */
};
@ -265,6 +287,16 @@ PyMemberDef MrdbConnection_Members[] =
offsetof(MrdbConnection, tls_version),
READONLY,
"TLS protocol version used by connection"},
{"client_capabilities",
T_ULONG,
offsetof(MrdbConnection, server_capabilities),
READONLY,
"Client capabilities"},
{"server_capabilities",
T_ULONG,
offsetof(MrdbConnection, server_capabilities),
READONLY,
"Server capabilities"},
{NULL} /* always last */
};
@ -311,9 +343,10 @@ MrdbConnection_Initialize(MrdbConnection *self,
compress= 0, ssl_verify_cert= 0;
PyObject *autocommit_obj= NULL;
PyObject *converter= NULL;
PyObject *asynchronous= NULL;
if (!PyArg_ParseTupleAndKeywords(args, dsnargs,
"|zzzzziziiibbzzzzzzzzzzibizibzzzzOO:connect",
"|zzzzziziiibbzzzzzzzzzzibizibzzzzO!OO!:connect",
dsn_keys,
&dsn, &host, &user, &password, &schema, &port, &socket,
&connect_timeout, &read_timeout, &write_timeout,
@ -325,21 +358,13 @@ MrdbConnection_Initialize(MrdbConnection *self,
&client_flags, &pool_name, &pool_size,
&reset_session, &plugin_dir,
&user, &schema, &password,
&autocommit_obj, &converter))
&PyBool_Type, &autocommit_obj,
&converter,
&PyBool_Type, &asynchronous))
{
return -1;
}
if (autocommit_obj)
{
if (autocommit_obj != Py_None &&
!CHECK_TYPE(autocommit_obj, &PyBool_Type))
{
PyErr_SetString(PyExc_TypeError, "Argument must be boolean or None");
return -1;
}
}
if (dsn)
{
mariadb_throw_exception(NULL, Mariadb_ProgrammingError, 1,
@ -370,40 +395,50 @@ MrdbConnection_Initialize(MrdbConnection *self,
if (local_infile != 0xFF)
{
mysql_optionsv(self->mysql, MYSQL_OPT_LOCAL_INFILE, &local_infile);
MADB_SET_OPTION(self->mysql, MYSQL_OPT_LOCAL_INFILE, &local_infile);
}
if (compress)
{
mysql_optionsv(self->mysql, MYSQL_OPT_COMPRESS, 1);
MADB_SET_OPTION(self->mysql, MYSQL_OPT_COMPRESS, 1);
}
if (init_command)
{
mysql_optionsv(self->mysql, MYSQL_INIT_COMMAND, init_command);
MADB_SET_OPTION(self->mysql, MYSQL_INIT_COMMAND, init_command);
}
if (plugin_dir) {
mysql_optionsv(self->mysql, MYSQL_PLUGIN_DIR, plugin_dir);
MADB_SET_OPTION(self->mysql, MYSQL_PLUGIN_DIR, plugin_dir);
} else {
#if defined(DEFAULT_PLUGINS_SUBDIR)
mysql_optionsv(self->mysql, MYSQL_PLUGIN_DIR, DEFAULT_PLUGINS_SUBDIR);
MADB_SET_OPTION(self->mysql, MYSQL_PLUGIN_DIR, DEFAULT_PLUGINS_SUBDIR);
#endif
}
/* read defaults from configuration file(s) */
if (default_file)
mysql_optionsv(self->mysql, MYSQL_READ_DEFAULT_FILE, default_file);
{
MADB_SET_OPTION(self->mysql, MYSQL_READ_DEFAULT_FILE, default_file);
}
if (default_group)
mysql_optionsv(self->mysql, MYSQL_READ_DEFAULT_GROUP, default_group);
{
MADB_SET_OPTION(self->mysql, MYSQL_READ_DEFAULT_GROUP, default_group);
}
/* set timeouts */
if (connect_timeout)
mysql_optionsv(self->mysql, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
{
MADB_SET_OPTION(self->mysql, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
}
if (read_timeout)
mysql_optionsv(self->mysql, MYSQL_OPT_READ_TIMEOUT, &read_timeout);
{
MADB_SET_OPTION(self->mysql, MYSQL_OPT_READ_TIMEOUT, &read_timeout);
}
if (write_timeout)
mysql_optionsv(self->mysql, MYSQL_OPT_WRITE_TIMEOUT, &write_timeout);
{
MADB_SET_OPTION(self->mysql, MYSQL_OPT_WRITE_TIMEOUT, &write_timeout);
}
/* set TLS/SSL options */
if (ssl_enforce || ssl_key || ssl_ca || ssl_cert || ssl_capath || ssl_cipher)
@ -413,11 +448,18 @@ MrdbConnection_Initialize(MrdbConnection *self,
(const char *)ssl_capath,
(const char *)ssl_cipher);
if (ssl_crl)
mysql_optionsv(self->mysql, MYSQL_OPT_SSL_CRL, ssl_crl);
{
MADB_SET_OPTION(self->mysql, MYSQL_OPT_SSL_CRL, ssl_crl);
}
if (ssl_crlpath)
mysql_optionsv(self->mysql, MYSQL_OPT_SSL_CRLPATH, ssl_crlpath);
{
MADB_SET_OPTION(self->mysql, MYSQL_OPT_SSL_CRLPATH, ssl_crlpath);
}
if (ssl_verify_cert)
mysql_optionsv(self->mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, (unsigned char *) &ssl_verify_cert);
{
MADB_SET_OPTION(self->mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, (unsigned char *) &ssl_verify_cert);
}
Py_BEGIN_ALLOW_THREADS;
mysql_real_connect(self->mysql, host, user, password, schema, port,
socket, client_flags);
@ -460,7 +502,13 @@ MrdbConnection_Initialize(MrdbConnection *self,
PyTuple_SetItem(self->server_version_info, 2, PyLong_FromLong(patch)))
goto end;
}
/*
if (asynchronous && PyObject_IsTrue(asynchronous))
{
self->asynchronous= 1;
MADB_SET_OPTION(self->mysql, MARIADB_OPT_SKIP_READ_RESPONSE, &self->asynchronous);
}
*/
end:
if (PyErr_Occurred())
return -1;
@ -580,19 +628,86 @@ void MrdbConnection_dealloc(MrdbConnection *self)
mysql_close(self->mysql);
Py_END_ALLOW_THREADS
}
Py_DECREF(self->server_version_info);
Py_XDECREF(self->server_version_info);
Py_TYPE(self)->tp_free((PyObject*)self);
}
}
PyObject *
MrdbConnection_executecommand(MrdbConnection *self,
PyObject *args)
{
char *cmd;
int rc;
MARIADB_CHECK_CONNECTION(self, NULL);
if (!PyArg_ParseTuple(args, "s", &cmd))
return NULL;
Py_BEGIN_ALLOW_THREADS;
rc= mysql_send_query(self->mysql, cmd, strlen(cmd));
Py_END_ALLOW_THREADS;
if (rc)
{
mariadb_throw_exception(self->mysql, NULL, 0, NULL);
return NULL;
}
Py_RETURN_NONE;
}
/*
PyObject *
MrdbConnection_readresponse(MrdbConnection *self)
{
int rc;
PyObject *result, *tmp;
MARIADB_CHECK_CONNECTION(self, NULL);
Py_BEGIN_ALLOW_THREADS;
rc= self->mysql->methods->db_read_query_result(self->mysql);
Py_END_ALLOW_THREADS;
if (rc)
{
mariadb_throw_exception(self->mysql, NULL, 0, NULL);
return NULL;
}
result= PyDict_New();
tmp= PyLong_FromLong((long)mysql_field_count(self->mysql));
PyDict_SetItemString(result, "field_count", tmp);
Py_DECREF(tmp);
tmp= PyLong_FromLong((long)mysql_affected_rows(self->mysql));
PyDict_SetItemString(result, "affected_rows", tmp);
Py_DECREF(tmp);
tmp= PyLong_FromLong((long)mysql_insert_id(self->mysql));
PyDict_SetItemString(result, "insert_id", tmp);
Py_DECREF(tmp);
tmp= PyLong_FromLong((long)self->mysql->server_status);
PyDict_SetItemString(result, "server_status", tmp);
Py_DECREF(tmp);
tmp= PyLong_FromLong((long)mysql_warning_count(self->mysql));
PyDict_SetItemString(result, "warning_count", tmp);
Py_DECREF(tmp);
return result;
}
*/
PyObject *MrdbConnection_close(MrdbConnection *self)
{
MARIADB_CHECK_CONNECTION(self, NULL);
/* Todo: check if all the cursor stuff is deleted (when using prepared
statements this should be handled in mysql_close) */
if (self->converter)
Py_DECREF(self->converter);
Py_XDECREF(self->converter);
self->converter= NULL;
Py_BEGIN_ALLOW_THREADS
@ -1329,6 +1444,27 @@ static PyObject *MrdbConnection_get_server_status(MrdbConnection *self)
return PyLong_FromLong((long)server_status);
}
static PyObject *MrdbConnection_query(MrdbConnection *self, PyObject *args)
{
char *cmd;
int rc;
if (!PyArg_ParseTuple(args, "s", &cmd))
return NULL;
Py_BEGIN_ALLOW_THREADS;
rc= mysql_send_query(self->mysql, cmd, strlen(cmd));
Py_END_ALLOW_THREADS;
if (rc)
{
mariadb_throw_exception(self->mysql, NULL, 0, NULL);
return NULL;
}
Py_RETURN_NONE;
}
/* vim: set tabstop=4 */
/* vim: set shiftwidth=4 */
/* vim: set expandtab */

View File

@ -38,6 +38,18 @@ static PyObject *
MrdbCursor_executemany(MrdbCursor *self,
PyObject *args);
static PyObject *
MrdbCursor_execute_binary(MrdbCursor *self);
static PyObject *
MrdbCursor_InitResultSet(MrdbCursor *self);
static PyObject *
MrdbCursor_execute_text(MrdbCursor *self, PyObject *args);
static PyObject *
MrdbCursor_parse(MrdbCursor *self, PyObject *args);
static PyObject *
MrdbCursor_description(MrdbCursor *self);
@ -67,6 +79,9 @@ MrdbCursor_fieldcount(MrdbCursor *self);
void
field_fetch_fromtext(MrdbCursor *self, char *data, unsigned int column);
static PyObject *
MrdbCursor_readresponse(MrdbCursor *self);
void
field_fetch_callback(void *data, unsigned int column, unsigned char **row);
static PyObject *mariadb_get_sequence_or_tuple(MrdbCursor *self);
@ -85,19 +100,19 @@ strncpy((a)->statement, (s), (l));\
(a)->statement[(l)]= 0;
#define CURSOR_FIELD_COUNT(a)\
((a)->is_text ? mysql_field_count((a)->connection->mysql) : (a)->stmt ? mysql_stmt_field_count((a)->stmt) : 0)
((a)->parseinfo.is_text ? mysql_field_count((a)->connection->mysql) : (a)->stmt ? mysql_stmt_field_count((a)->stmt) : 0)
#define CURSOR_WARNING_COUNT(a)\
(((a)->is_text) ? (long)mysql_warning_count((a)->connection->mysql) : ((a)->stmt) ? (long)mysql_stmt_warning_count((a)->stmt) : 0L)
(((a)->parseinfo.is_text) ? (long)mysql_warning_count((a)->connection->mysql) : ((a)->stmt) ? (long)mysql_stmt_warning_count((a)->stmt) : 0L)
#define CURSOR_AFFECTED_ROWS(a)\
(int64_t)((a)->is_text ? mysql_affected_rows((a)->connection->mysql) : (a)->stmt ? mysql_stmt_affected_rows((a)->stmt) : 0)
(int64_t)((a)->parseinfo.is_text ? mysql_affected_rows((a)->connection->mysql) : (a)->stmt ? mysql_stmt_affected_rows((a)->stmt) : 0)
#define CURSOR_INSERT_ID(a)\
((a)->is_text ? mysql_insert_id((a)->connection->mysql) : (a)->stmt ? mysql_stmt_insert_id((a)->stmt) : 0)
((a)->parseinfo.is_text ? mysql_insert_id((a)->connection->mysql) : (a)->stmt ? mysql_stmt_insert_id((a)->stmt) : 0)
#define CURSOR_NUM_ROWS(a)\
((a)->is_text ? mysql_num_rows((a)->result) : (a)->stmt ? mysql_stmt_num_rows((a)->stmt) : 0)
((a)->parseinfo.is_text ? mysql_num_rows((a)->result) : (a)->stmt ? mysql_stmt_num_rows((a)->stmt) : 0)
static char *mariadb_named_tuple_name= "mariadb.Row";
static char *mariadb_named_tuple_desc= "Named tupled row";
@ -106,8 +121,6 @@ static PyObject *Mariadb_no_operation(MrdbCursor *,
static PyObject *Mariadb_row_count(MrdbCursor *self);
static PyObject *Mariadb_row_number(MrdbCursor *self);
static PyObject *MrdbCursor_warnings(MrdbCursor *self);
static PyObject *MrdbCursor_getbuffered(MrdbCursor *self);
static int MrdbCursor_setbuffered(MrdbCursor *self, PyObject *arg);
static PyObject *MrdbCursor_lastrowid(MrdbCursor *self);
static PyObject *MrdbCursor_closed(MrdbCursor *self);
static PyObject *MrdbCursor_sp_outparams(MrdbCursor *self);
@ -125,8 +138,6 @@ static PyGetSetDef MrdbCursor_sets[]=
cursor_warnings__doc__, NULL},
{"closed", (getter)MrdbCursor_closed, NULL,
cursor_closed__doc__, NULL},
{"buffered", (getter)MrdbCursor_getbuffered, (setter)MrdbCursor_setbuffered,
cursor_buffered__doc__, NULL},
{"rownumber", (getter)Mariadb_row_number, NULL,
cursor_rownumber__doc__, NULL},
{"sp_outparams", (getter)MrdbCursor_sp_outparams, NULL,
@ -161,7 +172,7 @@ static PyMethodDef MrdbCursor_Methods[] =
{"fieldcount", (PyCFunction)MrdbCursor_fieldcount,
METH_NOARGS,
cursor_field_count__doc__},
{"nextset", (PyCFunction)MrdbCursor_nextset,
{"_nextset", (PyCFunction)MrdbCursor_nextset,
METH_NOARGS,
cursor_nextset__doc__},
{"setinputsizes", (PyCFunction)Mariadb_no_operation,
@ -176,6 +187,22 @@ static PyMethodDef MrdbCursor_Methods[] =
{"scroll", (PyCFunction)MrdbCursor_scroll,
METH_VARARGS | METH_KEYWORDS,
cursor_scroll__doc__},
/* internal helper functions */
{"_initresult", (PyCFunction)MrdbCursor_InitResultSet,
METH_NOARGS,
NULL},
{"_parse", (PyCFunction)MrdbCursor_parse,
METH_VARARGS,
NULL},
{"_readresponse", (PyCFunction)MrdbCursor_readresponse,
METH_NOARGS,
NULL},
{"_execute_text", (PyCFunction)MrdbCursor_execute_text,
METH_VARARGS,
NULL},
{"_execute_binary", (PyCFunction)MrdbCursor_execute_binary,
METH_NOARGS,
NULL},
{NULL} /* always last */
};
@ -183,11 +210,46 @@ static struct PyMemberDef MrdbCursor_Members[] =
{
{"statement",
T_STRING,
offsetof(MrdbCursor, statement),
offsetof(MrdbCursor, parseinfo.statement),
READONLY,
cursor_statement__doc__},
{"_paramstyle",
T_UINT,
offsetof(MrdbCursor, parseinfo.paramstyle),
READONLY,
MISSING_DOC},
{"_reprepare",
T_UINT,
offsetof(MrdbCursor, reprepare),
0,
MISSING_DOC},
{"_text",
T_BOOL,
offsetof(MrdbCursor, parseinfo.is_text),
0,
MISSING_DOC},
{"_paramlist",
T_OBJECT,
offsetof(MrdbCursor, parseinfo.paramlist),
READONLY,
MISSING_DOC},
{"_keys",
T_OBJECT,
offsetof(MrdbCursor, parseinfo.keys),
READONLY,
MISSING_DOC},
{"paramcount",
T_UINT,
offsetof(MrdbCursor, parseinfo.paramcount),
READONLY,
MISSING_DOC},
{"_data",
T_OBJECT,
offsetof(MrdbCursor, data),
0,
MISSING_DOC},
{"buffered",
T_BYTE,
T_BOOL,
offsetof(MrdbCursor, is_buffered),
0,
cursor_buffered__doc__},
@ -196,6 +258,23 @@ static struct PyMemberDef MrdbCursor_Members[] =
offsetof(MrdbCursor, row_array_size),
0,
cursor_arraysize__doc__},
{"field_count",
T_UINT,
offsetof(MrdbCursor, field_count),
READONLY,
"Number of columns"},
{"affected_rows",
T_ULONGLONG,
offsetof(MrdbCursor, affected_rows),
READONLY,
"Number of affected rows"},
{"insert_id",
T_UINT,
offsetof(MrdbCursor, lastrow_id),
READONLY,
"returns the ID generated by a query on a table with a column " \
"having the AUTO_INCREMENT attribute or the value for the last "\
"usage of LAST_INSERT_ID()"},
{NULL}
};
@ -211,14 +290,11 @@ buffered: buffered or unbuffered result sets
static int MrdbCursor_initialize(MrdbCursor *self, PyObject *args,
PyObject *kwargs)
{
char *key_words[]= {"", "named_tuple", "dictionary", "prefetch_size", "cursor_type",
"buffered", "prepared", "binary", NULL};
char *key_words[]= {"", "prefetch_size", "cursor_type",
"prepared", "binary", NULL};
PyObject *connection;
uint8_t is_named_tuple= 0;
uint8_t is_dictionary= 0;
unsigned long cursor_type= 0,
prefetch_rows= 0;
uint8_t is_buffered= 0;
uint8_t is_prepared= 0;
uint8_t is_binary= 0;
@ -226,9 +302,8 @@ static int MrdbCursor_initialize(MrdbCursor *self, PyObject *args,
return -1;
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
"O!|bbkkbbb", key_words, &MrdbConnection_Type, &connection,
&is_named_tuple, &is_dictionary, &prefetch_rows, &cursor_type, &is_buffered,
&is_prepared, &is_binary))
"O!|kkii", key_words, &MrdbConnection_Type, &connection,
&prefetch_rows, &cursor_type, &is_prepared, &is_binary))
return -1;
if (!((MrdbConnection *)connection)->mysql)
@ -246,29 +321,11 @@ static int MrdbCursor_initialize(MrdbCursor *self, PyObject *args,
return -1;
}
if (is_named_tuple && is_dictionary)
{
mariadb_throw_exception(NULL, Mariadb_ProgrammingError, 0,
"Results can be returned either as named tuple or as dictionary, but not as both.");
return -1;
}
if (is_named_tuple)
{
self->result_format= RESULT_NAMED_TUPLE;
} else if (is_dictionary)
{
self->result_format= RESULT_DICTIONARY;
}
Py_INCREF(connection);
self->connection= (MrdbConnection *)connection;
self->is_buffered= is_buffered ? is_buffered : self->connection->is_buffered;
self->is_binary= is_binary;
self->is_prepared= is_prepared;
self->is_text= 0;
self->parseinfo.is_text= 0;
self->stmt= NULL;
self->cursor_type= cursor_type;
@ -366,12 +423,20 @@ static PyObject *Mariadb_no_operation(MrdbCursor *self,
}
/* }}} */
void MrdbCursor_clearparseinfo(MrdbParseInfo *parseinfo)
{
if (parseinfo->statement)
MARIADB_FREE_MEM(parseinfo->statement);
Py_XDECREF(parseinfo->keys);
memset(parseinfo, 0, sizeof(MrdbParseInfo));
}
/* {{{ MrdbCursor_clear_result(MrdbCursor *self)
clear pending result sets
*/
static void MrdbCursor_clear_result(MrdbCursor *self)
{
if (!self->is_text &&
if (!self->parseinfo.is_text &&
self->stmt)
{
/* free current result */
@ -382,7 +447,7 @@ static void MrdbCursor_clear_result(MrdbCursor *self)
{
mysql_stmt_free_result(self->stmt);
}
} else if (self->is_text)
} else if (self->parseinfo.is_text)
{
/* free current result */
if (self->result)
@ -413,7 +478,7 @@ void MrdbCursor_clear(MrdbCursor *self, uint8_t new_stmt)
/* clear pending result sets */
MrdbCursor_clear_result(self);
if (!self->is_text && self->stmt) {
if (!self->parseinfo.is_text && self->stmt) {
if (new_stmt)
{
mysql_stmt_close(self->stmt);
@ -439,7 +504,8 @@ void MrdbCursor_clear(MrdbCursor *self, uint8_t new_stmt)
self->fields= NULL;
self->row_count= 0;
self->affected_rows= 0;
self->param_count= 0;
MARIADB_FREE_MEM(self->parseinfo.statement);
memset(&self->parseinfo, 0, sizeof(MrdbParseInfo));
MARIADB_FREE_MEM(self->values);
MARIADB_FREE_MEM(self->bind);
MARIADB_FREE_MEM(self->statement);
@ -474,7 +540,7 @@ void ma_cursor_close(MrdbCursor *self)
if (!self->is_closed)
{
MrdbCursor_clear_result(self);
if (!self->is_text && self->stmt)
if (!self->parseinfo.is_text && self->stmt)
{
/* Todo: check if all the cursor stuff is deleted (when using prepared
statements this should be handled in mysql_stmt_close) */
@ -485,14 +551,9 @@ void ma_cursor_close(MrdbCursor *self)
}
MrdbCursor_clear(self, 0);
if (self->is_text && self->stmt)
if (!self->parseinfo.is_text && self->stmt)
mysql_stmt_close(self->stmt);
if (self->parser)
{
MrdbParser_end(self->parser);
self->parser= NULL;
}
self->is_closed= 1;
}
}
@ -520,7 +581,7 @@ static int Mrdb_GetFieldInfo(MrdbCursor *self)
if (self->field_count)
{
if (self->is_text)
if (self->parseinfo.is_text)
{
self->result= (self->is_buffered) ? mysql_store_result(self->connection->mysql) :
mysql_use_result(self->connection->mysql);
@ -541,7 +602,7 @@ static int Mrdb_GetFieldInfo(MrdbCursor *self)
self->affected_rows= CURSOR_AFFECTED_ROWS(self);
self->fields= (self->is_text) ? mysql_fetch_fields(self->result) :
self->fields= (self->parseinfo.is_text) ? mysql_fetch_fields(self->result) :
mariadb_stmt_fetch_fields(self->stmt);
if (self->result_format == RESULT_NAMED_TUPLE) {
@ -571,10 +632,8 @@ static int Mrdb_GetFieldInfo(MrdbCursor *self)
return 0;
}
static int MrdbCursor_InitResultSet(MrdbCursor *self)
PyObject *MrdbCursor_InitResultSet(MrdbCursor *self)
{
self->field_count= CURSOR_FIELD_COUNT(self);
MARIADB_FREE_MEM(self->sequence_fields);
MARIADB_FREE_MEM(self->values);
@ -585,13 +644,22 @@ static int MrdbCursor_InitResultSet(MrdbCursor *self)
}
if (Mrdb_GetFieldInfo(self))
return 1;
return NULL;
if (!(self->values= (PyObject**)PyMem_RawCalloc(self->field_count, sizeof(PyObject *))))
return 1;
if (!self->is_text)
return NULL;
if (!self->parseinfo.is_text)
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_RESULT, field_fetch_callback);
return 0;
if (self->field_count)
{
self->row_count= CURSOR_NUM_ROWS(self);
} else {
self->row_count= CURSOR_AFFECTED_ROWS(self);
}
self->lastrow_id= CURSOR_INSERT_ID(self);
Py_RETURN_NONE;
}
static Py_ssize_t data_count(PyObject *data)
@ -612,26 +680,38 @@ static Py_ssize_t data_count(PyObject *data)
return 0;
}
static int Mrdb_execute_direct(MrdbCursor *self)
static int Mrdb_execute_direct(MrdbCursor *self,
const char *statement,
size_t statement_len)
{
int rc;
MARIADB_BEGIN_ALLOW_THREADS(self);
/* clear pending result sets */
MrdbCursor_clear_result(self);
/* if stmt is already prepared */
if (!self->reprepare)
{
rc= mysql_stmt_execute(self->stmt);
goto end;
}
/* execute_direct was implemented together with bulk operations, so we need
to check if MARIADB_CLIENT_STMT_BULK_OPERATIONS is set in extended server
capabilities */
if (!(self->connection->extended_server_capabilities &
(MARIADB_CLIENT_STMT_BULK_OPERATIONS >> 32)))
{
if (!(rc= mysql_stmt_prepare(self->stmt, self->parser->statement.str,
(unsigned long)self->parser->statement.length)))
if (!(rc= mysql_stmt_prepare(self->stmt, statement, (unsigned long)statement_len)))
{
rc= mysql_stmt_execute(self->stmt);
}
} else {
rc= mariadb_stmt_execute_direct(self->stmt, self->parser->statement.str,
self->parser->statement.length);
rc= mariadb_stmt_execute_direct(self->stmt, statement, statement_len);
}
end:
MARIADB_END_ALLOW_THREADS(self);
return rc;
}
@ -650,7 +730,6 @@ PyObject *MrdbCursor_execute(MrdbCursor *self,
int rc= 0;
int8_t is_buffered= -1;
static char *key_words[]= {"", "", "buffered", NULL};
char errmsg[128];
MARIADB_CHECK_STMT(self);
if (PyErr_Occurred())
@ -661,22 +740,20 @@ PyObject *MrdbCursor_execute(MrdbCursor *self,
return NULL;
/* If we don't have a prepared cursor, we need to end/free parser */
if (!self->is_prepared && self->parser)
if (!self->is_prepared && self->parseinfo.statement)
{
MrdbParser_end(self->parser);
self->parser= NULL;
MARIADB_FREE_MEM(self->parseinfo.statement);
memset(&self->parseinfo, 0, sizeof(MrdbParseInfo));
}
if (is_buffered != -1)
self->is_buffered= is_buffered;
/* if there are no parameters specified, we execute the statement in text protocol */
if (!data_count(Data) && !self->cursor_type && !self->is_binary)
if (!data_count(Data) && !self->cursor_type && self->parseinfo.is_text)
{
/* in case statement was executed before, we need to clear, since we don't use
binary protocol */
MrdbParser_end(self->parser);
self->parser= NULL;
MrdbCursor_clear(self, 0);
MARIADB_BEGIN_ALLOW_THREADS(self);
rc= mysql_real_query(self->connection->mysql, statement, (unsigned long)statement_len);
@ -686,7 +763,7 @@ PyObject *MrdbCursor_execute(MrdbCursor *self,
mariadb_throw_exception(self->connection->mysql, NULL, 0, NULL);
goto error;
}
self->is_text= 1;
self->parseinfo.is_text= 1;
CURSOR_SET_STATEMENT(self, statement, statement_len);
}
else
@ -713,12 +790,10 @@ PyObject *MrdbCursor_execute(MrdbCursor *self,
do_prepare= 0;
} else {
MrdbCursor_clear(self, 1);
MrdbParser_end(self->parser);
self->parser= NULL;
}
}
self->is_text= 0;
self->parseinfo.is_text= 0;
/*
if (!self->parser)
{
self->parser= MrdbParser_init(self->connection->mysql, statement, statement_len);
@ -753,19 +828,18 @@ PyObject *MrdbCursor_execute(MrdbCursor *self,
self->data= Data;
/* Load values */
if (mariadb_param_update(self, self->params, 0))
goto error;
}
} */
if (do_prepare)
{
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CURSOR_TYPE, &self->cursor_type);
mysql_stmt_attr_set(self->stmt, STMT_ATTR_PREFETCH_ROWS, &self->prefetch_rows);
mysql_stmt_attr_set(self->stmt, STMT_ATTR_PREBIND_PARAMS, &self->param_count);
mysql_stmt_attr_set(self->stmt, STMT_ATTR_PREBIND_PARAMS, &self->parseinfo.paramcount);
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_USER_DATA, (void *)self);
mysql_stmt_bind_param(self->stmt, self->params);
rc= Mrdb_execute_direct(self);
rc= Mrdb_execute_direct(self, statement, statement_len);
if (rc)
{
@ -774,7 +848,7 @@ PyObject *MrdbCursor_execute(MrdbCursor *self,
if (mysql_stmt_errno(self->stmt) == ER_UNSUPPORTED_PS)
{
MARIADB_BEGIN_ALLOW_THREADS(self);
self->is_text= 0;
self->parseinfo.is_text= 0;
rc= mysql_real_query(self->connection->mysql, statement, (unsigned long)statement_len);
MARIADB_END_ALLOW_THREADS(self);
@ -836,8 +910,6 @@ end:
error:
self->row_count= -1;
self->lastrow_id= 0;
MrdbParser_end(self->parser);
self->parser= NULL;
MrdbCursor_clear(self, 0);
return NULL;
}
@ -944,7 +1016,7 @@ static int MrdbCursor_fetchinternal(MrdbCursor *self)
self->fetched= 1;
if (!self->is_text)
if (!self->parseinfo.is_text)
{
rc= mysql_stmt_fetch(self->stmt);
if (rc == MYSQL_NO_DATA)
@ -1084,7 +1156,7 @@ MrdbCursor_scroll(MrdbCursor *self,
new_position= position; /* absolute */
}
if (!self->is_text)
if (!self->parseinfo.is_text)
{
mysql_stmt_data_seek(self->stmt, new_position);
}
@ -1224,7 +1296,7 @@ MrdbCursor_fetchall(MrdbCursor *self)
/* CONPY-99: Decrement Row to prevent memory leak */
Py_DECREF(Row);
}
self->row_count= (self->is_text) ? mysql_num_rows(self->result) :
self->row_count= (self->parseinfo.is_text) ? mysql_num_rows(self->result) :
mysql_stmt_num_rows(self->stmt);
return List;
}
@ -1244,7 +1316,7 @@ MrdbCursor_executemany_fallback(MrdbCursor *self,
if (rc)
goto error_mysql;
if (mysql_stmt_attr_set(self->stmt, STMT_ATTR_PREBIND_PARAMS, &self->param_count))
if (mysql_stmt_attr_set(self->stmt, STMT_ATTR_PREBIND_PARAMS, &self->parseinfo.paramcount))
{
goto error;
}
@ -1266,8 +1338,8 @@ MrdbCursor_executemany_fallback(MrdbCursor *self,
MARIADB_BEGIN_ALLOW_THREADS(self);
if (i==0)
{
rc= mysql_stmt_prepare(self->stmt, self->parser->statement.str,
(unsigned long)self->parser->statement.length);
rc= mysql_stmt_prepare(self->stmt, self->parseinfo.statement,
(unsigned long)self->parseinfo.statement_len);
}
if (!rc)
{
@ -1305,7 +1377,6 @@ MrdbCursor_executemany(MrdbCursor *self,
Py_ssize_t statement_len= 0;
int rc;
uint8_t do_prepare= 1;
char errmsg[128];
MARIADB_CHECK_STMT(self);
@ -1336,8 +1407,6 @@ MrdbCursor_executemany(MrdbCursor *self,
if (!self->is_prepared && self->statement)
{
MrdbCursor_clear(self, 0);
MrdbParser_end(self->parser);
self->parser= NULL;
}
if (!self->stmt)
@ -1348,11 +1417,7 @@ MrdbCursor_executemany(MrdbCursor *self,
goto error;
}
}
self->is_text= 0;
if (!self->parser)
{
/*
if (!(self->parser= MrdbParser_init(self->connection->mysql, statement, (size_t)statement_len)))
{
exit(-1);
@ -1362,9 +1427,8 @@ MrdbCursor_executemany(MrdbCursor *self,
PyErr_SetString(Mariadb_ProgrammingError, errmsg);
goto error;
}
CURSOR_SET_STATEMENT(self, statement, statement_len);
}
*/
if (mariadb_check_bulk_parameters(self, self->data))
goto error;
@ -1374,8 +1438,8 @@ MrdbCursor_executemany(MrdbCursor *self,
if (!(self->connection->extended_server_capabilities &
(MARIADB_CLIENT_STMT_BULK_OPERATIONS >> 32)))
{
if (MrdbCursor_executemany_fallback(self, self->parser->statement.str,
self->parser->statement.length))
if (MrdbCursor_executemany_fallback(self, self->parseinfo.statement,
self->parseinfo.statement_len))
goto error;
MARIADB_FREE_MEM(self->values);
Py_RETURN_NONE;
@ -1384,13 +1448,13 @@ MrdbCursor_executemany(MrdbCursor *self,
mysql_stmt_attr_set(self->stmt, STMT_ATTR_ARRAY_SIZE, &self->array_size);
if (do_prepare)
{
mysql_stmt_attr_set(self->stmt, STMT_ATTR_PREBIND_PARAMS, &self->param_count);
mysql_stmt_attr_set(self->stmt, STMT_ATTR_PREBIND_PARAMS, &self->parseinfo.paramcount);
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_USER_DATA, (void *)self);
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_PARAM, mariadb_param_update);
mysql_stmt_bind_param(self->stmt, self->params);
if (Mrdb_execute_direct(self))
// if (Mrdb_execute_direct(self))
{
mariadb_throw_exception(self->stmt, NULL, 1, NULL);
goto error;
@ -1413,8 +1477,6 @@ MrdbCursor_executemany(MrdbCursor *self,
Py_RETURN_NONE;
error:
MrdbCursor_clear(self, 0);
MrdbParser_end(self->parser);
self->parser= NULL;
return NULL;
}
@ -1428,16 +1490,9 @@ MrdbCursor_nextset(MrdbCursor *self)
{
return NULL;
}
/* hmmm */
if (!self->field_count)
{
mariadb_throw_exception(NULL, Mariadb_ProgrammingError, 0,
"Cursor doesn't have a result set");
return NULL;
}
MARIADB_BEGIN_ALLOW_THREADS(self);
if (!self->is_text)
if (!self->parseinfo.is_text)
rc= mysql_stmt_next_result(self->stmt);
else
{
@ -1455,9 +1510,9 @@ MrdbCursor_nextset(MrdbCursor *self)
Py_INCREF(Py_None);
return Py_None;
}
if (CURSOR_FIELD_COUNT(self))
if ((self->field_count= CURSOR_FIELD_COUNT(self)))
{
if (MrdbCursor_InitResultSet(self))
if (!MrdbCursor_InitResultSet(self))
{
return NULL;
}
@ -1471,13 +1526,7 @@ MrdbCursor_nextset(MrdbCursor *self)
static PyObject *
Mariadb_row_count(MrdbCursor *self)
{
/* PEP-249 requires to return -1 if the cursor was not executed before */
if (!self->statement)
{
return PyLong_FromLongLong(-1);
}
return PyLong_FromLongLong(self->row_count);
return PyLong_FromLongLong(CURSOR_NUM_ROWS(self));
}
static PyObject *
@ -1499,29 +1548,6 @@ MrdbCursor_warnings(MrdbCursor *self)
return PyLong_FromLong((long)CURSOR_WARNING_COUNT(self));
}
static PyObject *
MrdbCursor_getbuffered(MrdbCursor *self)
{
if (self->is_buffered)
{
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
}
static int
MrdbCursor_setbuffered(MrdbCursor *self, PyObject *arg)
{
if (!arg || !CHECK_TYPE(arg, &PyBool_Type))
{
PyErr_SetString(PyExc_TypeError, "Argument must be boolean");
return -1;
}
self->is_buffered= PyObject_IsTrue(arg);
return 0;
}
static PyObject *
MrdbCursor_lastrowid(MrdbCursor *self)
{
@ -1628,3 +1654,169 @@ end:
}
return rc;
}
static PyObject *
MrdbCursor_parse(MrdbCursor *self, PyObject *args)
{
const char *statement= NULL;
Py_ssize_t statement_len= 0;
MrdbParser *parser= NULL;
char errmsg[128];
if (self->parseinfo.statement)
{
MrdbCursor_clearparseinfo(&self->parseinfo);
}
if (!PyArg_ParseTuple(args, "s#|Ob", &statement, &statement_len))
{
return NULL;
}
if (!(parser= MrdbParser_init(self->connection->mysql, statement, statement_len)))
{
mariadb_throw_exception(NULL, Mariadb_ProgrammingError, 0,
"Can't initialize parser.");
return NULL;
}
if (MrdbParser_parse(parser, 0, errmsg, 128))
{
MrdbParser_end(parser);
PyErr_SetString(Mariadb_ProgrammingError, errmsg);
return NULL;
}
/* cleanup and save some parser stuff */
self->parseinfo.paramcount= parser->param_count;
self->parseinfo.paramstyle= parser->paramstyle;
self->parseinfo.statement= PyMem_RawCalloc(parser->statement.length + 1, 1);
memcpy(self->parseinfo.statement, parser->statement.str, parser->statement.length);
self->parseinfo.statement_len= parser->statement.length;
self->parseinfo.paramlist= parser->param_list;
self->parseinfo.is_text= (parser->command == SQL_NONE || parser->command == SQL_OTHER);
if (parser->paramstyle == PYFORMAT && parser->keys)
{
PyObject *tmp= PyTuple_New(parser->param_count);
for (uint32_t i= 0; i < parser->param_count; i++)
{
PyObject *key;
key= PyUnicode_FromString(parser->keys[i].str);
PyTuple_SetItem(tmp, i, key);
}
self->parseinfo.keys= tmp;
}
MrdbParser_end(parser);
return Py_None;
}
static PyObject *
MrdbCursor_execute_binary(MrdbCursor *self)
{
int rc;
MARIADB_CHECK_CONNECTION(self->connection, NULL);
if (!self->stmt &&
!(self->stmt= mysql_stmt_init(self->connection->mysql)))
{
mariadb_throw_exception(self->connection->mysql, NULL, 0, NULL);
goto error;
}
if (self->data && self->parseinfo.paramcount)
{
if (mariadb_check_execute_parameters(self, self->data))
goto error;
/* Load values */
if (mariadb_param_update(self, self->params, 0))
goto error;
}
if (self->reprepare)
{
printf("restting statement\n");
mysql_stmt_reset(self->stmt);
mysql_stmt_attr_set(self->stmt, STMT_ATTR_PREBIND_PARAMS, &self->parseinfo.paramcount);
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_USER_DATA, (void *)self);
}
if (self->parseinfo.paramcount)
mysql_stmt_bind_param(self->stmt, self->params);
if ((rc= Mrdb_execute_direct(self, self->parseinfo.statement, self->parseinfo.statement_len)))
{
mariadb_throw_exception(self->connection->mysql, NULL, 0, NULL);
goto error;
}
self->field_count= mysql_stmt_field_count(self->stmt);
Py_RETURN_NONE;
error:
return NULL;
}
static PyObject *
MrdbCursor_execute_text(MrdbCursor *self, PyObject *args)
{
int rc;
MYSQL *db;
char *statement;
size_t statement_len;
MARIADB_CHECK_CONNECTION(self->connection, NULL);
if (!PyArg_ParseTuple(args, "s#", &statement, &statement_len))
{
return NULL;
}
db= self->connection->mysql;
Py_BEGIN_ALLOW_THREADS;
rc= mysql_send_query(db, statement, statement_len);
Py_END_ALLOW_THREADS;
if (rc)
{
mariadb_throw_exception(db, NULL, 0, NULL);
return NULL;
}
Py_RETURN_NONE;
}
static PyObject *
MrdbCursor_readresponse(MrdbCursor *self)
{
int rc;
MYSQL *db;
MARIADB_CHECK_CONNECTION(self->connection, NULL);
db= self->connection->mysql;
if (self->parseinfo.is_text)
{
Py_BEGIN_ALLOW_THREADS;
rc= db->methods->db_read_query_result(db);
Py_END_ALLOW_THREADS;
if (rc)
{
mariadb_throw_exception(db, NULL, 0, NULL);
return NULL;
}
self->field_count= mysql_field_count(self->connection->mysql);
} else
{
self->field_count= mysql_stmt_field_count(self->stmt);
}
Py_RETURN_NONE;
}

View File

@ -28,6 +28,20 @@ const char *comment_start= "/*";
const char *comment_end= "*/";
const char literals[3]= {'\'', '\"', '`'};
static struct {
enum enum_binary_command command;
MrdbString str;
} binary_command[] =
{
{SQL_INSERT, {"INSERT", 6}},
{SQL_UPDATE, {"UPDATE", 6}},
{SQL_REPLACE, {"REPLACE", 7}},
{SQL_DELETE, {"DELETE", 6}},
{SQL_CALL, {"CALL", 4}},
{SQL_DO, {"DO", 2}},
{SQL_NONE, {NULL, 0}}
};
static uint8_t
check_keyword(char* ofs, char* end, char* keyword, size_t keylen)
{
@ -92,6 +106,8 @@ MrdbParser_init(MYSQL *mysql, const char *statement, size_t length)
memcpy(p->statement.str, statement, length);
p->statement.length= length;
p->mysql= mysql;
p->param_list= PyList_New(0);
p->param_count= 0;
}
return p;
}
@ -242,6 +258,7 @@ cont:
/* parmastyle = qmark */
if (*a == '?')
{
PyObject *tmp;
if (p->paramstyle && p->paramstyle != QMARK)
{
parser_error(errmsg, errmsg_len,
@ -250,6 +267,9 @@ cont:
}
p->paramstyle= QMARK;
p->param_count++;
tmp= PyLong_FromLong((long)(a - p->statement.str));
PyList_Append(p->param_list, tmp);
Py_DECREF(tmp);
a++;
continue;
}
@ -259,6 +279,7 @@ cont:
/* paramstyle format */
if (*(a+1) == 's' || *(a+1) == 'd')
{
PyObject *tmp;
if (p->paramstyle && p->paramstyle != FORMAT)
{
parser_error(errmsg, errmsg_len,
@ -269,6 +290,10 @@ cont:
*a= '?';
memmove(a+1, a+2, end - a);
end--;
tmp= PyLong_FromLong((long)(a - p->statement.str));
PyList_Append(p->param_list, tmp);
Py_DECREF(tmp);
a++;
p->param_count++;
continue;
@ -276,6 +301,9 @@ cont:
if (*(a+1) == '(')
{
char *val_end= strstr(a+1, ")s");
PyObject *tmp;
if (val_end)
{
ssize_t keylen= val_end - a + 1;
@ -288,6 +316,9 @@ cont:
p->paramstyle= PYFORMAT;
*a= '?';
p->param_count++;
tmp= PyLong_FromLong((long)(a - p->statement.str));
PyList_Append(p->param_list, tmp);
Py_DECREF(tmp);
if (p->keys)
{
MrdbString *m;
@ -317,7 +348,7 @@ cont:
memcpy(p->keys[p->param_count - 1].str, a + 2, keylen - 3);
p->keys[p->param_count - 1].length= keylen - 3;
memmove(a+1, val_end+2, end - a - keylen);
memmove(a+1, val_end+2, end - a - keylen + 1);
a+= 1;
end -= keylen;
continue;
@ -346,7 +377,24 @@ cont:
continue;
}
}
else {
/* determine SQL command */
if (p->command == SQL_NONE && p->param_count)
{
for (uint8_t i=0; binary_command[i].str.str; i++)
{
if (check_keyword(a, end, binary_command[i].str.str,
binary_command[i].str.length))
{
p->command= binary_command[i].command;
break;
}
}
if (p->command == SQL_NONE)
p->command= SQL_OTHER;
}
}
lastchar= *a;
a++;
}

View File

@ -0,0 +1,60 @@
#
# Copyright (C) 2021 Georg Richter and MariaDB Corporation AB
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Library General Public License for more details.
# You should have received a copy of the GNU Library General Public
# License along with this library; if not see <http://www.gnu.org/licenses>
# or write to the Free Software Foundation, Inc.,
# 51 Franklin St., Fifth Floor, Boston, MA 02110, USA
#
import struct
PROT_CMD_SLEEP= 0
PROT_CMD_QUIT= 1
PROT_CMD_INIT_DB= 2
PROT_CMD_QUERY= 3
PROT_CMD_FIELD_LIST= 4
PROT_CMD_CREATE_DB= 5
PROT_CMD_DROP_DB= 6
PROT_CMD_REFRESH= 7
PROT_CMD_SHUTDOWN= 8
PROT_CMD_STATISTICS= 9
PROT_CMD_PROCESS_INFO= 10
PROT_CMD_CONNECT= 11
PROT_CMD_PROCESS_KILL= 12
PROT_CMD_DEBUG= 13
PROT_CMD_PING= 14
PROT_CMD_TIME= 15,
PROT_CMD_DELAYED_INSERT= 16
PROT_CMD_CHANGE_USER= 17
PROT_CMD_BINLOG_DUMP= 18
PROT_CMD_TABLE_DUMP= 19
PROT_CMD_CONNECT_OUT = 20
PROT_CMD_REGISTER_SLAVE= 21
PROT_CMD_STMT_PREPARE= 22
PROT_CMD_STMT_EXECUTE= 23
PROT_CMD_STMT_SEND_LONG_DATA = 24
PROT_CMD_STMT_CLOSE= 25
PROT_CMD_STMT_RESET= 26
PROT_CMD_SET_OPTION= 27
PROT_CMD_STMT_FETCH= 28
PROT_CMD_DAEMON= 29
PROT_CMD_UNSUPPORTED= 30
PROT_CMD_RESET_CONNECTION= 31
PROT_CMD_STMT_BULK_EXECUTE= 250
def prot_length(buffer_size):
def send_command(socket, command, buffer):

3
mariadb/release_info.py Normal file
View File

@ -0,0 +1,3 @@
__author__='Georg Richter'
__version__='1.1.0'
__version_info__=(1, 1, 0, 'alpha', 0)

View File

@ -42,7 +42,7 @@ def dequote(s):
def get_config(options):
required_version = "3.1.5"
required_version = "3.2.0"
no_env = 0
static = options["link_static"]

View File

@ -98,6 +98,6 @@ setup(name='mariadb',
py_modules=['mariadb.__init__', 'mariadb.constants.CLIENT', 'mariadb.constants.CURSOR',
'mariadb.constants.STATUS',
'mariadb.field', 'mariadb.dbapi20', 'mariadb.connections', 'mariadb.connectionpool',
'mariadb.cursor', 'mariadb.release_info',
'mariadb.cursor', 'mariadb.release_info', 'mariadb.async_connections', 'mariadb.codecs',
'mariadb.constants.FIELD_TYPE', 'mariadb.constants.FIELD_FLAG', 'mariadb.constants.INDICATOR'],
)