mirror of
https://github.com/PyMySQL/mysqlclient.git
synced 2025-08-16 12:27:03 +08:00
Fix and optimize fetching dict rows. (#458)
This commit is contained in:
2
.github/workflows/windows.yaml
vendored
2
.github/workflows/windows.yaml
vendored
@ -2,6 +2,8 @@ name: Build windows wheels
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
131
MySQLdb/_mysql.c
131
MySQLdb/_mysql.c
@ -1194,7 +1194,8 @@ _mysql_field_to_python(
|
|||||||
static PyObject *
|
static PyObject *
|
||||||
_mysql_row_to_tuple(
|
_mysql_row_to_tuple(
|
||||||
_mysql_ResultObject *self,
|
_mysql_ResultObject *self,
|
||||||
MYSQL_ROW row)
|
MYSQL_ROW row,
|
||||||
|
PyObject *unused)
|
||||||
{
|
{
|
||||||
unsigned int n, i;
|
unsigned int n, i;
|
||||||
unsigned long *length;
|
unsigned long *length;
|
||||||
@ -1221,7 +1222,8 @@ _mysql_row_to_tuple(
|
|||||||
static PyObject *
|
static PyObject *
|
||||||
_mysql_row_to_dict(
|
_mysql_row_to_dict(
|
||||||
_mysql_ResultObject *self,
|
_mysql_ResultObject *self,
|
||||||
MYSQL_ROW row)
|
MYSQL_ROW row,
|
||||||
|
PyObject *cache)
|
||||||
{
|
{
|
||||||
unsigned int n, i;
|
unsigned int n, i;
|
||||||
unsigned long *length;
|
unsigned long *length;
|
||||||
@ -1243,40 +1245,42 @@ _mysql_row_to_dict(
|
|||||||
Py_DECREF(v);
|
Py_DECREF(v);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
int err = PyDict_Contains(r, pyname);
|
||||||
PyObject *tmp = PyDict_SetDefault(r, pyname, v);
|
if (err < 0) { // error
|
||||||
Py_DECREF(pyname);
|
|
||||||
if (!tmp) {
|
|
||||||
Py_DECREF(v);
|
Py_DECREF(v);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
if (tmp == v) {
|
if (err) { // duplicate
|
||||||
Py_DECREF(v);
|
Py_DECREF(pyname);
|
||||||
continue;
|
pyname = PyUnicode_FromFormat("%s.%s", fields[i].table, fields[i].name);
|
||||||
|
if (pyname == NULL) {
|
||||||
|
Py_DECREF(v);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pyname = PyUnicode_FromFormat("%s.%s", fields[i].table, fields[i].name);
|
err = PyDict_SetItem(r, pyname, v);
|
||||||
if (!pyname) {
|
if (cache) {
|
||||||
Py_DECREF(v);
|
PyTuple_SET_ITEM(cache, i, pyname);
|
||||||
goto error;
|
} else {
|
||||||
|
Py_DECREF(pyname);
|
||||||
}
|
}
|
||||||
int err = PyDict_SetItem(r, pyname, v);
|
|
||||||
Py_DECREF(pyname);
|
|
||||||
Py_DECREF(v);
|
Py_DECREF(v);
|
||||||
if (err) {
|
if (err) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
error:
|
error:
|
||||||
Py_XDECREF(r);
|
Py_DECREF(r);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_mysql_row_to_dict_old(
|
_mysql_row_to_dict_old(
|
||||||
_mysql_ResultObject *self,
|
_mysql_ResultObject *self,
|
||||||
MYSQL_ROW row)
|
MYSQL_ROW row,
|
||||||
|
PyObject *cache)
|
||||||
{
|
{
|
||||||
unsigned int n, i;
|
unsigned int n, i;
|
||||||
unsigned long *length;
|
unsigned long *length;
|
||||||
@ -1302,7 +1306,46 @@ _mysql_row_to_dict_old(
|
|||||||
pyname = PyUnicode_FromString(fields[i].name);
|
pyname = PyUnicode_FromString(fields[i].name);
|
||||||
}
|
}
|
||||||
int err = PyDict_SetItem(r, pyname, v);
|
int err = PyDict_SetItem(r, pyname, v);
|
||||||
Py_DECREF(pyname);
|
Py_DECREF(v);
|
||||||
|
if (cache) {
|
||||||
|
PyTuple_SET_ITEM(cache, i, pyname);
|
||||||
|
} else {
|
||||||
|
Py_DECREF(pyname);
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
error:
|
||||||
|
Py_XDECREF(r);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_mysql_row_to_dict_cached(
|
||||||
|
_mysql_ResultObject *self,
|
||||||
|
MYSQL_ROW row,
|
||||||
|
PyObject *cache)
|
||||||
|
{
|
||||||
|
PyObject *r = PyDict_New();
|
||||||
|
if (!r) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int n = mysql_num_fields(self->result);
|
||||||
|
unsigned long *length = mysql_fetch_lengths(self->result);
|
||||||
|
MYSQL_FIELD *fields = mysql_fetch_fields(self->result);
|
||||||
|
|
||||||
|
for (unsigned int i=0; i<n; i++) {
|
||||||
|
PyObject *c = PyTuple_GET_ITEM(self->converter, i);
|
||||||
|
PyObject *v = _mysql_field_to_python(c, row[i], length[i], &fields[i], self->encoding);
|
||||||
|
if (!v) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *pyname = PyTuple_GET_ITEM(cache, i); // borrowed
|
||||||
|
int err = PyDict_SetItem(r, pyname, v);
|
||||||
Py_DECREF(v);
|
Py_DECREF(v);
|
||||||
if (err) {
|
if (err) {
|
||||||
goto error;
|
goto error;
|
||||||
@ -1314,15 +1357,31 @@ _mysql_row_to_dict_old(
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef PyObject *_PYFUNC(_mysql_ResultObject *, MYSQL_ROW);
|
|
||||||
|
typedef PyObject *_convertfunc(_mysql_ResultObject *, MYSQL_ROW, PyObject *);
|
||||||
|
static _convertfunc * const row_converters[] = {
|
||||||
|
_mysql_row_to_tuple,
|
||||||
|
_mysql_row_to_dict,
|
||||||
|
_mysql_row_to_dict_old
|
||||||
|
};
|
||||||
|
|
||||||
Py_ssize_t
|
Py_ssize_t
|
||||||
_mysql__fetch_row(
|
_mysql__fetch_row(
|
||||||
_mysql_ResultObject *self,
|
_mysql_ResultObject *self,
|
||||||
PyObject *r, /* list object */
|
PyObject *r, /* list object */
|
||||||
Py_ssize_t maxrows,
|
Py_ssize_t maxrows,
|
||||||
_PYFUNC *convert_row)
|
int how)
|
||||||
{
|
{
|
||||||
|
_convertfunc *convert_row = row_converters[how];
|
||||||
|
|
||||||
|
PyObject *cache = NULL;
|
||||||
|
if (maxrows > 0 && how > 0) {
|
||||||
|
cache = PyTuple_New(mysql_num_fields(self->result));
|
||||||
|
if (!cache) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Py_ssize_t i;
|
Py_ssize_t i;
|
||||||
for (i = 0; i < maxrows; i++) {
|
for (i = 0; i < maxrows; i++) {
|
||||||
MYSQL_ROW row;
|
MYSQL_ROW row;
|
||||||
@ -1335,20 +1394,29 @@ _mysql__fetch_row(
|
|||||||
}
|
}
|
||||||
if (!row && mysql_errno(&(((_mysql_ConnectionObject *)(self->conn))->connection))) {
|
if (!row && mysql_errno(&(((_mysql_ConnectionObject *)(self->conn))->connection))) {
|
||||||
_mysql_Exception((_mysql_ConnectionObject *)self->conn);
|
_mysql_Exception((_mysql_ConnectionObject *)self->conn);
|
||||||
return -1;
|
goto error;
|
||||||
}
|
}
|
||||||
if (!row) {
|
if (!row) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
PyObject *v = convert_row(self, row);
|
PyObject *v = convert_row(self, row, cache);
|
||||||
if (!v) return -1;
|
if (!v) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (cache) {
|
||||||
|
convert_row = _mysql_row_to_dict_cached;
|
||||||
|
}
|
||||||
if (PyList_Append(r, v)) {
|
if (PyList_Append(r, v)) {
|
||||||
Py_DECREF(v);
|
Py_DECREF(v);
|
||||||
return -1;
|
goto error;
|
||||||
}
|
}
|
||||||
Py_DECREF(v);
|
Py_DECREF(v);
|
||||||
}
|
}
|
||||||
|
Py_XDECREF(cache);
|
||||||
return i;
|
return i;
|
||||||
|
error:
|
||||||
|
Py_XDECREF(cache);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static char _mysql_ResultObject_fetch_row__doc__[] =
|
static char _mysql_ResultObject_fetch_row__doc__[] =
|
||||||
@ -1366,15 +1434,7 @@ _mysql_ResultObject_fetch_row(
|
|||||||
PyObject *args,
|
PyObject *args,
|
||||||
PyObject *kwargs)
|
PyObject *kwargs)
|
||||||
{
|
{
|
||||||
typedef PyObject *_PYFUNC(_mysql_ResultObject *, MYSQL_ROW);
|
static char *kwlist[] = {"maxrows", "how", NULL };
|
||||||
static char *kwlist[] = { "maxrows", "how", NULL };
|
|
||||||
static _PYFUNC *row_converters[] =
|
|
||||||
{
|
|
||||||
_mysql_row_to_tuple,
|
|
||||||
_mysql_row_to_dict,
|
|
||||||
_mysql_row_to_dict_old
|
|
||||||
};
|
|
||||||
_PYFUNC *convert_row;
|
|
||||||
int maxrows=1, how=0;
|
int maxrows=1, how=0;
|
||||||
PyObject *r=NULL;
|
PyObject *r=NULL;
|
||||||
|
|
||||||
@ -1386,7 +1446,6 @@ _mysql_ResultObject_fetch_row(
|
|||||||
PyErr_SetString(PyExc_ValueError, "how out of range");
|
PyErr_SetString(PyExc_ValueError, "how out of range");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
convert_row = row_converters[how];
|
|
||||||
if (!maxrows) {
|
if (!maxrows) {
|
||||||
if (self->use) {
|
if (self->use) {
|
||||||
maxrows = INT_MAX;
|
maxrows = INT_MAX;
|
||||||
@ -1396,7 +1455,7 @@ _mysql_ResultObject_fetch_row(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!(r = PyList_New(0))) goto error;
|
if (!(r = PyList_New(0))) goto error;
|
||||||
Py_ssize_t rowsadded = _mysql__fetch_row(self, r, maxrows, convert_row);
|
Py_ssize_t rowsadded = _mysql__fetch_row(self, r, maxrows, how);
|
||||||
if (rowsadded == -1) goto error;
|
if (rowsadded == -1) goto error;
|
||||||
|
|
||||||
/* DB-API allows return rows as list.
|
/* DB-API allows return rows as list.
|
||||||
|
@ -111,3 +111,42 @@ def test_pyparam():
|
|||||||
assert cursor._executed == b"SELECT 1, 2"
|
assert cursor._executed == b"SELECT 1, 2"
|
||||||
cursor.execute(b"SELECT %(a)s, %(b)s", {b"a": 3, b"b": 4})
|
cursor.execute(b"SELECT %(a)s, %(b)s", {b"a": 3, b"b": 4})
|
||||||
assert cursor._executed == b"SELECT 3, 4"
|
assert cursor._executed == b"SELECT 3, 4"
|
||||||
|
|
||||||
|
|
||||||
|
def test_dictcursor():
|
||||||
|
conn = connect()
|
||||||
|
cursor = conn.cursor(MySQLdb.cursors.DictCursor)
|
||||||
|
|
||||||
|
cursor.execute("CREATE TABLE t1 (a int, b int, c int)")
|
||||||
|
_tables.append("t1")
|
||||||
|
cursor.execute("INSERT INTO t1 (a,b,c) VALUES (1,1,47), (2,2,47)")
|
||||||
|
|
||||||
|
cursor.execute("CREATE TABLE t2 (b int, c int)")
|
||||||
|
_tables.append("t2")
|
||||||
|
cursor.execute("INSERT INTO t2 (b,c) VALUES (1,1), (2,2)")
|
||||||
|
|
||||||
|
cursor.execute("SELECT * FROM t1 JOIN t2 ON t1.b=t2.b")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
|
assert len(rows) == 2
|
||||||
|
assert rows[0] == {"a": 1, "b": 1, "c": 47, "t2.b": 1, "t2.c": 1}
|
||||||
|
assert rows[1] == {"a": 2, "b": 2, "c": 47, "t2.b": 2, "t2.c": 2}
|
||||||
|
|
||||||
|
names1 = sorted(rows[0])
|
||||||
|
names2 = sorted(rows[1])
|
||||||
|
for a, b in zip(names1, names2):
|
||||||
|
assert a is b
|
||||||
|
|
||||||
|
# Old fetchtype
|
||||||
|
cursor._fetch_type = 2
|
||||||
|
cursor.execute("SELECT * FROM t1 JOIN t2 ON t1.b=t2.b")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
|
assert len(rows) == 2
|
||||||
|
assert rows[0] == {"t1.a": 1, "t1.b": 1, "t1.c": 47, "t2.b": 1, "t2.c": 1}
|
||||||
|
assert rows[1] == {"t1.a": 2, "t1.b": 2, "t1.c": 47, "t2.b": 2, "t2.c": 2}
|
||||||
|
|
||||||
|
names1 = sorted(rows[0])
|
||||||
|
names2 = sorted(rows[1])
|
||||||
|
for a, b in zip(names1, names2):
|
||||||
|
assert a is b
|
||||||
|
Reference in New Issue
Block a user