Follow up for CONPY-299

- Added byteswap function for vector (bigendian)
- Use buffer protocol instead of calling array's
  methods
This commit is contained in:
Georg Richter
2025-02-10 13:59:43 +01:00
parent 9508904911
commit befe7000c9
2 changed files with 183 additions and 84 deletions

View File

@ -24,6 +24,53 @@
#define IS_DECIMAL_TYPE(type) \
((type) == MYSQL_TYPE_NEWDECIMAL || (type) == MYSQL_TYPE_DOUBLE || (type) == MYSQL_TYPE_FLOAT)
static char *ma_byteswap(char *buf, size_t itemsize, size_t len)
{
char *p;
Py_ssize_t i;
switch (itemsize) {
case 1:
return buf;
case 2:
for (p = buf, i = len; --i >= 0; p += itemsize) {
char p0 = p[0];
p[0] = p[1];
p[1] = p0;
}
break;
case 4:
for (p = buf, i = len; --i >= 0; p += itemsize) {
char p0 = p[0];
char p1 = p[1];
p[0] = p[3];
p[1] = p[2];
p[2] = p1;
p[3] = p0;
}
break;
case 8:
for (p = buf, i = len; --i >= 0; p += itemsize) {
char p0 = p[0];
char p1 = p[1];
char p2 = p[2];
char p3 = p[3];
p[0] = p[7];
p[1] = p[6];
p[2] = p[5];
p[3] = p[4];
p[4] = p3;
p[5] = p2;
p[6] = p1;
p[7] = p0;
}
break;
default:
return 0;
}
return buf;
}
long MrdbIndicator_AsLong(PyObject *column)
{
PyObject *pyLong= PyObject_GetAttrString(column, "indicator");
@ -943,8 +990,11 @@ mariadb_get_column_info(PyObject *obj, MrdbParamInfo *paraminfo)
paraminfo->type= MYSQL_TYPE_LONG_BLOB;
return 0;
}
else {
else if (Py_TYPE(obj)->tp_str) {
/* If Object has string representation, we will use string representation */
paraminfo->type= MYSQL_TYPE_VAR_STRING;
return 0;
} else {
/* no corresponding object, return error */
return 2;
}
@ -1090,10 +1140,10 @@ mariadb_get_parameter_info(MrdbCursor *self,
param->is_unsigned= 0;
paramvalue.indicator= 0;
uint8_t rc;
if (!self->array_size)
{
uint8_t rc;
memset(&pinfo, 0, sizeof(MrdbParamInfo));
if (mariadb_get_parameter(self, 0, 0, column_nr, &paramvalue))
return 1;
@ -1103,15 +1153,14 @@ mariadb_get_parameter_info(MrdbCursor *self,
{
mariadb_throw_exception(NULL, Mariadb_ProgrammingError, 0,
"Can't retrieve column information for parameter %d",
column_nr);
column_nr + 1);
}
if (rc == 2)
{
mariadb_throw_exception(NULL, Mariadb_NotSupportedError, 0,
"Data type '%s' in column %d not supported in MariaDB Connector/Python",
Py_TYPE(paramvalue.value)->tp_name, column_nr);
Py_TYPE(paramvalue.value)->tp_name, column_nr + 1);
}
return 1;
}
param->buffer_type= pinfo.type;
@ -1122,11 +1171,20 @@ mariadb_get_parameter_info(MrdbCursor *self,
if (mariadb_get_parameter(self, 1, i, column_nr, &paramvalue))
return 1;
memset(&pinfo, 0, sizeof(MrdbParamInfo));
if (mariadb_get_column_info(paramvalue.value, &pinfo) && !paramvalue.indicator)
if ((rc= mariadb_get_column_info(paramvalue.value, &pinfo) && !paramvalue.indicator))
{
mariadb_throw_exception(NULL, Mariadb_ProgrammingError, 1,
"Invalid parameter type at row %d, column %d",
i+1, column_nr + 1);
if (rc == 1)
{
mariadb_throw_exception(NULL, Mariadb_ProgrammingError, 0,
"Can't retrieve column information for parameter %d at row %d.",
column_nr + 1, i + 1);
}
if (rc == 2)
{
mariadb_throw_exception(NULL, Mariadb_NotSupportedError, 0,
"Data type '%s' in column %d at row %d not supported in MariaDB Connector/Python",
Py_TYPE(paramvalue.value)->tp_name, column_nr + 1, i+1);
}
return 1;
}
@ -1428,27 +1486,41 @@ mariadb_param_to_bind(MrdbCursor *self,
*(double *)value->num= (double)PyFloat_AsDouble(value->value);
break;
case MYSQL_TYPE_LONG_BLOB:
if (value->free_me)
{
MARIADB_FREE_MEM(value->buffer);
value->free_me= 0;
}
if (!strcmp(Py_TYPE(value->value)->tp_name, "array.array") ||
!strcmp(Py_TYPE(value->value)->tp_name, "array"))
{
Py_ssize_t size= PySequence_Length(value->value);
PyObject *byte_array= NULL;
Py_buffer v;
bind->buffer= NULL;
if (!size)
if (PyObject_GetBuffer(value->value, &v, PyBUF_CONTIG_RO) < 0)
goto end;
if (!PyObject_HasAttrString(value->value, "tobytes"))
if (!v.len)
goto end;
if (!(byte_array= PyObject_CallMethod(value->value, "tobytes", NULL)))
goto end;
bind->buffer_length= (unsigned long)v.len;
#if PY_BIG_ENDIAN == 0
bind->buffer= (void *)v.buf;
#else
bind->buffer= value->buffer= PyMem_RawCalloc(1, v.len);
if (!bind->buffer)
{
mariadb_throw_exception(NULL, Mariadb_InterfaceError, 0,
"Not enough memory (tried to allocated %lld bytes)", v.len);
return 1;
}
value->free_me= 1;
memcpy(bind->buffer, v.buf, v.len);
bind->buffer= (void *)PyBytes_AS_STRING(byte_array);
bind->buffer_length= (unsigned long)PyBytes_GET_SIZE(byte_array);
Py_DECREF(byte_array);
bind->buffer= ma_byteswap((char *)bind->buffer, v.itemsize, v.len);
#endif
PyBuffer_Release(&v);
} else {
bind->buffer_length= (unsigned long)PyBytes_GET_SIZE(value->value);
bind->buffer= (void *) PyBytes_AS_STRING(value->value);
@ -1464,37 +1536,43 @@ mariadb_param_to_bind(MrdbCursor *self,
mariadb_pydate_to_tm(bind->buffer_type, value->value, &value->tm);
break;
case MYSQL_TYPE_NEWDECIMAL:
{
Py_ssize_t len;
PyObject *obj= NULL;
char *p;
if (value->free_me)
MARIADB_FREE_MEM(value->buffer);
if (!strcmp(Py_TYPE(value->value)->tp_name, "decimal.Decimal") ||
!strcmp(Py_TYPE(value->value)->tp_name, "Decimal"))
{
obj= PyObject_Str(value->value);
p= (void *)PyUnicode_AsUTF8AndSize(obj, &len);
}
else
{
obj= PyObject_Str(value->value);
p= (void *)PyUnicode_AsUTF8AndSize(obj, &len);
}
bind->buffer= value->buffer= PyMem_RawCalloc(1, len);
memcpy(value->buffer, p, len);
value->free_me= 1;
bind->buffer_length= (unsigned long)len;
Py_DECREF(obj);
}
break;
case MYSQL_TYPE_VAR_STRING:
{
Py_ssize_t len;
bind->buffer= (void *)PyUnicode_AsUTF8AndSize(value->value, &len);
bind->buffer_length= (unsigned long)len;
if (value->free_me)
{
MARIADB_FREE_MEM(value->buffer);
value->free_me= 0;
}
if (CHECK_TYPE(value->value, &PyUnicode_Type)) {
bind->buffer= (void *)PyUnicode_AsUTF8AndSize(value->value, &len);
bind->buffer_length= (unsigned long)len;
} else {
PyObject *obj= PyObject_Str(value->value);
char *p;
if (!obj) {
mariadb_throw_exception(self->stmt, Mariadb_ProgrammingError, 0,
"Python type %s has no string representation",
Py_TYPE(value->value)->tp_name);
return 1;
}
p= (void *)PyUnicode_AsUTF8AndSize(obj, &len);
if (!(bind->buffer= value->buffer= PyMem_RawCalloc(1, len)))
{
mariadb_throw_exception(NULL, Mariadb_InterfaceError, 0,
"Not enough memory (tried to allocated %lld bytes)", len);
return 1;
}
value->free_me= 1;
memcpy(bind->buffer, p, len);
bind->buffer_length= (unsigned long)len;
Py_DECREF(obj);
}
break;
}
case MYSQL_TYPE_NULL:

View File

@ -94,7 +94,7 @@ class TestCursor(unittest.TestCase):
cursor.execute("INSERT INTO t_vector VALUES (?,?)", (1, data))
cursor.execute("SELECT id, v, Vec_ToText(v) FROM t_vector")
row= cursor.fetchone()
self.connection.commit()
check_data= [row[1], array.array('f', eval(row[2]))]
cursor.execute("DROP TABLE t_vector")
@ -613,6 +613,27 @@ class TestCursor(unittest.TestCase):
self.assertEqual(row[0], 2)
del cursor
def test_conpy298(self):
import uuid, ipaddress
cursor= self.connection.cursor()
cursor.execute("DROP TABLE IF EXISTS t1")
cursor.execute("CREATE TABLE t1 (a inet6, b inet4, c uuid)")
values= (ipaddress.ip_address('::'), ipaddress.ip_address('192.168.0.1'),
uuid.uuid4())
cursor.execute("INSERT INTO t1 VALUES (?, ?, ?)", values)
cursor.execute("SELECT a,b,c FROM t1")
row= cursor.fetchone()
self.assertEqual(row[0], values[0].__str__())
self.assertEqual(row[1], values[1].__str__())
self.assertEqual(row[2], values[2].__str__())
cursor.execute("DROP TABLE t1")
cursor.close()
def test_conpy34(self):
cursor = self.connection.cursor()
cursor.execute("CREATE TEMPORARY TABLE t1 (a varchar(20),"