diff --git a/src/cursor.cpp b/src/cursor.cpp index f2620594..3f705c3a 100644 --- a/src/cursor.cpp +++ b/src/cursor.cpp @@ -358,6 +358,12 @@ static bool free_results(Cursor* self, int flags) self->description = Py_None; Py_INCREF(Py_None); } + if(self->coldescription != Py_None) + { + Py_DECREF(self->coldescription); + self->coldescription = Py_None; + Py_INCREF(Py_None); + } if (self->map_name_to_index) { @@ -399,11 +405,13 @@ static void closeimpl(Cursor* cur) Py_XDECREF(cur->pPreparedSQL); Py_XDECREF(cur->description); + Py_XDECREF(cur->coldescription); Py_XDECREF(cur->map_name_to_index); Py_XDECREF(cur->cnxn); cur->pPreparedSQL = 0; cur->description = 0; + cur->coldescription = 0; cur->map_name_to_index = 0; cur->cnxn = 0; } @@ -554,6 +562,36 @@ static bool PrepareResults(Cursor* cur, int cCols) return true; } +static bool expose_column_sql_types(Cursor* cur, int cCols) +{ + // Called after a SELECT has been executed to perform pre-fetch work. + // + // Goes through the earlier allocated ColumnInfo structures describing the returned data. + + I(cur->coldescription == Py_None); + + PyObject *colinfo=0, *coldesc=0; + coldesc = PyTuple_New((Py_ssize_t)cCols); + int i; + for (i = 0; i < cCols; i++) + { + ColumnInfo *ci = &cur->colinfos[i]; + colinfo = Py_BuildValue("(iiO)", + (int)ci->sql_type, + (int)ci->column_size, + (ci->is_unsigned ? Py_True : Py_False) + ); + PyTuple_SET_ITEM(coldesc, i, colinfo); + colinfo=0; + } + + Py_XDECREF(cur->coldescription); + cur->coldescription = coldesc; + coldesc = 0; + + return true; +} + static PyObject* execute(Cursor* cur, PyObject* pSql, PyObject* params, bool skip_first) { @@ -860,6 +898,9 @@ static PyObject* execute(Cursor* cur, PyObject* pSql, PyObject* params, bool ski if (!create_name_map(cur, cCols, lowercase())) return 0; + + if (!expose_column_sql_types(cur, cCols)) + return 0; } Py_INCREF(cur); @@ -1830,6 +1871,9 @@ static PyObject* Cursor_nextset(PyObject* self, PyObject* args) if (!create_name_map(cur, cCols, lowercase())) return 0; + + if (!expose_column_sql_types(cur, cCols)) + return 0; } SQLLEN cRows; @@ -2084,6 +2128,13 @@ static char description_doc[] = "the DB API and defined the pyodbc module: Date, Time, Timestamp, Binary,\n" \ "STRING, BINARY, NUMBER, and DATETIME."; +static char coldescription_doc[] = + "This read-only attribute describe columns returned by SQLDescribeCol.\n" + "Returns a tuple:\n" + "0 - sql_type\n" + "1 - column_width\n" + "2 - is_unsigned"; + static char arraysize_doc[] = "This read/write attribute specifies the number of rows to fetch at a time with\n" \ "fetchmany(). It defaults to 1 meaning to fetch a single row at a time."; @@ -2103,6 +2154,7 @@ static PyMemberDef Cursor_members[] = { {"rowcount", T_INT, offsetof(Cursor, rowcount), READONLY, rowcount_doc }, {"description", T_OBJECT_EX, offsetof(Cursor, description), READONLY, description_doc }, + {"coldescription", T_OBJECT_EX, offsetof(Cursor, coldescription),READONLY, coldescription_doc }, {"arraysize", T_INT, offsetof(Cursor, arraysize), 0, arraysize_doc }, {"connection", T_OBJECT_EX, offsetof(Cursor, cnxn), READONLY, connection_doc }, {"fast_executemany",T_BOOL, offsetof(Cursor, fastexecmany), 0, fastexecmany_doc }, @@ -2384,6 +2436,7 @@ Cursor_New(Connection* cnxn) cur->cnxn = cnxn; cur->hstmt = SQL_NULL_HANDLE; cur->description = Py_None; + cur->coldescription = Py_None; cur->pPreparedSQL = 0; cur->paramcount = 0; cur->paramtypes = 0; @@ -2397,6 +2450,7 @@ Cursor_New(Connection* cnxn) Py_INCREF(cnxn); Py_INCREF(cur->description); + Py_INCREF(cur->coldescription); SQLRETURN ret; Py_BEGIN_ALLOW_THREADS diff --git a/src/cursor.h b/src/cursor.h index c39a8d3a..ec123d30 100644 --- a/src/cursor.h +++ b/src/cursor.h @@ -135,6 +135,8 @@ struct Cursor // The description tuple described in the DB API 2.0 specification. Set to None when there are no results. PyObject* description; + PyObject* coldescription; + int arraysize; // The Cursor.rowcount attribute from the DB API specification.