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

View File

@ -8,7 +8,6 @@ Minimum supported Python version is 3.6
''' '''
from ._mariadb import ( from ._mariadb import (
Binary,
DataError, DataError,
DatabaseError, DatabaseError,
Error, Error,
@ -30,6 +29,7 @@ _POOLS= _CONNECTION_POOLS= {}
from mariadb.dbapi20 import * from mariadb.dbapi20 import *
from mariadb.connectionpool import * from mariadb.connectionpool import *
from mariadb.cursor import Cursor from mariadb.cursor import Cursor
from mariadb.async_connections import *
from mariadb.release_info import __version__ as __version__ from mariadb.release_info import __version__ as __version__
from mariadb.release_info import __version_info__ as __version_info__ from mariadb.release_info import __version_info__ as __version_info__
from mariadb.release_info import __author__ as __author__ from mariadb.release_info import __author__ as __author__
@ -43,14 +43,18 @@ def Binary(obj):
def connect(*args, **kwargs): def connect(*args, **kwargs):
from mariadb.connections import Connection from mariadb.connections import Connection
if kwargs and "pool_name" in kwargs: if kwargs:
if not kwargs["pool_name"] in mariadb._CONNECTION_POOLS: if "pool_name" in kwargs:
pool= mariadb.ConnectionPool(**kwargs) if not kwargs["pool_name"] in mariadb._CONNECTION_POOLS:
else: pool= mariadb.ConnectionPool(**kwargs)
pool= mariadb._CONNECTION_POOLS[kwargs["pool_name"]] else:
c= pool.get_connection() pool= mariadb._CONNECTION_POOLS[kwargs["pool_name"]]
return c c= pool.get_connection()
return c
is_async= kwargs.get("asynchronous", False)
if is_async:
return AsyncConnection(*args, **kwargs)
return Connection(*args, **kwargs) return Connection(*args, **kwargs)
Connection= connect 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.__last_used = 0
# self._autocommit= kwargs.pop("autocommit", True) # self._autocommit= kwargs.pop("autocommit", True)
# self._converter= kwargs.pop("converter", None) self._converter= kwargs.pop("converter", None)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)

View File

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

View File

@ -20,6 +20,13 @@
#include "mariadb_python.h" #include "mariadb_python.h"
#include "docs/connection.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[]= { char *dsn_keys[]= {
"dsn", "host", "user", "password", "database", "port", "unix_socket", "dsn", "host", "user", "password", "database", "port", "unix_socket",
"connect_timeout", "read_timeout", "write_timeout", "connect_timeout", "read_timeout", "write_timeout",
@ -31,7 +38,7 @@ char *dsn_keys[]= {
"client_flag", "pool_name", "pool_size", "client_flag", "pool_name", "pool_size",
"pool_reset_connection", "plugin_dir", "pool_reset_connection", "plugin_dir",
"username", "db", "passwd", "username", "db", "passwd",
"autocommit", "converter", "autocommit", "converter", "asynchronous",
NULL NULL
}; };
@ -47,6 +54,9 @@ static PyObject
static PyObject * static PyObject *
MrdbConnection_exception(PyObject *self, void *closure); MrdbConnection_exception(PyObject *self, void *closure);
static PyObject *
MrdbConnection_query(MrdbConnection *self, PyObject *args);
#define GETTER_EXCEPTION(name, exception, doc)\ #define GETTER_EXCEPTION(name, exception, doc)\
{ name,MrdbConnection_exception, NULL, doc, &exception } { name,MrdbConnection_exception, NULL, doc, &exception }
@ -93,6 +103,10 @@ MrdbConnection_get_server_version(MrdbConnection *self);
static PyObject * static PyObject *
MrdbConnection_get_server_status(MrdbConnection *self); MrdbConnection_get_server_status(MrdbConnection *self);
PyObject *
MrdbConnection_executecommand(MrdbConnection *self,
PyObject *args);
static PyGetSetDef static PyGetSetDef
MrdbConnection_sets[]= MrdbConnection_sets[]=
{ {
@ -131,6 +145,9 @@ MrdbConnection_sets[]=
static PyMethodDef static PyMethodDef
MrdbConnection_Methods[] = MrdbConnection_Methods[] =
{ {
{"_query", (PyCFunction)MrdbConnection_query,
METH_VARARGS,
NULL},
/* PEP-249 methods */ /* PEP-249 methods */
{"close", (PyCFunction)MrdbConnection_close, {"close", (PyCFunction)MrdbConnection_close,
METH_NOARGS, METH_NOARGS,
@ -209,6 +226,11 @@ MrdbConnection_Methods[] =
METH_VARARGS, METH_VARARGS,
connection_escape_string__doc__ connection_escape_string__doc__
}, },
/* Internal methods */
{ "_execute_command",
(PyCFunction)MrdbConnection_executecommand,
METH_VARARGS, NULL
},
{NULL} /* always last */ {NULL} /* always last */
}; };
@ -265,6 +287,16 @@ PyMemberDef MrdbConnection_Members[] =
offsetof(MrdbConnection, tls_version), offsetof(MrdbConnection, tls_version),
READONLY, READONLY,
"TLS protocol version used by connection"}, "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 */ {NULL} /* always last */
}; };
@ -311,9 +343,10 @@ MrdbConnection_Initialize(MrdbConnection *self,
compress= 0, ssl_verify_cert= 0; compress= 0, ssl_verify_cert= 0;
PyObject *autocommit_obj= NULL; PyObject *autocommit_obj= NULL;
PyObject *converter= NULL; PyObject *converter= NULL;
PyObject *asynchronous= NULL;
if (!PyArg_ParseTupleAndKeywords(args, dsnargs, if (!PyArg_ParseTupleAndKeywords(args, dsnargs,
"|zzzzziziiibbzzzzzzzzzzibizibzzzzOO:connect", "|zzzzziziiibbzzzzzzzzzzibizibzzzzO!OO!:connect",
dsn_keys, dsn_keys,
&dsn, &host, &user, &password, &schema, &port, &socket, &dsn, &host, &user, &password, &schema, &port, &socket,
&connect_timeout, &read_timeout, &write_timeout, &connect_timeout, &read_timeout, &write_timeout,
@ -325,21 +358,13 @@ MrdbConnection_Initialize(MrdbConnection *self,
&client_flags, &pool_name, &pool_size, &client_flags, &pool_name, &pool_size,
&reset_session, &plugin_dir, &reset_session, &plugin_dir,
&user, &schema, &password, &user, &schema, &password,
&autocommit_obj, &converter)) &PyBool_Type, &autocommit_obj,
&converter,
&PyBool_Type, &asynchronous))
{ {
return -1; 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) if (dsn)
{ {
mariadb_throw_exception(NULL, Mariadb_ProgrammingError, 1, mariadb_throw_exception(NULL, Mariadb_ProgrammingError, 1,
@ -370,40 +395,50 @@ MrdbConnection_Initialize(MrdbConnection *self,
if (local_infile != 0xFF) 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) if (compress)
{ {
mysql_optionsv(self->mysql, MYSQL_OPT_COMPRESS, 1); MADB_SET_OPTION(self->mysql, MYSQL_OPT_COMPRESS, 1);
} }
if (init_command) 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) { if (plugin_dir) {
mysql_optionsv(self->mysql, MYSQL_PLUGIN_DIR, plugin_dir); MADB_SET_OPTION(self->mysql, MYSQL_PLUGIN_DIR, plugin_dir);
} else { } else {
#if defined(DEFAULT_PLUGINS_SUBDIR) #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 #endif
} }
/* read defaults from configuration file(s) */ /* read defaults from configuration file(s) */
if (default_file) 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) 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 */ /* set timeouts */
if (connect_timeout) 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) 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) 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 */ /* set TLS/SSL options */
if (ssl_enforce || ssl_key || ssl_ca || ssl_cert || ssl_capath || ssl_cipher) 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_capath,
(const char *)ssl_cipher); (const char *)ssl_cipher);
if (ssl_crl) 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) 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) 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; Py_BEGIN_ALLOW_THREADS;
mysql_real_connect(self->mysql, host, user, password, schema, port, mysql_real_connect(self->mysql, host, user, password, schema, port,
socket, client_flags); socket, client_flags);
@ -460,7 +502,13 @@ MrdbConnection_Initialize(MrdbConnection *self,
PyTuple_SetItem(self->server_version_info, 2, PyLong_FromLong(patch))) PyTuple_SetItem(self->server_version_info, 2, PyLong_FromLong(patch)))
goto end; goto end;
} }
/*
if (asynchronous && PyObject_IsTrue(asynchronous))
{
self->asynchronous= 1;
MADB_SET_OPTION(self->mysql, MARIADB_OPT_SKIP_READ_RESPONSE, &self->asynchronous);
}
*/
end: end:
if (PyErr_Occurred()) if (PyErr_Occurred())
return -1; return -1;
@ -580,19 +628,86 @@ void MrdbConnection_dealloc(MrdbConnection *self)
mysql_close(self->mysql); mysql_close(self->mysql);
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
} }
Py_DECREF(self->server_version_info); Py_XDECREF(self->server_version_info);
Py_TYPE(self)->tp_free((PyObject*)self); 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) PyObject *MrdbConnection_close(MrdbConnection *self)
{ {
MARIADB_CHECK_CONNECTION(self, NULL); MARIADB_CHECK_CONNECTION(self, NULL);
/* Todo: check if all the cursor stuff is deleted (when using prepared /* Todo: check if all the cursor stuff is deleted (when using prepared
statements this should be handled in mysql_close) */ statements this should be handled in mysql_close) */
if (self->converter) Py_XDECREF(self->converter);
Py_DECREF(self->converter);
self->converter= NULL; self->converter= NULL;
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
@ -1329,6 +1444,27 @@ static PyObject *MrdbConnection_get_server_status(MrdbConnection *self)
return PyLong_FromLong((long)server_status); 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 tabstop=4 */
/* vim: set shiftwidth=4 */ /* vim: set shiftwidth=4 */
/* vim: set expandtab */ /* vim: set expandtab */

View File

@ -38,6 +38,18 @@ static PyObject *
MrdbCursor_executemany(MrdbCursor *self, MrdbCursor_executemany(MrdbCursor *self,
PyObject *args); 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 * static PyObject *
MrdbCursor_description(MrdbCursor *self); MrdbCursor_description(MrdbCursor *self);
@ -67,6 +79,9 @@ MrdbCursor_fieldcount(MrdbCursor *self);
void void
field_fetch_fromtext(MrdbCursor *self, char *data, unsigned int column); field_fetch_fromtext(MrdbCursor *self, char *data, unsigned int column);
static PyObject *
MrdbCursor_readresponse(MrdbCursor *self);
void void
field_fetch_callback(void *data, unsigned int column, unsigned char **row); field_fetch_callback(void *data, unsigned int column, unsigned char **row);
static PyObject *mariadb_get_sequence_or_tuple(MrdbCursor *self); static PyObject *mariadb_get_sequence_or_tuple(MrdbCursor *self);
@ -85,19 +100,19 @@ strncpy((a)->statement, (s), (l));\
(a)->statement[(l)]= 0; (a)->statement[(l)]= 0;
#define CURSOR_FIELD_COUNT(a)\ #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)\ #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)\ #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)\ #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)\ #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_name= "mariadb.Row";
static char *mariadb_named_tuple_desc= "Named tupled 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_count(MrdbCursor *self);
static PyObject *Mariadb_row_number(MrdbCursor *self); static PyObject *Mariadb_row_number(MrdbCursor *self);
static PyObject *MrdbCursor_warnings(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_lastrowid(MrdbCursor *self);
static PyObject *MrdbCursor_closed(MrdbCursor *self); static PyObject *MrdbCursor_closed(MrdbCursor *self);
static PyObject *MrdbCursor_sp_outparams(MrdbCursor *self); static PyObject *MrdbCursor_sp_outparams(MrdbCursor *self);
@ -125,8 +138,6 @@ static PyGetSetDef MrdbCursor_sets[]=
cursor_warnings__doc__, NULL}, cursor_warnings__doc__, NULL},
{"closed", (getter)MrdbCursor_closed, NULL, {"closed", (getter)MrdbCursor_closed, NULL,
cursor_closed__doc__, NULL}, cursor_closed__doc__, NULL},
{"buffered", (getter)MrdbCursor_getbuffered, (setter)MrdbCursor_setbuffered,
cursor_buffered__doc__, NULL},
{"rownumber", (getter)Mariadb_row_number, NULL, {"rownumber", (getter)Mariadb_row_number, NULL,
cursor_rownumber__doc__, NULL}, cursor_rownumber__doc__, NULL},
{"sp_outparams", (getter)MrdbCursor_sp_outparams, NULL, {"sp_outparams", (getter)MrdbCursor_sp_outparams, NULL,
@ -161,7 +172,7 @@ static PyMethodDef MrdbCursor_Methods[] =
{"fieldcount", (PyCFunction)MrdbCursor_fieldcount, {"fieldcount", (PyCFunction)MrdbCursor_fieldcount,
METH_NOARGS, METH_NOARGS,
cursor_field_count__doc__}, cursor_field_count__doc__},
{"nextset", (PyCFunction)MrdbCursor_nextset, {"_nextset", (PyCFunction)MrdbCursor_nextset,
METH_NOARGS, METH_NOARGS,
cursor_nextset__doc__}, cursor_nextset__doc__},
{"setinputsizes", (PyCFunction)Mariadb_no_operation, {"setinputsizes", (PyCFunction)Mariadb_no_operation,
@ -176,6 +187,22 @@ static PyMethodDef MrdbCursor_Methods[] =
{"scroll", (PyCFunction)MrdbCursor_scroll, {"scroll", (PyCFunction)MrdbCursor_scroll,
METH_VARARGS | METH_KEYWORDS, METH_VARARGS | METH_KEYWORDS,
cursor_scroll__doc__}, 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 */ {NULL} /* always last */
}; };
@ -183,11 +210,46 @@ static struct PyMemberDef MrdbCursor_Members[] =
{ {
{"statement", {"statement",
T_STRING, T_STRING,
offsetof(MrdbCursor, statement), offsetof(MrdbCursor, parseinfo.statement),
READONLY, READONLY,
cursor_statement__doc__}, 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", {"buffered",
T_BYTE, T_BOOL,
offsetof(MrdbCursor, is_buffered), offsetof(MrdbCursor, is_buffered),
0, 0,
cursor_buffered__doc__}, cursor_buffered__doc__},
@ -196,6 +258,23 @@ static struct PyMemberDef MrdbCursor_Members[] =
offsetof(MrdbCursor, row_array_size), offsetof(MrdbCursor, row_array_size),
0, 0,
cursor_arraysize__doc__}, 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} {NULL}
}; };
@ -211,14 +290,11 @@ buffered: buffered or unbuffered result sets
static int MrdbCursor_initialize(MrdbCursor *self, PyObject *args, static int MrdbCursor_initialize(MrdbCursor *self, PyObject *args,
PyObject *kwargs) PyObject *kwargs)
{ {
char *key_words[]= {"", "named_tuple", "dictionary", "prefetch_size", "cursor_type", char *key_words[]= {"", "prefetch_size", "cursor_type",
"buffered", "prepared", "binary", NULL}; "prepared", "binary", NULL};
PyObject *connection; PyObject *connection;
uint8_t is_named_tuple= 0;
uint8_t is_dictionary= 0;
unsigned long cursor_type= 0, unsigned long cursor_type= 0,
prefetch_rows= 0; prefetch_rows= 0;
uint8_t is_buffered= 0;
uint8_t is_prepared= 0; uint8_t is_prepared= 0;
uint8_t is_binary= 0; uint8_t is_binary= 0;
@ -226,9 +302,8 @@ static int MrdbCursor_initialize(MrdbCursor *self, PyObject *args,
return -1; return -1;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, if (!PyArg_ParseTupleAndKeywords(args, kwargs,
"O!|bbkkbbb", key_words, &MrdbConnection_Type, &connection, "O!|kkii", key_words, &MrdbConnection_Type, &connection,
&is_named_tuple, &is_dictionary, &prefetch_rows, &cursor_type, &is_buffered, &prefetch_rows, &cursor_type, &is_prepared, &is_binary))
&is_prepared, &is_binary))
return -1; return -1;
if (!((MrdbConnection *)connection)->mysql) if (!((MrdbConnection *)connection)->mysql)
@ -246,29 +321,11 @@ static int MrdbCursor_initialize(MrdbCursor *self, PyObject *args,
return -1; 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); Py_INCREF(connection);
self->connection= (MrdbConnection *)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_prepared= is_prepared;
self->is_text= 0; self->parseinfo.is_text= 0;
self->stmt= NULL; self->stmt= NULL;
self->cursor_type= cursor_type; 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) /* {{{ MrdbCursor_clear_result(MrdbCursor *self)
clear pending result sets clear pending result sets
*/ */
static void MrdbCursor_clear_result(MrdbCursor *self) static void MrdbCursor_clear_result(MrdbCursor *self)
{ {
if (!self->is_text && if (!self->parseinfo.is_text &&
self->stmt) self->stmt)
{ {
/* free current result */ /* free current result */
@ -382,7 +447,7 @@ static void MrdbCursor_clear_result(MrdbCursor *self)
{ {
mysql_stmt_free_result(self->stmt); mysql_stmt_free_result(self->stmt);
} }
} else if (self->is_text) } else if (self->parseinfo.is_text)
{ {
/* free current result */ /* free current result */
if (self->result) if (self->result)
@ -413,7 +478,7 @@ void MrdbCursor_clear(MrdbCursor *self, uint8_t new_stmt)
/* clear pending result sets */ /* clear pending result sets */
MrdbCursor_clear_result(self); MrdbCursor_clear_result(self);
if (!self->is_text && self->stmt) { if (!self->parseinfo.is_text && self->stmt) {
if (new_stmt) if (new_stmt)
{ {
mysql_stmt_close(self->stmt); mysql_stmt_close(self->stmt);
@ -439,7 +504,8 @@ void MrdbCursor_clear(MrdbCursor *self, uint8_t new_stmt)
self->fields= NULL; self->fields= NULL;
self->row_count= 0; self->row_count= 0;
self->affected_rows= 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->values);
MARIADB_FREE_MEM(self->bind); MARIADB_FREE_MEM(self->bind);
MARIADB_FREE_MEM(self->statement); MARIADB_FREE_MEM(self->statement);
@ -474,7 +540,7 @@ void ma_cursor_close(MrdbCursor *self)
if (!self->is_closed) if (!self->is_closed)
{ {
MrdbCursor_clear_result(self); 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 /* Todo: check if all the cursor stuff is deleted (when using prepared
statements this should be handled in mysql_stmt_close) */ statements this should be handled in mysql_stmt_close) */
@ -485,14 +551,9 @@ void ma_cursor_close(MrdbCursor *self)
} }
MrdbCursor_clear(self, 0); MrdbCursor_clear(self, 0);
if (self->is_text && self->stmt) if (!self->parseinfo.is_text && self->stmt)
mysql_stmt_close(self->stmt); mysql_stmt_close(self->stmt);
if (self->parser)
{
MrdbParser_end(self->parser);
self->parser= NULL;
}
self->is_closed= 1; self->is_closed= 1;
} }
} }
@ -520,7 +581,7 @@ static int Mrdb_GetFieldInfo(MrdbCursor *self)
if (self->field_count) if (self->field_count)
{ {
if (self->is_text) if (self->parseinfo.is_text)
{ {
self->result= (self->is_buffered) ? mysql_store_result(self->connection->mysql) : self->result= (self->is_buffered) ? mysql_store_result(self->connection->mysql) :
mysql_use_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->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); mariadb_stmt_fetch_fields(self->stmt);
if (self->result_format == RESULT_NAMED_TUPLE) { if (self->result_format == RESULT_NAMED_TUPLE) {
@ -571,10 +632,8 @@ static int Mrdb_GetFieldInfo(MrdbCursor *self)
return 0; 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->sequence_fields);
MARIADB_FREE_MEM(self->values); MARIADB_FREE_MEM(self->values);
@ -585,13 +644,22 @@ static int MrdbCursor_InitResultSet(MrdbCursor *self)
} }
if (Mrdb_GetFieldInfo(self)) if (Mrdb_GetFieldInfo(self))
return 1; return NULL;
if (!(self->values= (PyObject**)PyMem_RawCalloc(self->field_count, sizeof(PyObject *)))) if (!(self->values= (PyObject**)PyMem_RawCalloc(self->field_count, sizeof(PyObject *))))
return 1; return NULL;
if (!self->is_text) if (!self->parseinfo.is_text)
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_RESULT, field_fetch_callback); 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) static Py_ssize_t data_count(PyObject *data)
@ -612,26 +680,38 @@ static Py_ssize_t data_count(PyObject *data)
return 0; 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; int rc;
MARIADB_BEGIN_ALLOW_THREADS(self); 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 /* execute_direct was implemented together with bulk operations, so we need
to check if MARIADB_CLIENT_STMT_BULK_OPERATIONS is set in extended server to check if MARIADB_CLIENT_STMT_BULK_OPERATIONS is set in extended server
capabilities */ capabilities */
if (!(self->connection->extended_server_capabilities & if (!(self->connection->extended_server_capabilities &
(MARIADB_CLIENT_STMT_BULK_OPERATIONS >> 32))) (MARIADB_CLIENT_STMT_BULK_OPERATIONS >> 32)))
{ {
if (!(rc= mysql_stmt_prepare(self->stmt, self->parser->statement.str, if (!(rc= mysql_stmt_prepare(self->stmt, statement, (unsigned long)statement_len)))
(unsigned long)self->parser->statement.length)))
{ {
rc= mysql_stmt_execute(self->stmt); rc= mysql_stmt_execute(self->stmt);
} }
} else { } else {
rc= mariadb_stmt_execute_direct(self->stmt, self->parser->statement.str, rc= mariadb_stmt_execute_direct(self->stmt, statement, statement_len);
self->parser->statement.length);
} }
end:
MARIADB_END_ALLOW_THREADS(self); MARIADB_END_ALLOW_THREADS(self);
return rc; return rc;
} }
@ -650,7 +730,6 @@ PyObject *MrdbCursor_execute(MrdbCursor *self,
int rc= 0; int rc= 0;
int8_t is_buffered= -1; int8_t is_buffered= -1;
static char *key_words[]= {"", "", "buffered", NULL}; static char *key_words[]= {"", "", "buffered", NULL};
char errmsg[128];
MARIADB_CHECK_STMT(self); MARIADB_CHECK_STMT(self);
if (PyErr_Occurred()) if (PyErr_Occurred())
@ -661,22 +740,20 @@ PyObject *MrdbCursor_execute(MrdbCursor *self,
return NULL; return NULL;
/* If we don't have a prepared cursor, we need to end/free parser */ /* 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); MARIADB_FREE_MEM(self->parseinfo.statement);
self->parser= NULL; memset(&self->parseinfo, 0, sizeof(MrdbParseInfo));
} }
if (is_buffered != -1) if (is_buffered != -1)
self->is_buffered= is_buffered; self->is_buffered= is_buffered;
/* if there are no parameters specified, we execute the statement in text protocol */ /* 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 /* in case statement was executed before, we need to clear, since we don't use
binary protocol */ binary protocol */
MrdbParser_end(self->parser);
self->parser= NULL;
MrdbCursor_clear(self, 0); MrdbCursor_clear(self, 0);
MARIADB_BEGIN_ALLOW_THREADS(self); MARIADB_BEGIN_ALLOW_THREADS(self);
rc= mysql_real_query(self->connection->mysql, statement, (unsigned long)statement_len); 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); mariadb_throw_exception(self->connection->mysql, NULL, 0, NULL);
goto error; goto error;
} }
self->is_text= 1; self->parseinfo.is_text= 1;
CURSOR_SET_STATEMENT(self, statement, statement_len); CURSOR_SET_STATEMENT(self, statement, statement_len);
} }
else else
@ -713,12 +790,10 @@ PyObject *MrdbCursor_execute(MrdbCursor *self,
do_prepare= 0; do_prepare= 0;
} else { } else {
MrdbCursor_clear(self, 1); MrdbCursor_clear(self, 1);
MrdbParser_end(self->parser);
self->parser= NULL;
} }
} }
self->is_text= 0; self->parseinfo.is_text= 0;
/*
if (!self->parser) if (!self->parser)
{ {
self->parser= MrdbParser_init(self->connection->mysql, statement, statement_len); self->parser= MrdbParser_init(self->connection->mysql, statement, statement_len);
@ -753,19 +828,18 @@ PyObject *MrdbCursor_execute(MrdbCursor *self,
self->data= Data; self->data= Data;
/* Load values */
if (mariadb_param_update(self, self->params, 0)) if (mariadb_param_update(self, self->params, 0))
goto error; goto error;
} } */
if (do_prepare) if (do_prepare)
{ {
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CURSOR_TYPE, &self->cursor_type); 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_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_attr_set(self->stmt, STMT_ATTR_CB_USER_DATA, (void *)self);
mysql_stmt_bind_param(self->stmt, self->params); mysql_stmt_bind_param(self->stmt, self->params);
rc= Mrdb_execute_direct(self); rc= Mrdb_execute_direct(self, statement, statement_len);
if (rc) if (rc)
{ {
@ -774,7 +848,7 @@ PyObject *MrdbCursor_execute(MrdbCursor *self,
if (mysql_stmt_errno(self->stmt) == ER_UNSUPPORTED_PS) if (mysql_stmt_errno(self->stmt) == ER_UNSUPPORTED_PS)
{ {
MARIADB_BEGIN_ALLOW_THREADS(self); 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); rc= mysql_real_query(self->connection->mysql, statement, (unsigned long)statement_len);
MARIADB_END_ALLOW_THREADS(self); MARIADB_END_ALLOW_THREADS(self);
@ -836,8 +910,6 @@ end:
error: error:
self->row_count= -1; self->row_count= -1;
self->lastrow_id= 0; self->lastrow_id= 0;
MrdbParser_end(self->parser);
self->parser= NULL;
MrdbCursor_clear(self, 0); MrdbCursor_clear(self, 0);
return NULL; return NULL;
} }
@ -944,7 +1016,7 @@ static int MrdbCursor_fetchinternal(MrdbCursor *self)
self->fetched= 1; self->fetched= 1;
if (!self->is_text) if (!self->parseinfo.is_text)
{ {
rc= mysql_stmt_fetch(self->stmt); rc= mysql_stmt_fetch(self->stmt);
if (rc == MYSQL_NO_DATA) if (rc == MYSQL_NO_DATA)
@ -1084,7 +1156,7 @@ MrdbCursor_scroll(MrdbCursor *self,
new_position= position; /* absolute */ new_position= position; /* absolute */
} }
if (!self->is_text) if (!self->parseinfo.is_text)
{ {
mysql_stmt_data_seek(self->stmt, new_position); mysql_stmt_data_seek(self->stmt, new_position);
} }
@ -1224,7 +1296,7 @@ MrdbCursor_fetchall(MrdbCursor *self)
/* CONPY-99: Decrement Row to prevent memory leak */ /* CONPY-99: Decrement Row to prevent memory leak */
Py_DECREF(Row); 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); mysql_stmt_num_rows(self->stmt);
return List; return List;
} }
@ -1244,7 +1316,7 @@ MrdbCursor_executemany_fallback(MrdbCursor *self,
if (rc) if (rc)
goto error_mysql; 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; goto error;
} }
@ -1266,8 +1338,8 @@ MrdbCursor_executemany_fallback(MrdbCursor *self,
MARIADB_BEGIN_ALLOW_THREADS(self); MARIADB_BEGIN_ALLOW_THREADS(self);
if (i==0) if (i==0)
{ {
rc= mysql_stmt_prepare(self->stmt, self->parser->statement.str, rc= mysql_stmt_prepare(self->stmt, self->parseinfo.statement,
(unsigned long)self->parser->statement.length); (unsigned long)self->parseinfo.statement_len);
} }
if (!rc) if (!rc)
{ {
@ -1305,7 +1377,6 @@ MrdbCursor_executemany(MrdbCursor *self,
Py_ssize_t statement_len= 0; Py_ssize_t statement_len= 0;
int rc; int rc;
uint8_t do_prepare= 1; uint8_t do_prepare= 1;
char errmsg[128];
MARIADB_CHECK_STMT(self); MARIADB_CHECK_STMT(self);
@ -1336,8 +1407,6 @@ MrdbCursor_executemany(MrdbCursor *self,
if (!self->is_prepared && self->statement) if (!self->is_prepared && self->statement)
{ {
MrdbCursor_clear(self, 0); MrdbCursor_clear(self, 0);
MrdbParser_end(self->parser);
self->parser= NULL;
} }
if (!self->stmt) if (!self->stmt)
@ -1348,12 +1417,8 @@ MrdbCursor_executemany(MrdbCursor *self,
goto error; goto error;
} }
} }
/*
self->is_text= 0; if (!(self->parser= MrdbParser_init(self->connection->mysql, statement, (size_t)statement_len)))
if (!self->parser)
{
if (!(self->parser= MrdbParser_init(self->connection->mysql, statement, (size_t)statement_len)))
{ {
exit(-1); exit(-1);
} }
@ -1362,9 +1427,8 @@ MrdbCursor_executemany(MrdbCursor *self,
PyErr_SetString(Mariadb_ProgrammingError, errmsg); PyErr_SetString(Mariadb_ProgrammingError, errmsg);
goto error; goto error;
} }
CURSOR_SET_STATEMENT(self, statement, statement_len);
} }
*/
if (mariadb_check_bulk_parameters(self, self->data)) if (mariadb_check_bulk_parameters(self, self->data))
goto error; goto error;
@ -1374,8 +1438,8 @@ MrdbCursor_executemany(MrdbCursor *self,
if (!(self->connection->extended_server_capabilities & if (!(self->connection->extended_server_capabilities &
(MARIADB_CLIENT_STMT_BULK_OPERATIONS >> 32))) (MARIADB_CLIENT_STMT_BULK_OPERATIONS >> 32)))
{ {
if (MrdbCursor_executemany_fallback(self, self->parser->statement.str, if (MrdbCursor_executemany_fallback(self, self->parseinfo.statement,
self->parser->statement.length)) self->parseinfo.statement_len))
goto error; goto error;
MARIADB_FREE_MEM(self->values); MARIADB_FREE_MEM(self->values);
Py_RETURN_NONE; Py_RETURN_NONE;
@ -1384,13 +1448,13 @@ MrdbCursor_executemany(MrdbCursor *self,
mysql_stmt_attr_set(self->stmt, STMT_ATTR_ARRAY_SIZE, &self->array_size); mysql_stmt_attr_set(self->stmt, STMT_ATTR_ARRAY_SIZE, &self->array_size);
if (do_prepare) 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_USER_DATA, (void *)self);
mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_PARAM, mariadb_param_update); mysql_stmt_attr_set(self->stmt, STMT_ATTR_CB_PARAM, mariadb_param_update);
mysql_stmt_bind_param(self->stmt, self->params); 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); mariadb_throw_exception(self->stmt, NULL, 1, NULL);
goto error; goto error;
@ -1413,8 +1477,6 @@ MrdbCursor_executemany(MrdbCursor *self,
Py_RETURN_NONE; Py_RETURN_NONE;
error: error:
MrdbCursor_clear(self, 0); MrdbCursor_clear(self, 0);
MrdbParser_end(self->parser);
self->parser= NULL;
return NULL; return NULL;
} }
@ -1428,16 +1490,9 @@ MrdbCursor_nextset(MrdbCursor *self)
{ {
return NULL; 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); MARIADB_BEGIN_ALLOW_THREADS(self);
if (!self->is_text) if (!self->parseinfo.is_text)
rc= mysql_stmt_next_result(self->stmt); rc= mysql_stmt_next_result(self->stmt);
else else
{ {
@ -1455,9 +1510,9 @@ MrdbCursor_nextset(MrdbCursor *self)
Py_INCREF(Py_None); Py_INCREF(Py_None);
return 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; return NULL;
} }
@ -1471,13 +1526,7 @@ MrdbCursor_nextset(MrdbCursor *self)
static PyObject * static PyObject *
Mariadb_row_count(MrdbCursor *self) Mariadb_row_count(MrdbCursor *self)
{ {
/* PEP-249 requires to return -1 if the cursor was not executed before */ return PyLong_FromLongLong(CURSOR_NUM_ROWS(self));
if (!self->statement)
{
return PyLong_FromLongLong(-1);
}
return PyLong_FromLongLong(self->row_count);
} }
static PyObject * static PyObject *
@ -1499,29 +1548,6 @@ MrdbCursor_warnings(MrdbCursor *self)
return PyLong_FromLong((long)CURSOR_WARNING_COUNT(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 * static PyObject *
MrdbCursor_lastrowid(MrdbCursor *self) MrdbCursor_lastrowid(MrdbCursor *self)
{ {
@ -1628,3 +1654,169 @@ end:
} }
return rc; 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 *comment_end= "*/";
const char literals[3]= {'\'', '\"', '`'}; 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 static uint8_t
check_keyword(char* ofs, char* end, char* keyword, size_t keylen) 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); memcpy(p->statement.str, statement, length);
p->statement.length= length; p->statement.length= length;
p->mysql= mysql; p->mysql= mysql;
p->param_list= PyList_New(0);
p->param_count= 0;
} }
return p; return p;
} }
@ -242,6 +258,7 @@ cont:
/* parmastyle = qmark */ /* parmastyle = qmark */
if (*a == '?') if (*a == '?')
{ {
PyObject *tmp;
if (p->paramstyle && p->paramstyle != QMARK) if (p->paramstyle && p->paramstyle != QMARK)
{ {
parser_error(errmsg, errmsg_len, parser_error(errmsg, errmsg_len,
@ -250,6 +267,9 @@ cont:
} }
p->paramstyle= QMARK; p->paramstyle= QMARK;
p->param_count++; p->param_count++;
tmp= PyLong_FromLong((long)(a - p->statement.str));
PyList_Append(p->param_list, tmp);
Py_DECREF(tmp);
a++; a++;
continue; continue;
} }
@ -259,6 +279,7 @@ cont:
/* paramstyle format */ /* paramstyle format */
if (*(a+1) == 's' || *(a+1) == 'd') if (*(a+1) == 's' || *(a+1) == 'd')
{ {
PyObject *tmp;
if (p->paramstyle && p->paramstyle != FORMAT) if (p->paramstyle && p->paramstyle != FORMAT)
{ {
parser_error(errmsg, errmsg_len, parser_error(errmsg, errmsg_len,
@ -269,6 +290,10 @@ cont:
*a= '?'; *a= '?';
memmove(a+1, a+2, end - a); memmove(a+1, a+2, end - a);
end--; end--;
tmp= PyLong_FromLong((long)(a - p->statement.str));
PyList_Append(p->param_list, tmp);
Py_DECREF(tmp);
a++; a++;
p->param_count++; p->param_count++;
continue; continue;
@ -276,6 +301,9 @@ cont:
if (*(a+1) == '(') if (*(a+1) == '(')
{ {
char *val_end= strstr(a+1, ")s"); char *val_end= strstr(a+1, ")s");
PyObject *tmp;
if (val_end) if (val_end)
{ {
ssize_t keylen= val_end - a + 1; ssize_t keylen= val_end - a + 1;
@ -288,6 +316,9 @@ cont:
p->paramstyle= PYFORMAT; p->paramstyle= PYFORMAT;
*a= '?'; *a= '?';
p->param_count++; p->param_count++;
tmp= PyLong_FromLong((long)(a - p->statement.str));
PyList_Append(p->param_list, tmp);
Py_DECREF(tmp);
if (p->keys) if (p->keys)
{ {
MrdbString *m; MrdbString *m;
@ -317,7 +348,7 @@ cont:
memcpy(p->keys[p->param_count - 1].str, a + 2, keylen - 3); memcpy(p->keys[p->param_count - 1].str, a + 2, keylen - 3);
p->keys[p->param_count - 1].length= 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; a+= 1;
end -= keylen; end -= keylen;
continue; continue;
@ -345,8 +376,25 @@ cont:
a += 7; a += 7;
continue; 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; lastchar= *a;
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): def get_config(options):
required_version = "3.1.5" required_version = "3.2.0"
no_env = 0 no_env = 0
static = options["link_static"] static = options["link_static"]

View File

@ -98,6 +98,6 @@ setup(name='mariadb',
py_modules=['mariadb.__init__', 'mariadb.constants.CLIENT', 'mariadb.constants.CURSOR', py_modules=['mariadb.__init__', 'mariadb.constants.CLIENT', 'mariadb.constants.CURSOR',
'mariadb.constants.STATUS', 'mariadb.constants.STATUS',
'mariadb.field', 'mariadb.dbapi20', 'mariadb.connections', 'mariadb.connectionpool', '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'], 'mariadb.constants.FIELD_TYPE', 'mariadb.constants.FIELD_FLAG', 'mariadb.constants.INDICATOR'],
) )