mirror of
https://github.com/psycopg/psycopg2.git
synced 2025-01-31 17:34:08 +03:00
Added support with cursors without scroll clause
Using nothing is different from NO SCROLL, see DECLARE notes in PG docs.
This commit is contained in:
parent
d074b096be
commit
a79a5292e7
|
@ -52,75 +52,70 @@
|
|||
static PyObject *
|
||||
psyco_conn_cursor(connectionObject *self, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
PyObject *obj;
|
||||
PyObject *obj = NULL;
|
||||
PyObject *rv = NULL;
|
||||
PyObject *name = Py_None;
|
||||
PyObject *factory = (PyObject *)&cursorType;
|
||||
PyObject *withhold = Py_False;
|
||||
PyObject *scrollable = Py_False;
|
||||
PyObject *scrollable = Py_None;
|
||||
|
||||
static char *kwlist[] = {
|
||||
"name", "cursor_factory", "withhold", "scrollable", NULL};
|
||||
|
||||
EXC_IF_CONN_CLOSED(self);
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(
|
||||
args, kwargs, "|OOOO", kwlist,
|
||||
&name, &factory, &withhold, &scrollable)) {
|
||||
return NULL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (PyObject_IsTrue(withhold) && (name == Py_None)) {
|
||||
PyErr_SetString(ProgrammingError,
|
||||
"'withhold=True can be specified only for named cursors");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (PyObject_IsTrue(scrollable) && (name == Py_None)) {
|
||||
PyErr_SetString(ProgrammingError,
|
||||
"'scrollable=True can be specified only for named cursors");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
EXC_IF_CONN_CLOSED(self);
|
||||
|
||||
if (self->status != CONN_STATUS_READY &&
|
||||
self->status != CONN_STATUS_BEGIN &&
|
||||
self->status != CONN_STATUS_PREPARED) {
|
||||
PyErr_SetString(OperationalError,
|
||||
"asynchronous connection attempt underway");
|
||||
return NULL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (name != Py_None && self->async == 1) {
|
||||
PyErr_SetString(ProgrammingError,
|
||||
"asynchronous connections "
|
||||
"cannot produce named cursors");
|
||||
return NULL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
Dprintf("psyco_conn_cursor: new %s cursor for connection at %p",
|
||||
(name == Py_None ? "unnamed" : "named"), self);
|
||||
|
||||
if (!(obj = PyObject_CallFunctionObjArgs(factory, self, name, NULL))) {
|
||||
return NULL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (PyObject_IsInstance(obj, (PyObject *)&cursorType) == 0) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"cursor factory must be subclass of psycopg2._psycopg.cursor");
|
||||
Py_DECREF(obj);
|
||||
return NULL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (PyObject_IsTrue(withhold))
|
||||
((cursorObject*)obj)->withhold = 1;
|
||||
|
||||
if (PyObject_IsTrue(scrollable))
|
||||
((cursorObject*)obj)->scrollable = 1;
|
||||
if (0 != psyco_curs_withhold_set((cursorObject *)obj, withhold)) {
|
||||
goto exit;
|
||||
}
|
||||
if (0 != psyco_curs_scrollable_set((cursorObject *)obj, scrollable)) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
Dprintf("psyco_conn_cursor: new cursor at %p: refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
obj, Py_REFCNT(obj)
|
||||
);
|
||||
return obj;
|
||||
);
|
||||
|
||||
rv = obj;
|
||||
obj = NULL;
|
||||
|
||||
exit:
|
||||
Py_XDECREF(obj);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -43,7 +43,11 @@ struct cursorObject {
|
|||
int closed:1; /* 1 if the cursor is closed */
|
||||
int notuples:1; /* 1 if the command was not a SELECT query */
|
||||
int withhold:1; /* 1 if the cursor is named and uses WITH HOLD */
|
||||
int scrollable:1; /* 1 if the cursor is named as SCROLLABLE */
|
||||
|
||||
int scrollable; /* 1 if the cursor is named and SCROLLABLE,
|
||||
0 if not scrollable
|
||||
-1 if undefined (PG may decide scrollable or not)
|
||||
*/
|
||||
|
||||
long int rowcount; /* number of rows affected by last execute */
|
||||
long int columns; /* number of columns fetched from the db */
|
||||
|
@ -85,9 +89,11 @@ struct cursorObject {
|
|||
};
|
||||
|
||||
|
||||
/* C-callable functions in cursor_int.c and cursor_ext.c */
|
||||
/* C-callable functions in cursor_int.c and cursor_type.c */
|
||||
BORROWED HIDDEN PyObject *curs_get_cast(cursorObject *self, PyObject *oid);
|
||||
HIDDEN void curs_reset(cursorObject *self);
|
||||
HIDDEN int psyco_curs_withhold_set(cursorObject *self, PyObject *pyvalue);
|
||||
HIDDEN int psyco_curs_scrollable_set(cursorObject *self, PyObject *pyvalue);
|
||||
|
||||
/* exception-raising macros */
|
||||
#define EXC_IF_CURS_CLOSED(self) \
|
||||
|
|
|
@ -370,6 +370,7 @@ _psyco_curs_execute(cursorObject *self,
|
|||
int res = -1;
|
||||
int tmp;
|
||||
PyObject *fquery, *cvt = NULL;
|
||||
const char *scroll;
|
||||
|
||||
operation = _psyco_curs_validate_sql_basic(self, operation);
|
||||
|
||||
|
@ -396,6 +397,21 @@ _psyco_curs_execute(cursorObject *self,
|
|||
if (0 > _mogrify(vars, operation, self, &cvt)) { goto exit; }
|
||||
}
|
||||
|
||||
switch (self->scrollable) {
|
||||
case -1:
|
||||
scroll = "";
|
||||
break;
|
||||
case 0:
|
||||
scroll = "NO SCROLL ";
|
||||
break;
|
||||
case 1:
|
||||
scroll = "SCROLL ";
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(InternalError, "unexpected scrollable value");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (vars && cvt) {
|
||||
if (!(fquery = _psyco_curs_merge_query_args(self, operation, cvt))) {
|
||||
goto exit;
|
||||
|
@ -403,9 +419,9 @@ _psyco_curs_execute(cursorObject *self,
|
|||
|
||||
if (self->name != NULL) {
|
||||
self->query = Bytes_FromFormat(
|
||||
"DECLARE \"%s\" %sSCROLL CURSOR %s HOLD FOR %s",
|
||||
"DECLARE \"%s\" %sCURSOR %s HOLD FOR %s",
|
||||
self->name,
|
||||
self->scrollable ? "":"NO ",
|
||||
scroll,
|
||||
self->withhold ? "WITH" : "WITHOUT",
|
||||
Bytes_AS_STRING(fquery));
|
||||
Py_DECREF(fquery);
|
||||
|
@ -417,9 +433,9 @@ _psyco_curs_execute(cursorObject *self,
|
|||
else {
|
||||
if (self->name != NULL) {
|
||||
self->query = Bytes_FromFormat(
|
||||
"DECLARE \"%s\" %sSCROLL CURSOR %s HOLD FOR %s",
|
||||
"DECLARE \"%s\" %sCURSOR %s HOLD FOR %s",
|
||||
self->name,
|
||||
self->scrollable ? "":"NO ",
|
||||
scroll,
|
||||
self->withhold ? "WITH" : "WITHOUT",
|
||||
Bytes_AS_STRING(operation));
|
||||
}
|
||||
|
@ -1567,12 +1583,12 @@ psyco_curs_withhold_get(cursorObject *self)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
int
|
||||
psyco_curs_withhold_set(cursorObject *self, PyObject *pyvalue)
|
||||
{
|
||||
int value;
|
||||
|
||||
if (self->name == NULL) {
|
||||
if (pyvalue != Py_False && self->name == NULL) {
|
||||
PyErr_SetString(ProgrammingError,
|
||||
"trying to set .withhold on unnamed cursor");
|
||||
return -1;
|
||||
|
@ -1592,25 +1608,42 @@ psyco_curs_withhold_set(cursorObject *self, PyObject *pyvalue)
|
|||
static PyObject *
|
||||
psyco_curs_scrollable_get(cursorObject *self)
|
||||
{
|
||||
PyObject *ret;
|
||||
ret = self->scrollable ? Py_True : Py_False;
|
||||
Py_INCREF(ret);
|
||||
PyObject *ret = NULL;
|
||||
|
||||
switch (self->scrollable) {
|
||||
case -1:
|
||||
ret = Py_None;
|
||||
break;
|
||||
case 0:
|
||||
ret = Py_False;
|
||||
break;
|
||||
case 1:
|
||||
ret = Py_True;
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(InternalError, "unexpected scrollable value");
|
||||
}
|
||||
|
||||
Py_XINCREF(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
int
|
||||
psyco_curs_scrollable_set(cursorObject *self, PyObject *pyvalue)
|
||||
{
|
||||
int value;
|
||||
|
||||
if (self->name == NULL) {
|
||||
if (pyvalue != Py_None && self->name == NULL) {
|
||||
PyErr_SetString(ProgrammingError,
|
||||
"trying to set .scrollable on unnamed cursor");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((value = PyObject_IsTrue(pyvalue)) == -1)
|
||||
if (pyvalue == Py_None) {
|
||||
value = -1;
|
||||
} else if ((value = PyObject_IsTrue(pyvalue)) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
self->scrollable = value;
|
||||
|
||||
|
|
|
@ -226,7 +226,7 @@ class CursorTests(unittest.TestCase):
|
|||
for t in range(2):
|
||||
if not t:
|
||||
curs = self.conn.cursor("S")
|
||||
self.assertEqual(curs.scrollable, False);
|
||||
self.assertEqual(curs.scrollable, None);
|
||||
curs.scrollable = True
|
||||
else:
|
||||
curs = self.conn.cursor("S", scrollable=True)
|
||||
|
@ -252,6 +252,32 @@ class CursorTests(unittest.TestCase):
|
|||
|
||||
curs.close()
|
||||
|
||||
def test_not_scrollable(self):
|
||||
self.assertRaises(psycopg2.ProgrammingError, self.conn.cursor,
|
||||
scrollable=False)
|
||||
|
||||
curs = self.conn.cursor()
|
||||
curs.execute("create table scrollable (data int)")
|
||||
curs.executemany("insert into scrollable values (%s)",
|
||||
[ (i,) for i in range(100) ])
|
||||
curs.close()
|
||||
|
||||
curs = self.conn.cursor("S") # default scrollability
|
||||
curs.execute("select * from scrollable")
|
||||
self.assertEqual(curs.scrollable, None)
|
||||
curs.scroll(2)
|
||||
try:
|
||||
curs.scroll(-1)
|
||||
except psycopg2.OperationalError:
|
||||
return self.skipTest("can't evaluate non-scrollable cursor")
|
||||
curs.close()
|
||||
|
||||
curs = self.conn.cursor("S", scrollable=False)
|
||||
self.assertEqual(curs.scrollable, False)
|
||||
curs.execute("select * from scrollable")
|
||||
curs.scroll(2)
|
||||
self.assertRaises(psycopg2.OperationalError, curs.scroll, -1)
|
||||
|
||||
@skip_before_postgres(8, 2)
|
||||
def test_iter_named_cursor_efficient(self):
|
||||
curs = self.conn.cursor('tmp')
|
||||
|
|
Loading…
Reference in New Issue
Block a user