diff --git a/include/mariadb_python.h b/include/mariadb_python.h index fae1906..b9f3149 100644 --- a/include/mariadb_python.h +++ b/include/mariadb_python.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #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)) diff --git a/setup.py b/setup.py index a337061..1f0dae1 100644 --- a/setup.py +++ b/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, diff --git a/src/mariadb.c b/src/mariadb.c index afa6fc4..64bf3bf 100644 --- a/src/mariadb.c +++ b/src/mariadb.c @@ -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)); diff --git a/src/mariadb_codecs.c b/src/mariadb_codecs.c index ad87e57..e7311a5 100644 --- a/src/mariadb_codecs.c +++ b/src/mariadb_codecs.c @@ -19,6 +19,327 @@ #include "mariadb_python.h" #include + +/* {{{ 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) - self->values[column]= PyBytes_FromStringAndSize((const char *)*row, (Py_ssize_t)length); + { + 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= STMT_INDICATOR_NULL; + param->value= NULL; } - param->indicator= PyLong_AsLong(indicator); - param->value= value; - return 0; - } - else + } else + { param->value= column; - if (param->value == Py_None) - param->indicator= STMT_INDICATOR_NULL; - else 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: - bind->buffer_length= (unsigned long)PyBytes_GET_SIZE(value->value); - bind->buffer= (void *) PyBytes_AS_STRING(value->value); + 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])) diff --git a/test/cursor.py b/test/cursor.py index 674067b..9dc3c10 100644 --- a/test/cursor.py +++ b/test/cursor.py @@ -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 diff --git a/test/cursor_mariadb.py b/test/cursor_mariadb.py index 80fdd2c..eedc108 100644 --- a/test/cursor_mariadb.py +++ b/test/cursor_mariadb.py @@ -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) diff --git a/test/cursor_mysql.py b/test/cursor_mysql.py index d9b39ca..d1c1c4c 100644 --- a/test/cursor_mysql.py +++ b/test/cursor_mysql.py @@ -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)