Skip to content

Commit

Permalink
Add Cursor.fetchval(). While testing, fixed incorrect statement encod…
Browse files Browse the repository at this point in the history
…ing.

I had not converted all SQL statements properly.
  • Loading branch information
mkleehammer committed Dec 9, 2016
1 parent ee23209 commit 36e88df
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 10 deletions.
11 changes: 11 additions & 0 deletions docs/api-cursor.html
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,17 @@ <h1 id="cursor_executemany">executemany</h1>
<p>This will execute the SQL statement twice, once with <code>('A', 1)</code>
and once with <code>('B', 2)</code>.</p>

<h1 id="cursor_fetchval">fetchval</h1>

<pre>cursor.fetchval() --> value or None</pre>

<p>Returns the first column value from the next row or None if no more rows are available:</p>

<pre>
cursor.execute("select count(*) from users")
c = cursor.fetchval()
</pre>

<h1 id="cursor_fetchone">fetchone</h1>

<pre>cursor.fetchone() --> Row or None</pre>
Expand Down
3 changes: 2 additions & 1 deletion src/connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
#include "cnxninfo.h"
#include "sqlwchar.h"

#if PY_MAJOR_VERSION < 3
static bool IsStringType(PyObject* t) { return (void*)t == (void*)&PyString_Type; }
static bool IsUnicodeType(PyObject* t) { return (void*)t == (void*)&PyUnicode_Type; }

#endif

static char connection_doc[] =
"Connection objects manage connections to the database.\n"
Expand Down
37 changes: 33 additions & 4 deletions src/cursor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,10 @@ static PyObject* execute(Cursor* cur, PyObject* pSql, PyObject* params, bool ski
if (!query)
return 0;
Py_BEGIN_ALLOW_THREADS
ret = SQLExecDirect(cur->hstmt, (SQLCHAR*)query.value(), (SQLINTEGER)query.len());
if (enc.ctype == SQL_C_WCHAR)
ret = SQLExecDirectW(cur->hstmt, (SQLWCHAR*)query.value(), (SQLINTEGER)query.len());
else
ret = SQLExecDirect(cur->hstmt, (SQLCHAR*)query.value(), (SQLINTEGER)query.len());
Py_END_ALLOW_THREADS
}
else
Expand All @@ -700,10 +703,10 @@ static PyObject* execute(Cursor* cur, PyObject* pSql, PyObject* params, bool ski
if (!query)
return 0;
Py_BEGIN_ALLOW_THREADS
if (enc.ctype == SQL_C_CHAR)
ret = SQLExecDirect(cur->hstmt, (SQLCHAR*)query.value(), (SQLINTEGER)query.len());
else
if (enc.ctype == SQL_C_WCHAR)
ret = SQLExecDirectW(cur->hstmt, (SQLWCHAR*)query.value(), (SQLINTEGER)query.len());
else
ret = SQLExecDirect(cur->hstmt, (SQLCHAR*)query.value(), (SQLINTEGER)query.len());
Py_END_ALLOW_THREADS
}
}
Expand Down Expand Up @@ -1142,6 +1145,25 @@ static PyObject* Cursor_iternext(PyObject* self)
return result;
}

static PyObject* Cursor_fetchval(PyObject* self, PyObject* args)
{
UNUSED(args);

Cursor* cursor = Cursor_Validate(self, CURSOR_REQUIRE_RESULTS | CURSOR_RAISE_ERROR);
if (!cursor)
return 0;

Object row(Cursor_fetch(cursor));

if (!row)
{
if (PyErr_Occurred())
return 0;
Py_RETURN_NONE;
}

return Row_item(row, 0);
}

static PyObject* Cursor_fetchone(PyObject* self, PyObject* args)
{
Expand Down Expand Up @@ -2088,6 +2110,12 @@ static char nextset_doc[] = "nextset() --> True | None\n" \

static char ignored_doc[] = "Ignored.";

static char fetchval_doc[] =
"fetchval() --> value | None\n" \
"\n"
"Returns the first column of the next row in the result set or None\n" \
"if there are no more rows.";

static char fetchone_doc[] =
"fetchone() --> Row | None\n" \
"\n" \
Expand Down Expand Up @@ -2162,6 +2190,7 @@ static PyMethodDef Cursor_methods[] =
{ "executemany", (PyCFunction)Cursor_executemany, METH_VARARGS, executemany_doc },
{ "setinputsizes", (PyCFunction)Cursor_ignored, METH_VARARGS, ignored_doc },
{ "setoutputsize", (PyCFunction)Cursor_ignored, METH_VARARGS, ignored_doc },
{ "fetchval", (PyCFunction)Cursor_fetchval, METH_NOARGS, fetchval_doc },
{ "fetchone", (PyCFunction)Cursor_fetchone, METH_NOARGS, fetchone_doc },
{ "fetchall", (PyCFunction)Cursor_fetchall, METH_NOARGS, fetchall_doc },
{ "fetchmany", (PyCFunction)Cursor_fetchmany, METH_VARARGS, fetchmany_doc },
Expand Down
10 changes: 8 additions & 2 deletions src/params.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,10 @@ bool PrepareAndBind(Cursor* cur, PyObject* pSql, PyObject* original_params, bool
const TextEnc& enc = cur->cnxn->unicode_enc;
SQLWChar sql(pSql, 0, enc.name);
Py_BEGIN_ALLOW_THREADS
ret = SQLPrepareW(cur->hstmt, (SQLWCHAR*)sql.value(), (SQLINTEGER)sql.len());
if (enc.ctype == SQL_C_WCHAR)
ret = SQLPrepareW(cur->hstmt, (SQLWCHAR*)sql.value(), (SQLINTEGER)sql.len());
else
ret = SQLPrepare(cur->hstmt, (SQLCHAR*)sql.value(), (SQLINTEGER)sql.len());
if (SQL_SUCCEEDED(ret))
{
szErrorFunc = "SQLNumParams";
Expand All @@ -761,7 +764,10 @@ bool PrepareAndBind(Cursor* cur, PyObject* pSql, PyObject* original_params, bool
SQLWChar sql(pSql, 0, enc.name);
TRACE("SQLPrepare(%s)\n", PyString_AS_STRING(pSql));
Py_BEGIN_ALLOW_THREADS
ret = SQLPrepare(cur->hstmt, (SQLCHAR*)PyString_AS_STRING(pSql), SQL_NTS);
if (enc.ctype == SQL_C_WCHAR)
ret = SQLPrepareW(cur->hstmt, (SQLWCHAR*)sql.value(), (SQLINTEGER)sql.len());
else
ret = SQLPrepare(cur->hstmt, (SQLCHAR*)sql.value(), (SQLINTEGER)sql.len());
if (SQL_SUCCEEDED(ret))
{
szErrorFunc = "SQLNumParams";
Expand Down
3 changes: 1 addition & 2 deletions src/row.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,7 @@ static int Row_contains(PyObject* o, PyObject* el)
return cmp;
}


static PyObject* Row_item(PyObject* o, Py_ssize_t i)
PyObject* Row_item(PyObject* o, Py_ssize_t i)
{
// Apparently, negative indexes are handled by magic ;) -- they never make it here.

Expand Down
2 changes: 2 additions & 0 deletions src/row.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Row* Row_InternalNew(PyObject* description, PyObject* map_name_to_index, Py_ssiz
*/
void FreeRowValues(Py_ssize_t cValues, PyObject** apValues);

PyObject* Row_item(PyObject* o, Py_ssize_t i);

extern PyTypeObject RowType;
#define Row_Check(op) PyObject_TypeCheck(op, &RowType)
#define Row_CheckExact(op) (Py_TYPE(op) == &RowType)
Expand Down
2 changes: 1 addition & 1 deletion tests2/mysqltests.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def setUp(self):

self.cnxn.setdecoding(pyodbc.SQL_WCHAR, encoding='utf-8')
self.cnxn.setencoding(str, encoding='utf-8')
self.cnxn.setencoding(unicode, encoding='utf-8')
self.cnxn.setencoding(unicode, encoding='utf-8', ctype=pyodbc.SQL_CHAR)

# As of libmyodbc5w 5.3 SQLGetTypeInfo returns absurdly small sizes
# leading to slow writes. Override them:
Expand Down
6 changes: 6 additions & 0 deletions tests3/mysqltests.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ def setUp(self):
# leading to slow writes. Override them:
self.cnxn.maxwrite = 1024 * 1024 * 1024

# My MySQL configuration (and I think the default) sends *everything*
# in UTF-8. The pyodbc default is to send Unicode as UTF-16 and to
# decode WCHAR via UTF-16. Change them both to UTF-8.
self.cnxn.setdecoding(pyodbc.SQL_WCHAR, encoding='utf-8')
self.cnxn.setencoding(encoding='utf-8')

for i in range(3):
try:
self.cursor.execute("drop table t%d" % i)
Expand Down
7 changes: 7 additions & 0 deletions tests3/pgtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,13 @@ def test_fixed_str(self):
self.assertEqual(len(v), len(value)) # If we alloc'd wrong, the test below might work because of an embedded NULL
self.assertEqual(v, value)

def test_fetchval(self):
expected = "test"
self.cursor.execute("create table t1(s varchar(20))")
self.cursor.execute("insert into t1 values(?)", expected)
result = self.cursor.execute("select * from t1").fetchval()
self.assertEqual(result, expected)

def test_negative_row_index(self):
self.cursor.execute("create table t1(s varchar(20))")
self.cursor.execute("insert into t1 values(?)", "1")
Expand Down

0 comments on commit 36e88df

Please sign in to comment.