Permalink
Browse files

fix: Function sequence error with prepared execute, SQLTables, same e…

…xecute. (Lost fix)

This was fixed in 2.1.12 and lost during the Python 3 conversion.

The saved prepared statement was not cleaned up when a SQLTables or similar function was used.
If the next execute used the same SQL, pyodbc did not call SQLPrepare.
(cherry picked from commit 7fdec2f)
1 parent 6a457c3 commit dc35242ed3aea667557e1f1e16fc9f35f4a451fd Michael Kleehammer committed Oct 17, 2011
Showing with 66 additions and 28 deletions.
  1. +43 −28 src/cursor.cpp
  2. +11 −0 tests2/informixtests.py
  3. +12 −0 tests2/sqlservertests.py
View
@@ -360,20 +360,35 @@ static bool create_name_map(Cursor* cur, SQLSMALLINT field_count, bool lower)
return success;
}
-enum free_results_type
+
+enum free_results_flags
{
- FREE_STATEMENT,
- KEEP_STATEMENT
+ FREE_STATEMENT = 0x01,
+ KEEP_STATEMENT = 0x02,
+ FREE_PREPARED = 0x04,
+ KEEP_PREPARED = 0x08,
+
+ STATEMENT_MASK = 0x03,
+ PREPARED_MASK = 0x0C
};
-static bool free_results(Cursor* self, free_results_type free_statement)
+static bool free_results(Cursor* self, int flags)
{
// Internal function called any time we need to free the memory associated with query results. It is safe to call
// this even when a query has not been executed.
// If we ran out of memory, it is possible that we have a cursor but colinfos is zero. However, we should be
// deleting this object, so the cursor will be freed when the HSTMT is destroyed. */
+ I((flags & STATEMENT_MASK) != 0);
+ I((flags & PREPARED_MASK) != 0);
+
+ if ((flags & PREPARED_MASK) == FREE_PREPARED)
+ {
+ Py_XDECREF(self->pPreparedSQL);
+ self->pPreparedSQL = 0;
+ }
+
if (self->colinfos)
{
pyodbc_free(self->colinfos);
@@ -382,7 +397,7 @@ static bool free_results(Cursor* self, free_results_type free_statement)
if (StatementIsValid(self))
{
- if (free_statement == FREE_STATEMENT)
+ if ((flags & STATEMENT_MASK) == FREE_STATEMENT)
{
Py_BEGIN_ALLOW_THREADS
SQLFreeStmt(self->hstmt, SQL_CLOSE);
@@ -429,7 +444,7 @@ static void closeimpl(Cursor* cur)
//
// This method releases the GIL lock while closing, so verify the HDBC still exists if you use it.
- free_results(cur, FREE_STATEMENT);
+ free_results(cur, FREE_STATEMENT | FREE_PREPARED);
FreeParameterInfo(cur);
FreeParameterData(cur);
@@ -630,7 +645,7 @@ static PyObject* execute(Cursor* cur, PyObject* pSql, PyObject* params, bool ski
SQLRETURN ret = 0;
- free_results(cur, FREE_STATEMENT);
+ free_results(cur, FREE_STATEMENT | KEEP_PREPARED);
const char* szLastFunction = "";
@@ -1174,8 +1189,8 @@ static PyObject* Cursor_tables(PyObject* self, PyObject* args, PyObject* kwargs)
return 0;
Cursor* cur = Cursor_Validate(self, CURSOR_REQUIRE_OPEN);
-
- if (!free_results(cur, FREE_STATEMENT))
+
+ if (!free_results(cur, FREE_STATEMENT | FREE_PREPARED))
return 0;
SQLRETURN ret = 0;
@@ -1243,8 +1258,8 @@ static PyObject* Cursor_columns(PyObject* self, PyObject* args, PyObject* kwargs
return 0;
Cursor* cur = Cursor_Validate(self, CURSOR_REQUIRE_OPEN);
-
- if (!free_results(cur, FREE_STATEMENT))
+
+ if (!free_results(cur, FREE_STATEMENT | FREE_PREPARED))
return 0;
SQLRETURN ret = 0;
@@ -1315,8 +1330,8 @@ static PyObject* Cursor_statistics(PyObject* self, PyObject* args, PyObject* kwa
return 0;
Cursor* cur = Cursor_Validate(self, CURSOR_REQUIRE_OPEN);
-
- if (!free_results(cur, FREE_STATEMENT))
+
+ if (!free_results(cur, FREE_STATEMENT | FREE_PREPARED))
return 0;
SQLUSMALLINT nUnique = (SQLUSMALLINT)(PyObject_IsTrue(pUnique) ? SQL_INDEX_UNIQUE : SQL_INDEX_ALL);
@@ -1392,8 +1407,8 @@ static PyObject* _specialColumns(PyObject* self, PyObject* args, PyObject* kwarg
return 0;
Cursor* cur = Cursor_Validate(self, CURSOR_REQUIRE_OPEN);
-
- if (!free_results(cur, FREE_STATEMENT))
+
+ if (!free_results(cur, FREE_STATEMENT | FREE_PREPARED))
return 0;
SQLRETURN ret = 0;
@@ -1463,8 +1478,8 @@ static PyObject* Cursor_primaryKeys(PyObject* self, PyObject* args, PyObject* kw
return 0;
Cursor* cur = Cursor_Validate(self, CURSOR_REQUIRE_OPEN);
-
- if (!free_results(cur, FREE_STATEMENT))
+
+ if (!free_results(cur, FREE_STATEMENT | FREE_PREPARED))
return 0;
SQLRETURN ret = 0;
@@ -1534,8 +1549,8 @@ static PyObject* Cursor_foreignKeys(PyObject* self, PyObject* args, PyObject* kw
return 0;
Cursor* cur = Cursor_Validate(self, CURSOR_REQUIRE_OPEN);
-
- if (!free_results(cur, FREE_STATEMENT))
+
+ if (!free_results(cur, FREE_STATEMENT | FREE_PREPARED))
return 0;
SQLRETURN ret = 0;
@@ -1602,8 +1617,8 @@ static PyObject* Cursor_getTypeInfo(PyObject* self, PyObject* args, PyObject* kw
return 0;
Cursor* cur = Cursor_Validate(self, CURSOR_REQUIRE_OPEN);
-
- if (!free_results(cur, FREE_STATEMENT))
+
+ if (!free_results(cur, FREE_STATEMENT | FREE_PREPARED))
return 0;
SQLRETURN ret = 0;
@@ -1651,7 +1666,7 @@ static PyObject* Cursor_nextset(PyObject* self, PyObject* args)
if (ret == SQL_NO_DATA)
{
- free_results(cur, FREE_STATEMENT);
+ free_results(cur, FREE_STATEMENT | KEEP_PREPARED);
Py_RETURN_FALSE;
}
@@ -1664,10 +1679,10 @@ static PyObject* Cursor_nextset(PyObject* self, PyObject* args)
// Note: The SQL Server driver sometimes returns HY007 here if multiple statements (separated by ;) were
// submitted. This is not documented, but I've seen it with multiple successful inserts.
- free_results(cur, FREE_STATEMENT);
+ free_results(cur, FREE_STATEMENT | KEEP_PREPARED);
return RaiseErrorFromHandle("SQLNumResultCols", cur->cnxn->hdbc, cur->hstmt);
}
- free_results(cur, KEEP_STATEMENT);
+ free_results(cur, KEEP_STATEMENT | KEEP_PREPARED);
if (cCols != 0)
{
@@ -1729,8 +1744,8 @@ static PyObject* Cursor_procedureColumns(PyObject* self, PyObject* args, PyObjec
return 0;
Cursor* cur = Cursor_Validate(self, CURSOR_REQUIRE_OPEN);
-
- if (!free_results(cur, FREE_STATEMENT))
+
+ if (!free_results(cur, FREE_STATEMENT | FREE_PREPARED))
return 0;
SQLRETURN ret = 0;
@@ -1788,8 +1803,8 @@ static PyObject* Cursor_procedures(PyObject* self, PyObject* args, PyObject* kwa
return 0;
Cursor* cur = Cursor_Validate(self, CURSOR_REQUIRE_OPEN);
-
- if (!free_results(cur, FREE_STATEMENT))
+
+ if (!free_results(cur, FREE_STATEMENT | FREE_PREPARED))
return 0;
SQLRETURN ret = 0;
View
@@ -1215,6 +1215,17 @@ def test_drivers(self):
current = m.group(1)
self.assert_(current in drivers)
+ def test_prepare_cleanup(self):
+ # When statement is prepared, it is kept in case the next execute uses the same statement. This must be
+ # removed when a non-execute statement is used that returns results, such as SQLTables.
+
+ self.cursor.execute("select top 1 name from sysobjects where name = ?", "bogus")
+ self.cursor.fetchone()
+
+ self.cursor.tables("bogus")
+
+ self.cursor.execute("select top 1 name from sysobjects where name = ?", "bogus")
+ self.cursor.fetchone()
def main():
View
@@ -1297,6 +1297,18 @@ def test_drivers(self):
current = m.group(1)
self.assert_(current in drivers)
+ def test_prepare_cleanup(self):
+ # When statement is prepared, it is kept in case the next execute uses the same statement. This must be
+ # removed when a non-execute statement is used that returns results, such as SQLTables.
+
+ self.cursor.execute("select top 1 name from sysobjects where name = ?", "bogus")
+ self.cursor.fetchone()
+
+ self.cursor.tables("bogus")
+
+ self.cursor.execute("select top 1 name from sysobjects where name = ?", "bogus")
+ self.cursor.fetchone()
+
def main():

0 comments on commit dc35242

Please sign in to comment.