mirror of
https://github.com/mariadb-corporation/mariadb-connector-python.git
synced 2025-07-27 13:01:19 +00:00
Added support for indicators (executemany):
mariadb.indicator_null, mariadb.indicator_default, mariadb.indictor.ignore Added support for Tuple and list objects: List and Tuple will be stored as blob in a dynamic column
This commit is contained in:
@ -24,6 +24,7 @@
|
||||
#include <mysql.h>
|
||||
#include <errmsg.h>
|
||||
#include <mysqld_error.h>
|
||||
#include <mariadb_dyncol.h>
|
||||
#include <time.h>
|
||||
|
||||
#if defined(_WIN32) && defined(_MSVC)
|
||||
@ -38,6 +39,9 @@
|
||||
|
||||
#define MAX_TPC_XID_SIZE 65
|
||||
|
||||
/* Magic constant for checking dynamic columns */
|
||||
#define PYTHON_DYNCOL_VALUE 0xA378BD8E
|
||||
|
||||
enum enum_dataapi_groups
|
||||
{
|
||||
DBAPI_NUMBER= 1,
|
||||
@ -47,6 +51,12 @@ enum enum_dataapi_groups
|
||||
DBAPI_ROWID
|
||||
};
|
||||
|
||||
enum enum_dyncol_type
|
||||
{
|
||||
DYNCOL_LIST= 1,
|
||||
DYNCOL_TUPLE
|
||||
};
|
||||
|
||||
enum enum_tpc_state
|
||||
{
|
||||
TPC_STATE_NONE= 0,
|
||||
@ -75,6 +85,7 @@ typedef struct {
|
||||
typedef struct {
|
||||
enum enum_field_types type;
|
||||
size_t bits; /* for PyLong Object */
|
||||
PyTypeObject *ob_type;
|
||||
uint8_t is_negative;
|
||||
uint8_t has_indicator;
|
||||
} MrdbParamInfo;
|
||||
@ -87,9 +98,15 @@ typedef struct {
|
||||
uint8_t free_me;
|
||||
void *buffer;
|
||||
unsigned char num[8];
|
||||
DYNAMIC_COLUMN dyncol;
|
||||
MYSQL_TIME tm;
|
||||
} MrdbParamValue;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
enum enum_indicator_type indicator;
|
||||
} MrdbIndicator;
|
||||
|
||||
/* PEP-249: Cursor object */
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
@ -152,6 +169,7 @@ PyObject *Mariadb_Warning;
|
||||
|
||||
/* Object types */
|
||||
PyTypeObject Mariadb_Fieldinfo_Type;
|
||||
PyTypeObject MrdbIndicator_Type;
|
||||
PyTypeObject MrdbConnection_Type;
|
||||
PyTypeObject MrdbCursor_Type;
|
||||
PyTypeObject Mariadb_DBAPIType_Type;
|
||||
@ -167,6 +185,8 @@ void mariadb_throw_exception(void *handle,
|
||||
const char *message,
|
||||
...);
|
||||
|
||||
PyObject *MrdbIndicator_Object(uint32_t type);
|
||||
long MrdbIndicator_AsLong(PyObject *v);
|
||||
PyObject *Mariadb_DBAPIType_Object(uint32_t type);
|
||||
PyObject *MrdbConnection_affected_rows(MrdbConnection *self);
|
||||
PyObject *MrdbConnection_autocommit(MrdbConnection *self,
|
||||
@ -199,6 +219,10 @@ uint8_t mariadb_param_update(void *data, MYSQL_BIND *bind, uint32_t row_nr);
|
||||
|
||||
|
||||
/* Helper macros */
|
||||
|
||||
#define MrdbIndicator_Check(a)\
|
||||
(Py_TYPE((a)) == &MrdbIndicator_Type)
|
||||
|
||||
#define MARIADB_FEATURE_SUPPORTED(mysql,version)\
|
||||
(mysql_get_server_version((mysql)) >= (version))
|
||||
|
||||
|
2
setup.py
2
setup.py
@ -60,7 +60,7 @@ setup(name='mariadb',
|
||||
version='0.1',
|
||||
description='Python MariaDB extension',
|
||||
author='Georg Richter',
|
||||
ext_modules=[Extension('mariadb', ['src/mariadb.c', 'src/mariadb_connection.c', 'src/mariadb_exception.c', 'src/mariadb_cursor.c', 'src/mariadb_codecs.c', 'src/mariadb_field.c', 'src/mariadb_dbapitype.c'],
|
||||
ext_modules=[Extension('mariadb', ['src/mariadb.c', 'src/mariadb_connection.c', 'src/mariadb_exception.c', 'src/mariadb_cursor.c', 'src/mariadb_codecs.c', 'src/mariadb_field.c', 'src/mariadb_dbapitype.c', 'src/mariadb_indicator.c'],
|
||||
include_dirs=mariadb_includes,
|
||||
library_dirs= mariadb_lib_dirs,
|
||||
libraries= mariadb_libraries,
|
||||
|
@ -93,13 +93,8 @@ struct st_constants {
|
||||
const char *strvalue;
|
||||
} u;
|
||||
};
|
||||
|
||||
struct st_constants int_constants[]= {
|
||||
{"INDICATOR_NTS", {STMT_INDICATOR_NTS}},
|
||||
{"INDICATOR_NONE", {STMT_INDICATOR_NONE}},
|
||||
{"INDICATOR_NULL", {STMT_INDICATOR_NULL}},
|
||||
{"INDICATOR_DEFAULT", {STMT_INDICATOR_DEFAULT}},
|
||||
{"INDICATOR_IGNORE", {STMT_INDICATOR_IGNORE}},
|
||||
{"INDICATOR_IGNORE_ROW", {STMT_INDICATOR_IGNORE_ROW}},
|
||||
{"CURSOR_TYPE_READ_ONLY", {CURSOR_TYPE_READ_ONLY}},
|
||||
{"CURSOR_TYPE_NONE", {CURSOR_TYPE_NO_CURSOR}},
|
||||
{NULL, {0}} /* Always last */
|
||||
@ -132,6 +127,10 @@ PyMODINIT_FUNC PyInit_mariadb(void)
|
||||
if (PyType_Ready(&MrdbCursor_Type) == -1)
|
||||
goto error;
|
||||
|
||||
Py_TYPE(&MrdbIndicator_Type) = &PyType_Type;
|
||||
if (PyType_Ready(&MrdbIndicator_Type) == -1)
|
||||
goto error;
|
||||
|
||||
Py_TYPE(&Mariadb_Fieldinfo_Type) = &PyType_Type;
|
||||
if (PyType_Ready(&Mariadb_Fieldinfo_Type) == -1)
|
||||
goto error;
|
||||
@ -187,6 +186,12 @@ PyMODINIT_FUNC PyInit_mariadb(void)
|
||||
|
||||
Py_INCREF(&MrdbConnection_Type);
|
||||
PyModule_AddObject(module, "connection", (PyObject *)&MrdbConnection_Type);
|
||||
|
||||
PyModule_AddObject(module, "indicator_null", MrdbIndicator_Object(STMT_INDICATOR_NULL));
|
||||
PyModule_AddObject(module, "indicator_default", MrdbIndicator_Object(STMT_INDICATOR_DEFAULT));
|
||||
PyModule_AddObject(module, "indicator_ignore", MrdbIndicator_Object(STMT_INDICATOR_IGNORE));
|
||||
PyModule_AddObject(module, "indicator_row", MrdbIndicator_Object(STMT_INDICATOR_IGNORE_ROW));
|
||||
|
||||
PyModule_AddObject(module, "NUMBER", Mariadb_DBAPIType_Object(DBAPI_NUMBER));
|
||||
PyModule_AddObject(module, "BINARY", Mariadb_DBAPIType_Object(DBAPI_BINARY));
|
||||
PyModule_AddObject(module, "STRING", Mariadb_DBAPIType_Object(DBAPI_STRING));
|
||||
|
@ -19,6 +19,327 @@
|
||||
#include "mariadb_python.h"
|
||||
#include <datetime.h>
|
||||
|
||||
|
||||
/* {{{ mariadb_pydate_to_tm
|
||||
converts a Python date/time/datetime object to MYSQL_TIME
|
||||
*/
|
||||
static void mariadb_pydate_to_tm(enum enum_field_types type,
|
||||
PyObject *obj,
|
||||
MYSQL_TIME *tm)
|
||||
{
|
||||
if (!PyDateTimeAPI)
|
||||
PyDateTime_IMPORT;
|
||||
memset(tm, 0, sizeof(MYSQL_TIME));
|
||||
if (type == MYSQL_TYPE_TIME ||
|
||||
type == MYSQL_TYPE_DATETIME)
|
||||
{
|
||||
uint8_t is_time= PyTime_CheckExact(obj);
|
||||
tm->hour= is_time ? PyDateTime_TIME_GET_HOUR(obj) :
|
||||
PyDateTime_DATE_GET_HOUR(obj);
|
||||
tm->minute= is_time ? PyDateTime_TIME_GET_MINUTE(obj) :
|
||||
PyDateTime_DATE_GET_MINUTE(obj);
|
||||
tm->second= is_time ? PyDateTime_TIME_GET_SECOND(obj) :
|
||||
PyDateTime_DATE_GET_SECOND(obj);
|
||||
tm->second_part= is_time ? PyDateTime_TIME_GET_MICROSECOND(obj) :
|
||||
PyDateTime_DATE_GET_MICROSECOND(obj);
|
||||
if (type == MYSQL_TYPE_TIME)
|
||||
{
|
||||
tm->time_type= MYSQL_TIMESTAMP_TIME;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (type == MYSQL_TYPE_DATE ||
|
||||
type == MYSQL_TYPE_DATETIME)
|
||||
{
|
||||
tm->year= PyDateTime_GET_YEAR(obj);
|
||||
tm->month= PyDateTime_GET_MONTH(obj);
|
||||
tm->day= PyDateTime_GET_DAY(obj);
|
||||
if (type == MYSQL_TYPE_DATE)
|
||||
tm->time_type= MYSQL_TIMESTAMP_DATE;
|
||||
else
|
||||
tm->time_type= MYSQL_TIMESTAMP_DATETIME;
|
||||
}
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ check_is_dyncol
|
||||
|
||||
Checks if the binary object is a dynamic column with the following
|
||||
conditions:
|
||||
- dyncol has names and is not stored in old format
|
||||
- number of elements (columns) can't be zero
|
||||
- first value is an integer
|
||||
- 4 high bytes of integer = PYTHON_DYNCOL_VALUE
|
||||
*/
|
||||
static uint8_t check_is_dyncol(DYNAMIC_COLUMN *col)
|
||||
{
|
||||
uint32_t count= 0;
|
||||
DYNAMIC_COLUMN_VALUE val;
|
||||
if (mariadb_dyncol_check(col) != ER_DYNCOL_OK ||
|
||||
!mariadb_dyncol_has_names(col))
|
||||
return 0;
|
||||
|
||||
if (mariadb_dyncol_column_count(col, &count) != ER_DYNCOL_OK ||
|
||||
!count)
|
||||
return 0;
|
||||
|
||||
if (mariadb_dyncol_get_num(col, 0, &val) != ER_DYNCOL_OK)
|
||||
return 0;
|
||||
|
||||
if (val.type != DYN_COL_UINT ||
|
||||
val.x.ulong_value >> 32 != PYTHON_DYNCOL_VALUE)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ mariadb_dyncol_to_pyobj
|
||||
converts a dynamic column to python object (list or tuple)
|
||||
|
||||
check if the dyncol is correct should be made before
|
||||
calling this function
|
||||
*/
|
||||
static PyObject *mariadb_dyncol_to_pyobj(DYNAMIC_COLUMN *col)
|
||||
{
|
||||
PyObject *pycol= NULL;
|
||||
enum enum_dyncol_type type= 0;
|
||||
uint32_t i, column_count;
|
||||
DYNAMIC_COLUMN_VALUE dyncol_val;
|
||||
|
||||
/* First element contains the type */
|
||||
if (mariadb_dyncol_get_num(col, 0, &dyncol_val) != ER_DYNCOL_OK)
|
||||
return NULL;
|
||||
|
||||
type= dyncol_val.x.ulong_value - (dyncol_val.x.ulong_value << 32);
|
||||
if (!type || type > DYNCOL_TUPLE)
|
||||
return NULL;
|
||||
|
||||
if (mariadb_dyncol_column_count(col, &column_count) != ER_DYNCOL_OK)
|
||||
return NULL;
|
||||
|
||||
column_count--;
|
||||
|
||||
if (type == DYNCOL_LIST)
|
||||
pycol= PyList_New(column_count);
|
||||
else if (type == DYNCOL_TUPLE)
|
||||
pycol= PyTuple_New(column_count);
|
||||
|
||||
for (i=1; i <= column_count; i++)
|
||||
{
|
||||
PyObject *newval;
|
||||
DYNAMIC_COLUMN_VALUE val;
|
||||
|
||||
if (mariadb_dyncol_get_num(col, i, &val) != ER_DYNCOL_OK)
|
||||
goto error;
|
||||
|
||||
switch(val.type) {
|
||||
case DYN_COL_NULL:
|
||||
Py_INCREF(Py_None);
|
||||
newval= Py_None;
|
||||
break;
|
||||
case DYN_COL_STRING:
|
||||
newval= PyUnicode_FromStringAndSize(val.x.string.value.str,
|
||||
val.x.string.value.length);
|
||||
break;
|
||||
case DYN_COL_INT:
|
||||
newval= PyLong_FromLongLong(val.x.long_value);
|
||||
break;
|
||||
case DYN_COL_UINT:
|
||||
newval= PyLong_FromUnsignedLongLong(val.x.ulong_value);
|
||||
break;
|
||||
case DYN_COL_DOUBLE:
|
||||
newval= PyFloat_FromDouble(val.x.double_value);
|
||||
break;
|
||||
case DYN_COL_DYNCOL:
|
||||
newval= mariadb_dyncol_to_pyobj((DYNAMIC_COLUMN *)&val.x.string.value);
|
||||
break;
|
||||
case DYN_COL_DATE:
|
||||
case DYN_COL_TIME:
|
||||
case DYN_COL_DATETIME:
|
||||
{
|
||||
MYSQL_TIME tm= val.x.time_value;
|
||||
switch (tm.time_type) {
|
||||
case MYSQL_TIMESTAMP_TIME:
|
||||
newval= PyTime_FromTime(tm.hour, tm.minute, tm.second, tm.second_part);
|
||||
break;
|
||||
case MYSQL_TIMESTAMP_DATE:
|
||||
newval= PyDate_FromDate(tm.year, tm.month, tm.day);
|
||||
break;
|
||||
case MYSQL_TIMESTAMP_DATETIME:
|
||||
newval= PyDateTime_FromDateAndTime(tm.year, tm.month, tm.day, tm.hour, tm.minute,
|
||||
tm.second, tm.second_part);
|
||||
break;
|
||||
default:
|
||||
Py_INCREF(Py_None);
|
||||
newval= Py_None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
if (newval)
|
||||
{
|
||||
if (type == DYNCOL_LIST)
|
||||
PyList_SetItem(pycol, i - 1, newval);
|
||||
else if (type == DYNCOL_TUPLE)
|
||||
PyTuple_SetItem(pycol, i - 1, newval);
|
||||
}
|
||||
}
|
||||
return pycol;
|
||||
|
||||
error:
|
||||
return NULL;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* add_dynamic_key */
|
||||
static char *add_dynamic_key(char *keys, uint8_t size, uint32_t column)
|
||||
{
|
||||
snprintf(keys + (column * size), size + 1, "%*d", size, column);
|
||||
return keys + (column * size);
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ mariadb_pyobj_to_dyncol */
|
||||
static uint8_t mariadb_pyobj_to_dyncol(DYNAMIC_COLUMN *col,
|
||||
PyObject *obj,
|
||||
uint32_t level)
|
||||
{
|
||||
enum enum_dyncol_type type= 0;
|
||||
size_t size= 0;
|
||||
char *keys= 0;
|
||||
uint8_t rc= 1;
|
||||
uint32_t i, keys_size, *column_vals= NULL;
|
||||
MYSQL_LEX_STRING *column_keys= NULL;
|
||||
DYNAMIC_COLUMN_VALUE *values= NULL;
|
||||
DYNAMIC_COLUMN *new_column;
|
||||
|
||||
if (Py_TYPE(obj) == &PyList_Type)
|
||||
{
|
||||
type= DYNCOL_LIST;
|
||||
size= PyList_Size(obj);
|
||||
}
|
||||
else if (Py_TYPE(obj) == &PyTuple_Type)
|
||||
{
|
||||
type= DYNCOL_TUPLE;
|
||||
size= PyTuple_Size(obj);
|
||||
}
|
||||
else
|
||||
return 1; /* other types not supported yet */
|
||||
|
||||
keys_size= (uint8_t)log10(size) + 1;
|
||||
|
||||
if (!(column_keys= (MYSQL_LEX_STRING *)PyMem_RawCalloc(size + 1, sizeof(MYSQL_LEX_STRING))))
|
||||
goto end;
|
||||
|
||||
if (!(keys= (char *)PyMem_RawCalloc(1, (size + 1) * (size_t)keys_size + 1)))
|
||||
goto end;
|
||||
|
||||
if (!(column_vals= (uint32_t *)PyMem_RawCalloc((size + 1), sizeof(uint32_t))))
|
||||
goto end;
|
||||
|
||||
if (!(values = (DYNAMIC_COLUMN_VALUE *)PyMem_RawCalloc((size + 1), sizeof(DYNAMIC_COLUMN_VALUE))))
|
||||
goto end;
|
||||
|
||||
/* First element always specifes the type (tuple or list) */
|
||||
values[0].type= DYN_COL_UINT;
|
||||
values[0].x.ulong_value= ((long long)PYTHON_DYNCOL_VALUE << 32) + type;
|
||||
column_keys[0].str= add_dynamic_key(keys, keys_size, 0) ;
|
||||
column_keys[0].length= keys_size;
|
||||
|
||||
for (i=0; i < size; i++)
|
||||
{
|
||||
PyObject *item;
|
||||
|
||||
if (type == DYNCOL_LIST)
|
||||
item= PyList_GetItem(obj, i);
|
||||
else
|
||||
item= PyTuple_GetItem(obj, i);
|
||||
|
||||
if (Py_TYPE(item) == &PyLong_Type)
|
||||
{
|
||||
if (_PyLong_Sign(item) < 0)
|
||||
{
|
||||
values[i+1].type= DYN_COL_INT;
|
||||
values[i+1].x.long_value= PyLong_AsLongLong(item);
|
||||
} else {
|
||||
values[i+1].type= DYN_COL_UINT;
|
||||
values[i+1].x.ulong_value= PyLong_AsUnsignedLongLong(item);
|
||||
}
|
||||
} else if (Py_TYPE(item) == &PyFloat_Type)
|
||||
{
|
||||
values[i+1].type= DYN_COL_DOUBLE;
|
||||
values[i+1].x.double_value= PyFloat_AsDouble(item);
|
||||
} else if (Py_TYPE(item) == &PyUnicode_Type)
|
||||
{
|
||||
values[i+1].type= DYN_COL_STRING;
|
||||
values[i+1].x.string.value.str= PyUnicode_AsUTF8AndSize(item,
|
||||
(Py_ssize_t *)&values[i].x.string.value.length);
|
||||
values[i+1].x.string.charset= ma_charset_utf8_general_ci;
|
||||
} else if (Py_TYPE(item) == &PyBytes_Type)
|
||||
{
|
||||
values[i+1].type= DYN_COL_STRING;
|
||||
values[i+1].x.string.value.length= (size_t)PyBytes_GET_SIZE(item);
|
||||
values[i+1].x.string.value.str= PyBytes_AS_STRING(item);
|
||||
values[i+1].x.string.charset= ma_charset_bin;
|
||||
} else if (PyDate_CheckExact(item))
|
||||
{
|
||||
values[i+1].type= DYN_COL_DATE;
|
||||
mariadb_pydate_to_tm(MYSQL_TYPE_DATE, item, &values[i+1].x.time_value);
|
||||
} else if (PyTime_CheckExact(item))
|
||||
{
|
||||
values[i+1].type= DYN_COL_TIME;
|
||||
mariadb_pydate_to_tm(MYSQL_TYPE_TIME, item, &values[i+1].x.time_value);
|
||||
} else if (PyDateTime_CheckExact(item))
|
||||
{
|
||||
values[i+1].type= DYN_COL_DATETIME;
|
||||
mariadb_pydate_to_tm(MYSQL_TYPE_DATETIME, item, &values[i+1].x.time_value);
|
||||
} else if (Py_TYPE(item) == &PyList_Type ||
|
||||
Py_TYPE(item) == &PyTuple_Type)
|
||||
{
|
||||
new_column= (DYNAMIC_COLUMN *)alloca(sizeof(DYNAMIC_COLUMN));
|
||||
if (mariadb_pyobj_to_dyncol(new_column, item, level + 1))
|
||||
{
|
||||
goto end;
|
||||
}
|
||||
if (mariadb_dyncol_check(new_column) != ER_DYNCOL_OK)
|
||||
goto end;
|
||||
values[i+1].type= DYN_COL_DYNCOL;
|
||||
values[i+1].x.string.value.str= new_column->str;
|
||||
values[i+1].x.string.value.length= new_column->length;
|
||||
} else
|
||||
goto end;
|
||||
column_keys[i+1].str= add_dynamic_key(keys, keys_size, i+1);
|
||||
column_keys[i+1].length= keys_size;
|
||||
}
|
||||
/* now we can build our dynamic column */
|
||||
mariadb_dyncol_init(col);
|
||||
if (mariadb_dyncol_create_many_named(col, size + 1 , column_keys, values, 0) != ER_DYNCOL_OK)
|
||||
goto end;
|
||||
rc= 0;
|
||||
end:
|
||||
if (rc)
|
||||
{
|
||||
/* in case of error make sure that we free dyncols */
|
||||
for (i=0; i < size; i++)
|
||||
{
|
||||
if (values[i].type== DYN_COL_DYNCOL)
|
||||
mariadb_dyncol_free((DYNAMIC_COLUMN *)values[i].x.string.value.str);
|
||||
}
|
||||
}
|
||||
if (keys)
|
||||
PyMem_RawFree(keys);
|
||||
if (column_keys)
|
||||
PyMem_RawFree(column_keys);
|
||||
if (values)
|
||||
PyMem_RawFree(values);
|
||||
return rc;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ field_fetch_callback
|
||||
This function was previously registered with mysql_stmt_attr_set and
|
||||
STMT_ATTR_FIELD_FETCH_CALLBACK parameter. Instead of filling a bind buffer
|
||||
@ -181,7 +502,22 @@ void field_fetch_callback(void *data, unsigned int column, unsigned char **row)
|
||||
{
|
||||
unsigned long length= mysql_net_field_length(row);
|
||||
if (self->fields[column].flags & BINARY_FLAG)
|
||||
{
|
||||
DYNAMIC_COLUMN dc;
|
||||
uint8_t rc;
|
||||
|
||||
/* check if we have a DYNAMIC_COLUMN */
|
||||
mariadb_dyncol_init(&dc);
|
||||
dc.str= (char *)*row;
|
||||
dc.length= length;
|
||||
|
||||
if ((rc= check_is_dyncol(&dc)))
|
||||
{
|
||||
self->values[column]= mariadb_dyncol_to_pyobj(&dc);
|
||||
}
|
||||
else
|
||||
self->values[column]= PyBytes_FromStringAndSize((const char *)*row, (Py_ssize_t)length);
|
||||
}
|
||||
else
|
||||
self->values[column]= PyUnicode_FromStringAndSize((const char *)*row, (Py_ssize_t)length);
|
||||
|
||||
@ -209,6 +545,7 @@ void field_fetch_callback(void *data, unsigned int column, unsigned char **row)
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
|
||||
/* {{{ mariadb_get_column_info
|
||||
This function analyzes the Python object and calculates the corresponding
|
||||
MYSQL_TYPE, unsigned flag or NULL values and stores the information in
|
||||
@ -219,6 +556,13 @@ static uint8_t mariadb_get_column_info(PyObject *obj,
|
||||
{
|
||||
if (!PyDateTimeAPI)
|
||||
PyDateTime_IMPORT;
|
||||
|
||||
if (obj == NULL)
|
||||
{
|
||||
paraminfo->type= MYSQL_TYPE_BLOB;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (Py_TYPE(obj) == &PyLong_Type)
|
||||
{
|
||||
size_t b= _PyLong_NumBits(obj);
|
||||
@ -232,7 +576,9 @@ static uint8_t mariadb_get_column_info(PyObject *obj,
|
||||
{
|
||||
paraminfo->type= MYSQL_TYPE_DOUBLE;
|
||||
return 0;
|
||||
} else if (Py_TYPE(obj) == &PyBytes_Type)
|
||||
} else if (Py_TYPE(obj) == &PyBytes_Type ||
|
||||
Py_TYPE(obj) == &PyList_Type ||
|
||||
Py_TYPE(obj) == &PyTuple_Type)
|
||||
{
|
||||
paraminfo->type= MYSQL_TYPE_LONG_BLOB;
|
||||
return 0;
|
||||
@ -252,6 +598,11 @@ static uint8_t mariadb_get_column_info(PyObject *obj,
|
||||
{
|
||||
paraminfo->type= MYSQL_TYPE_VAR_STRING;
|
||||
return 0;
|
||||
} else if (Py_TYPE(obj) == &PyTuple_Type ||
|
||||
Py_TYPE(obj) == &PyList_Type)
|
||||
{
|
||||
paraminfo->type= MYSQL_TYPE_LONG_BLOB;
|
||||
paraminfo->ob_type= Py_TYPE(obj);
|
||||
} else if (obj == Py_None)
|
||||
{
|
||||
paraminfo->type= MYSQL_TYPE_NULL;
|
||||
@ -317,42 +668,29 @@ static uint8_t mariadb_get_parameter(MrdbCursor *self,
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (Py_TYPE(column) == &PyTuple_Type) /* (value, indicator) */
|
||||
/* check if an indicator was passed */
|
||||
if (MrdbIndicator_Check(column))
|
||||
{
|
||||
PyObject *indicator;
|
||||
PyObject *value;
|
||||
if (PyTuple_Size(column) != 2)
|
||||
{
|
||||
mariadb_throw_exception(self->stmt, Mariadb_DataError, 0,
|
||||
"Invalid tuple at row %d, column %d", row_nr, column_nr);
|
||||
return 1;
|
||||
}
|
||||
if (!MARIADB_FEATURE_SUPPORTED(self->stmt->mysql, 100206))
|
||||
{
|
||||
mariadb_throw_exception(NULL, Mariadb_DataError, 0,
|
||||
"MariaDB %s doesn't support indicator variables. Required version is 10.2.6 or newer", mysql_get_server_info(self->stmt->mysql));
|
||||
return 1;
|
||||
}
|
||||
value= PyTuple_GetItem(column, 0);
|
||||
indicator= PyTuple_GetItem(column, 1);
|
||||
if (Py_TYPE(indicator) != &PyLong_Type ||
|
||||
(PyLong_AsLong(indicator) < STMT_INDICATOR_NTS ||
|
||||
PyLong_AsLong(indicator) > STMT_INDICATOR_IGNORE_ROW))
|
||||
param->indicator= MrdbIndicator_AsLong(column);
|
||||
param->value= NULL; /* you can't have both indicator and value */
|
||||
} else if (column == Py_None)
|
||||
{
|
||||
if (MARIADB_FEATURE_SUPPORTED(self->stmt->mysql, 100206))
|
||||
{
|
||||
mariadb_throw_exception(self->stmt, Mariadb_DataError, 0,
|
||||
"Invalid value for indicator at row %d, column %d", row_nr, column_nr);
|
||||
return 1;
|
||||
}
|
||||
param->indicator= PyLong_AsLong(indicator);
|
||||
param->value= value;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
param->value= column;
|
||||
if (param->value == Py_None)
|
||||
param->indicator= STMT_INDICATOR_NULL;
|
||||
else
|
||||
param->value= NULL;
|
||||
}
|
||||
} else
|
||||
{
|
||||
param->value= column;
|
||||
param->indicator= STMT_INDICATOR_NONE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
/* }}} */
|
||||
@ -618,43 +956,26 @@ static uint8_t mariadb_param_to_bind(MYSQL_BIND *bind,
|
||||
*(double *)value->num= (double)PyFloat_AsDouble(value->value);
|
||||
break;
|
||||
case MYSQL_TYPE_LONG_BLOB:
|
||||
if (Py_TYPE(value->value) == &PyTuple_Type ||
|
||||
Py_TYPE(value->value) == &PyList_Type)
|
||||
{
|
||||
if (value->dyncol.length)
|
||||
mariadb_dyncol_free(&value->dyncol);
|
||||
if (mariadb_pyobj_to_dyncol(&value->dyncol, value->value, 0))
|
||||
return 1;
|
||||
bind->buffer_length= (unsigned long)value->dyncol.length;
|
||||
bind->buffer= (void *)value->dyncol.str;
|
||||
} else
|
||||
{
|
||||
bind->buffer_length= (unsigned long)PyBytes_GET_SIZE(value->value);
|
||||
bind->buffer= (void *) PyBytes_AS_STRING(value->value);
|
||||
}
|
||||
break;
|
||||
case MYSQL_TYPE_DATE:
|
||||
case MYSQL_TYPE_TIME:
|
||||
case MYSQL_TYPE_DATETIME:
|
||||
bind->buffer= &value->tm;
|
||||
memset(&value->tm, 0, sizeof(MYSQL_TIME));
|
||||
if (bind->buffer_type == MYSQL_TYPE_TIME ||
|
||||
bind->buffer_type == MYSQL_TYPE_DATETIME)
|
||||
{
|
||||
uint8_t is_time= PyTime_CheckExact(value->value);
|
||||
value->tm.hour= is_time ? PyDateTime_TIME_GET_HOUR(value->value) :
|
||||
PyDateTime_DATE_GET_HOUR(value->value);
|
||||
value->tm.minute= is_time ? PyDateTime_TIME_GET_MINUTE(value->value) :
|
||||
PyDateTime_DATE_GET_MINUTE(value->value);
|
||||
value->tm.second= is_time ? PyDateTime_TIME_GET_SECOND(value->value) :
|
||||
PyDateTime_DATE_GET_SECOND(value->value);
|
||||
value->tm.second_part= is_time ? PyDateTime_TIME_GET_MICROSECOND(value->value) :
|
||||
PyDateTime_DATE_GET_MICROSECOND(value->value);
|
||||
if (bind->buffer_type == MYSQL_TYPE_TIME)
|
||||
{
|
||||
value->tm.time_type= MYSQL_TIMESTAMP_TIME;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bind->buffer_type == MYSQL_TYPE_DATE ||
|
||||
bind->buffer_type == MYSQL_TYPE_DATETIME)
|
||||
{
|
||||
value->tm.year= PyDateTime_GET_YEAR(value->value);
|
||||
value->tm.month= PyDateTime_GET_MONTH(value->value);
|
||||
value->tm.day= PyDateTime_GET_DAY(value->value);
|
||||
if (bind->buffer_type == MYSQL_TYPE_DATE)
|
||||
value->tm.time_type= MYSQL_TIMESTAMP_DATE;
|
||||
else
|
||||
value->tm.time_type= MYSQL_TIMESTAMP_DATETIME;
|
||||
}
|
||||
mariadb_pydate_to_tm(bind->buffer_type, value->value, &value->tm);
|
||||
break;
|
||||
case MYSQL_TYPE_VAR_STRING:
|
||||
{
|
||||
@ -700,7 +1021,9 @@ uint8_t mariadb_param_update(void *data, MYSQL_BIND *bind, uint32_t row_nr)
|
||||
if (mariadb_get_parameter(self, (self->array_size > 0), row_nr, i, &self->value[i]))
|
||||
return 1;
|
||||
if (self->value[i].indicator)
|
||||
bind->u.indicator= &self->value[i].indicator;
|
||||
{
|
||||
bind[i].u.indicator= &self->value[i].indicator;
|
||||
}
|
||||
if (self->value[i].indicator < 1)
|
||||
{
|
||||
if (mariadb_param_to_bind(&bind[i], &self->value[i]))
|
||||
|
@ -265,5 +265,32 @@ class CursorTest(unittest.TestCase):
|
||||
self.assertEqual(expected_typecodes, typecodes)
|
||||
del cursor
|
||||
|
||||
def test_tuple(self):
|
||||
cursor= self.connection.cursor()
|
||||
cursor.execute("CREATE OR REPLACE TABLE dyncol1 (a blob)");
|
||||
tpl=(1,2,3)
|
||||
cursor.execute("INSERT INTO dyncol1 VALUES (?)", tpl);
|
||||
del cursor
|
||||
|
||||
def test_indicator(self):
|
||||
cursor= self.connection.cursor()
|
||||
cursor.execute("CREATE OR REPLACE TABLE ind1 (a int, b int default 2,c int)");
|
||||
vals= (mariadb.indicator_null, mariadb.indicator_default, 3)
|
||||
cursor.executemany("INSERT INTO ind1 VALUES (?,?,?)", [vals])
|
||||
cursor.execute("SELECT a, b, c FROM ind1")
|
||||
row= cursor.fetchone()
|
||||
self.assertEqual(row[0], None)
|
||||
self.assertEqual(row[1], 2)
|
||||
self.assertEqual(row[2], 3)
|
||||
|
||||
|
||||
def test_tuple(self):
|
||||
cursor= self.connection.cursor()
|
||||
cursor.execute("CREATE OR REPLACE TABLE dyncol1 (a blob)");
|
||||
t= datetime.datetime(2018,6,20,12,22,31,123456)
|
||||
val=([1,t,3,(1,2,3)],)
|
||||
cursor.execute("INSERT INTO dyncol1 VALUES (?)", val);
|
||||
cursor.execute("SELECT a FROM dyncol1")
|
||||
row= cursor.fetchone()
|
||||
self.assertEqual(row,val);
|
||||
del cursor
|
||||
|
@ -18,7 +18,7 @@ class CursorTest(unittest.TestCase):
|
||||
cursor.execute("CREATE OR REPLACE TABLE t1(a int not null auto_increment primary key, b int, c int, d varchar(20),e date)")
|
||||
# cursor.execute("set @@autocommit=0");
|
||||
list_in= []
|
||||
for i in range(1, 300000):
|
||||
for i in range(1, 300001):
|
||||
row= (i,i,i,"bar", datetime.date(2019,1,1))
|
||||
list_in.append(row)
|
||||
cursor.executemany("INSERT INTO t1 VALUES (?,?,?,?,?)", list_in)
|
||||
|
@ -19,7 +19,7 @@ class CursorTest(unittest.TestCase):
|
||||
cursor.execute("SET @@autocommit=0");
|
||||
c = (1,2,3, "bar", datetime.date(2018,11,11))
|
||||
list_in= []
|
||||
for i in range(1,300000):
|
||||
for i in range(1,300001):
|
||||
row= (i,i,i,"bar", datetime.date(2019,1,1))
|
||||
list_in.append(row)
|
||||
cursor.executemany("INSERT INTO t1 VALUES (%s,%s,%s,%s,%s)", list_in)
|
||||
|
Reference in New Issue
Block a user