Skip to content

Commit

Permalink
Rework row fetching across the module
Browse files Browse the repository at this point in the history
Whilst checking the performance implications of pymssql#158 it was noted that
there was a rather large amount of type conversion occuring when
fetching a row from pymssql. The number of conversions required have
been cut down significantly with these changes.

For a tuple row it is changed from:

tuple -> dict -> dict -> tuple to tuple

And for a dictionary:

tuple -> dict -> dict to dict -> dict

The additional dict->dict conversion could be dropped with either
additional arguments being passed to _mssql so it doesn't add the
positional mappings to the dictionary. I didn't want to break the _mssql
module just incase.
  • Loading branch information
Damien Churchill committed Jan 9, 2014
1 parent a97a43b commit 563aeaa
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 45 deletions.
6 changes: 3 additions & 3 deletions _mssql.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ cdef class MSSQLConnection:
cpdef execute_non_query(self, query, params=?)
cpdef execute_row(self, query, params=?)
cpdef execute_scalar(self, query, params=?)
cdef fetch_next_row(self, int)
cdef fetch_next_row_dict(self, int)
cdef fetch_next_row(self, int, int)
cdef format_and_run_query(self, query_string, params=?)
cdef format_sql_command(self, format, params=?)
cdef get_result(self)
cdef get_row(self, int)
cdef get_row(self, int, int)

cdef class MSSQLRowIterator:
cdef MSSQLConnection conn
cdef int row_format

cdef class MSSQLStoredProcedure:
cdef MSSQLConnection conn
Expand Down
78 changes: 42 additions & 36 deletions _mssql.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ DEF EXCOMM = 9
DEF DBVERSION_71 = 5
DEF DBVERSION_72 = 6

ROW_FORMAT_TUPLE = 1
ROW_FORMAT_DICT = 2

from cpython cimport PY_MAJOR_VERSION, PY_MINOR_VERSION

if PY_MAJOR_VERSION >= 2 and PY_MINOR_VERSION >= 5:
Expand Down Expand Up @@ -348,16 +351,17 @@ cdef RETCODE db_cancel(MSSQLConnection conn):
##############################
cdef class MSSQLRowIterator:

def __init__(self, connection):
def __init__(self, connection, int row_format):
self.conn = connection
self.row_format = row_format

def __iter__(self):
return self

def __next__(self):
assert_connected(self.conn)
clr_err(self.conn)
return self.conn.fetch_next_row_dict(1)
return self.conn.fetch_next_row(1, self.row_format)

############################
## MSSQL Connection Class ##
Expand Down Expand Up @@ -924,7 +928,7 @@ cdef class MSSQLConnection:
"""
log("_mssql.MSSQLConnection.execute_row()")
self.format_and_run_query(query_string, params)
return self.fetch_next_row_dict(0)
return self.fetch_next_row(0, ROW_FORMAT_DICT)

cpdef execute_scalar(self, query_string, params=None):
"""
Expand Down Expand Up @@ -963,9 +967,9 @@ cdef class MSSQLConnection:
self.last_dbresults = 0
return None

return self.get_row(rtc)[0]
return self.get_row(rtc, ROW_FORMAT_TUPLE)[0]

cdef fetch_next_row(self, int throw):
cdef fetch_next_row(self, int throw, int row_format):
cdef RETCODE rtc
log("_mssql.MSSQLConnection.fetch_next_row() BEGIN")
try:
Expand All @@ -992,29 +996,10 @@ cdef class MSSQLConnection:
raise StopIteration
return None

return self.get_row(rtc)
return self.get_row(rtc, row_format)
finally:
log("_mssql.MSSQLConnection.fetch_next_row() END")

cdef fetch_next_row_dict(self, int throw):
cdef int col
log("_mssql.MSSQLConnection.fetch_next_row_dict()")

row_dict = {}
row = self.fetch_next_row(throw)

for col in xrange(1, self.num_columns + 1):
name = self.column_names[col - 1]
value = row[col - 1]

# Add key by column name, only if the column has a name
if name:
row_dict[name] = value

row_dict[col - 1] = value

return row_dict

cdef format_and_run_query(self, query_string, params=None):
"""
This is a helper function, which does most of the work needed by any
Expand Down Expand Up @@ -1083,6 +1068,17 @@ cdef class MSSQLConnection:
finally:
log("_mssql.MSSQLConnection.get_header() END")

def get_iterator(self, int row_format):
"""
get_iterator(row_format) -- allows the format of the iterator to be specified
While the iter(conn) call will always return a dictionary, this
method allows the return type of the row to be specified.
"""
assert_connected(self)
clr_err(self)
return MSSQLRowIterator(self, row_format)

cdef get_result(self):
cdef int coltype
cdef char log_message[200]
Expand Down Expand Up @@ -1131,7 +1127,7 @@ cdef class MSSQLConnection:
finally:
log("_mssql.MSSQLConnection.get_result() END")

cdef get_row(self, int row_info):
cdef get_row(self, int row_info, int row_format):
cdef DBPROCESS *dbproc = self.dbproc
cdef int col
cdef int col_type
Expand All @@ -1143,7 +1139,10 @@ cdef class MSSQLConnection:
global _row_count
_row_count += 1

record = tuple()
if row_format == ROW_FORMAT_TUPLE:
record = tuple()
elif row_format == ROW_FORMAT_DICT:
record = dict()

for col in xrange(1, self.num_columns + 1):
with nogil:
Expand All @@ -1152,16 +1151,23 @@ cdef class MSSQLConnection:
len = get_length(dbproc, row_info, col)

if data == NULL:
record += (None,)
continue

IF PYMSSQL_DEBUG == 1:
global _row_count
fprintf(stderr, 'Processing row %d, column %d,' \
'Got data=%x, coltype=%d, len=%d\n', _row_count, col,
data, col_type, len)
value = None
else:
IF PYMSSQL_DEBUG == 1:
global _row_count
fprintf(stderr, 'Processing row %d, column %d,' \
'Got data=%x, coltype=%d, len=%d\n', _row_count, col,
data, col_type, len)
value = self.convert_db_value(data, col_type, len)

if row_format == ROW_FORMAT_TUPLE:
record += (value,)
elif row_format == ROW_FORMAT_DICT:
name = self.column_names[col - 1]
record[col - 1] = value
if name:
record[name] = value

record += (self.convert_db_value(data, col_type, len),)
return record

def init_procedure(self, procname):
Expand Down
11 changes: 5 additions & 6 deletions pymssql.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -443,12 +443,11 @@ cdef class Cursor:
Helper method used by fetchone and fetchmany to fetch and handle
converting the row if as_dict = False.
"""
row = next(iter(self._source._conn))
self._rownumber = self._source._conn.rows_affected
if self.as_dict:
return row2dict(row)
row = dict([(k, v) for k, v in row.items() if isinstance(k, int)])
return tuple([row[r] for r in sorted(row) if type(r) == int])
row_format = _mssql.ROW_FORMAT_DICT if self.as_dict else _mssql.ROW_FORMAT_TUPLE
row = next(self._source._conn.get_iterator(row_format))
if not self.as_dict:
return row
return row2dict(row)

def fetchone(self):
if self.description is None:
Expand Down

0 comments on commit 563aeaa

Please sign in to comment.