Skip to content

Commit

Permalink
CONPY-56: Support dictionary option in cursor class
Browse files Browse the repository at this point in the history
Added optional boolean parameter 'dictionary' for cursor class.
When dictionary parameter was set to true, the fetch operations will
return rows from result set as Dict.
  • Loading branch information
9EOR9 committed Apr 14, 2020
1 parent e2cebf7 commit e873f87
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 30 deletions.
13 changes: 11 additions & 2 deletions include/docs/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,19 @@ PyDoc_STRVAR(

PyDoc_STRVAR(
connection_cursor__doc__,
"cursor()\n"
"cursor(self, buffered=None, dictionary=None, named_tuple=None, cursor_type=mariadb.CURSOR_TYPE_NONE,\n"
" prepared=None, prefetch_rows=1)\n"
"--\n"
"\n"
"Return a new cursor object for the current connection."
"Returns a new cursor object for the current connection.\n\n"
"By default the result will be unbuffered, which means before executing another\n"
"statement with the same connection the entire result set must be fetched.\n\n"
"fetch methods of the cursor class by default return result set values as a tuple, unless\n"
"named_tuple or dictionary was specified. The latter one exists for compatibility reasons\n"
"and should be avoided due to possible inconsistency in case two or more fields in a\n"
"result sets have the same name.\n\n"
"If cursor_type is set to mariadb.CURSOR_TYPE_READ_ONLY, a cursor is opened for the\n"
"statement invoked with cursors execute() method."
);

PyDoc_STRVAR(
Expand Down
11 changes: 10 additions & 1 deletion include/mariadb_python.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ enum enum_dataapi_groups
DBAPI_ROWID
};

enum enum_result_format
{
RESULT_TUPLE= 0,
RESULT_NAMED_TUPLE,
RESULT_DICTIONARY
};

enum enum_dyncol_type
{
DYNCOL_LIST= 1,
Expand Down Expand Up @@ -237,9 +244,11 @@ typedef struct {
int64_t row_count;
uint32_t field_count;
unsigned long row_number;
enum enum_result_format result_format;
uint8_t is_prepared;
uint8_t is_buffered;
uint8_t is_named_tuple;
/* uint8_t is_named_tuple;
uint8_t is_dictionary; */
uint8_t is_closed;
uint8_t is_text;
MrdbParser *parser;
Expand Down
4 changes: 2 additions & 2 deletions src/mariadb.c
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ struct st_constants {
} u;
};

struct st_constants int_constants[]= {
static struct st_constants int_constants[]= {
{"CURSOR_TYPE_READ_ONLY", {CURSOR_TYPE_READ_ONLY}},
{"CURSOR_TYPE_NONE", {CURSOR_TYPE_NO_CURSOR}},
{NULL, {0}} /* Always last */
Expand Down Expand Up @@ -193,7 +193,7 @@ PyMODINIT_FUNC PyInit_mariadb(void)
PyModule_AddIntConstant(module, intvals->name,
intvals->u.lvalue);
intvals++;
}
}

/* PEP-249: mandatory module globals */
PyModule_AddObject(module, "apilevel",
Expand Down
70 changes: 46 additions & 24 deletions src/mariadb_cursor.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,17 +101,6 @@ strncpy((a)->statement, (s), (l));\
#define CURSOR_NUM_ROWS(a)\
((a)->is_text ? mysql_num_rows((a)->result) : (a)->stmt ? mysql_stmt_num_rows((a)->stmt) : 0)

#define MARIADB_SET_SEQUENCE_OR_TUPLE_ITEM(self, row, column)\
if ((self)->is_named_tuple)\
{\
PyStructSequence_SET_ITEM((row), (column),\
(self)->values[(column)]);\
}\
else {\
PyTuple_SET_ITEM((row), (column), (self)->values[(column)]);\
}


static char *mariadb_named_tuple_name= "Row";
static char *mariadb_named_tuple_desc= "Named tupled row";
static PyObject *Mariadb_no_operation(MrdbCursor *,
Expand Down Expand Up @@ -236,10 +225,11 @@ buffered: buffered or unbuffered result sets
static int MrdbCursor_initialize(MrdbCursor *self, PyObject *args,
PyObject *kwargs)
{
char *key_words[]= {"", "named_tuple", "prefetch_size", "cursor_type",
char *key_words[]= {"", "named_tuple", "dictionary", "prefetch_size", "cursor_type",
"buffered", "prepared", NULL};
PyObject *connection;
uint8_t is_named_tuple= 0;
uint8_t is_dictionary= 0;
unsigned long cursor_type= 0,
prefetch_rows= 0;
uint8_t is_buffered= 0;
Expand All @@ -249,8 +239,8 @@ static int MrdbCursor_initialize(MrdbCursor *self, PyObject *args,
return -1;

if (!PyArg_ParseTupleAndKeywords(args, kwargs,
"O!|bkkbb", key_words, &MrdbConnection_Type, &connection,
&is_named_tuple, &prefetch_rows, &cursor_type, &is_buffered,
"O!|bbkkbb", key_words, &MrdbConnection_Type, &connection,
&is_named_tuple, &is_dictionary, &prefetch_rows, &cursor_type, &is_buffered,
&is_prepared))
return -1;

Expand All @@ -269,6 +259,22 @@ static int MrdbCursor_initialize(MrdbCursor *self, PyObject *args,
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);
self->connection= (MrdbConnection *)connection;
self->is_buffered= is_buffered ? is_buffered : self->connection->is_buffered;
Expand All @@ -284,7 +290,6 @@ static int MrdbCursor_initialize(MrdbCursor *self, PyObject *args,

self->cursor_type= cursor_type;
self->prefetch_rows= prefetch_rows;
self->is_named_tuple= is_named_tuple;
self->row_array_size= 1;

if (self->cursor_type || self->prefetch_rows)
Expand Down Expand Up @@ -452,6 +457,21 @@ void MrdbCursor_clear(MrdbCursor *self, uint8_t new_stmt)
}
/* }}} */

static void ma_set_result_column_value(MrdbCursor *self, PyObject *row, uint32_t column)
{
switch (self->result_format) {
case RESULT_NAMED_TUPLE:
PyStructSequence_SET_ITEM(row, column, self->values[column]);
break;
case RESULT_DICTIONARY:
PyDict_SetItemString(row, self->fields[column].name, self->values[column]);
break;
default:
PyTuple_SET_ITEM(row, column, (self)->values[column]);
}
}


/* {{{ ma_cursor_close
closes the statement handle of current cursor. After call to
cursor_close the cursor can't be reused anymore
Expand Down Expand Up @@ -526,7 +546,7 @@ static int Mrdb_GetFieldInfo(MrdbCursor *self)
self->fields= (self->is_text) ? mysql_fetch_fields(self->result) :
mariadb_stmt_fetch_fields(self->stmt);

if (self->is_named_tuple) {
if (self->result_format == RESULT_NAMED_TUPLE) {
unsigned int i;
if (!(self->sequence_fields= (PyStructSequence_Field *)
PyMem_RawCalloc(self->field_count + 1,
Expand Down Expand Up @@ -918,7 +938,7 @@ MrdbCursor_fetchone(MrdbCursor *self)

for (i= 0; i < field_count; i++)
{
MARIADB_SET_SEQUENCE_OR_TUPLE_ITEM(self, row, i);
ma_set_result_column_value(self, row, i);
}
return row;
}
Expand Down Expand Up @@ -1077,7 +1097,7 @@ MrdbCursor_fetchmany(MrdbCursor *self,
}
for (j=0; j < field_count; j++)
{
MARIADB_SET_SEQUENCE_OR_TUPLE_ITEM(self, Row, j);
ma_set_result_column_value(self, Row, j);
}
PyList_Append(List, Row);
}
Expand All @@ -1088,12 +1108,14 @@ MrdbCursor_fetchmany(MrdbCursor *self,
static PyObject *
mariadb_get_sequence_or_tuple(MrdbCursor *self)
{
if (self->is_named_tuple)
switch (self->result_format)
{
return PyStructSequence_New(self->sequence_type);
}
else {
return PyTuple_New(self->field_count);
case RESULT_NAMED_TUPLE:
return PyStructSequence_New(self->sequence_type);
case RESULT_DICTIONARY:
return PyDict_New();
default:
return PyTuple_New(self->field_count);
}
}

Expand Down Expand Up @@ -1134,7 +1156,7 @@ MrdbCursor_fetchall(MrdbCursor *self)

for (j=0; j < field_count; j++)
{
MARIADB_SET_SEQUENCE_OR_TUPLE_ITEM(self, Row, j)
ma_set_result_column_value(self, Row, j);
}
PyList_Append(List, Row);
}
Expand Down
10 changes: 10 additions & 0 deletions test/integration/test_cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -845,5 +845,15 @@ def test_conpy49(self):
self.assertEqual(row[0], Decimal('10.20'))
del con

def test_conpy56(self):
con= create_connection()
cur=con.cursor(dictionary=True)
cur.execute("select 'foo' as bar, 'bar' as foo")
row= cur.fetchone()
self.assertEqual(row["foo"], "bar")
self.assertEqual(row["bar"], "foo")
del con


if __name__ == '__main__':
unittest.main()
2 changes: 1 addition & 1 deletion test/integration/test_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def test_conn_timeout_exception(self):
except mariadb.DatabaseError as err:
self.assertEqual(err.sqlstate, "HY000")
self.assertEqual(err.errno, 2002)
self.assertTrue(err.errmsg.find("Can't connect to MySQL server on '8.8.8.8'") > -1)
self.assertTrue(err.errmsg.find("server on '8.8.8.8'") > -1)
end = datetime.today()
difference = end - start
self.assertEqual(difference.days, 0)
Expand Down

0 comments on commit e873f87

Please sign in to comment.