summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGaëtan de Menten <gdementen@gmail.com>2010-02-13 22:53:39 +0000
committerGaëtan de Menten <gdementen@gmail.com>2010-02-13 22:53:39 +0000
commit165609a190665f5453417c9c935a834714c7f5a5 (patch)
tree90d3d0da3f233cf6fc211f367eea0dba661b098e
parentf2974ef3993e02646a2dfade5feb74afb78f370f (diff)
downloadsqlalchemy-165609a190665f5453417c9c935a834714c7f5a5.tar.gz
- Added an optional C extension to speed up the sql layer by
reimplementing the highest impact functions. The actual speedups will depend heavily on your DBAPI and the mix of datatypes used in your tables, and can vary from a 50% improvement to more than 200%. It also provides a modest (~20%) indirect improvement to ORM speed for large queries. Note that it is *not* built/installed by default. See README for installation instructions. - The most common result processors conversion function were moved to the new "processors" module. Dialect authors are encouraged to use those functions whenever they correspond to their needs instead of implementing custom ones.
-rw-r--r--CHANGES14
-rw-r--r--README9
-rw-r--r--lib/sqlalchemy/cextension/processors.c384
-rw-r--r--lib/sqlalchemy/cextension/resultproxy.c586
-rw-r--r--lib/sqlalchemy/dialects/access/base.py45
-rw-r--r--lib/sqlalchemy/dialects/informix/base.py2
-rw-r--r--lib/sqlalchemy/dialects/maxdb/base.py122
-rw-r--r--lib/sqlalchemy/dialects/mssql/base.py17
-rw-r--r--lib/sqlalchemy/dialects/mysql/base.py14
-rw-r--r--lib/sqlalchemy/dialects/mysql/mysqldb.py8
-rw-r--r--lib/sqlalchemy/dialects/mysql/oursql.py8
-rw-r--r--lib/sqlalchemy/dialects/oracle/zxjdbc.py3
-rw-r--r--lib/sqlalchemy/dialects/postgresql/pg8000.py25
-rw-r--r--lib/sqlalchemy/dialects/postgresql/psycopg2.py19
-rw-r--r--lib/sqlalchemy/dialects/postgresql/pypostgresql.py8
-rw-r--r--lib/sqlalchemy/dialects/sqlite/base.py44
-rw-r--r--lib/sqlalchemy/dialects/sybase/base.py19
-rw-r--r--lib/sqlalchemy/engine/base.py382
-rw-r--r--lib/sqlalchemy/processors.py90
-rw-r--r--lib/sqlalchemy/test/profiling.py9
-rw-r--r--lib/sqlalchemy/types.py42
-rw-r--r--setup.py12
-rw-r--r--test/aaa_profiling/test_resultset.py4
-rw-r--r--test/aaa_profiling/test_zoomark.py4
-rw-r--r--test/perf/stress_all.py226
-rw-r--r--test/perf/stresstest.py174
-rw-r--r--test/sql/test_query.py12
27 files changed, 1880 insertions, 402 deletions
diff --git a/CHANGES b/CHANGES
index cdf7e71f5..7b67dc705 100644
--- a/CHANGES
+++ b/CHANGES
@@ -59,6 +59,20 @@ CHANGES
[ticket:1689]
- sql
+ - Added an optional C extension to speed up the sql layer by
+ reimplementing RowProxy and the most common result processors.
+ The actual speedups will depend heavily on your DBAPI and
+ the mix of datatypes used in your tables, and can vary from
+ a 50% improvement to more than 200%. It also provides a modest
+ (~20%) indirect improvement to ORM speed for large queries.
+ Note that it is *not* built/installed by default.
+ See README for installation instructions.
+
+ - The most common result processors conversion function were
+ moved to the new "processors" module. Dialect authors are
+ encouraged to use those functions whenever they correspond
+ to their needs instead of implementing custom ones.
+
- Added math negation operator support, -x.
- FunctionElement subclasses are now directly executable the
diff --git a/README b/README
index 7caaf2723..4bbdd20a1 100644
--- a/README
+++ b/README
@@ -35,6 +35,15 @@ To install::
To use without installation, include the ``lib`` directory in your Python
path.
+Installing the C extension
+--------------------------
+
+Edit "setup.py" and set ``BUILD_CEXTENSIONS`` to ``True``, then install it as
+above. If you want only to build the extension and not install it, you can do
+so with::
+
+ python setup.py build
+
Running Tests
-------------
diff --git a/lib/sqlalchemy/cextension/processors.c b/lib/sqlalchemy/cextension/processors.c
new file mode 100644
index 000000000..23b7be4f2
--- /dev/null
+++ b/lib/sqlalchemy/cextension/processors.c
@@ -0,0 +1,384 @@
+/*
+processors.c
+Copyright (C) 2010 Gaetan de Menten gdementen@gmail.com
+
+This module is part of SQLAlchemy and is released under
+the MIT License: http://www.opensource.org/licenses/mit-license.php
+*/
+
+#include <Python.h>
+#include <datetime.h>
+
+static PyObject *
+int_to_boolean(PyObject *self, PyObject *arg)
+{
+ long l = 0;
+ PyObject *res;
+
+ if (arg == Py_None)
+ Py_RETURN_NONE;
+
+ l = PyInt_AsLong(arg);
+ if (l == 0) {
+ res = Py_False;
+ } else if (l == 1) {
+ res = Py_True;
+ } else if ((l == -1) && PyErr_Occurred()) {
+ /* -1 can be either the actual value, or an error flag. */
+ return NULL;
+ } else {
+ PyErr_SetString(PyExc_ValueError,
+ "int_to_boolean only accepts None, 0 or 1");
+ return NULL;
+ }
+
+ Py_INCREF(res);
+ return res;
+}
+
+static PyObject *
+to_str(PyObject *self, PyObject *arg)
+{
+ if (arg == Py_None)
+ Py_RETURN_NONE;
+
+ return PyObject_Str(arg);
+}
+
+static PyObject *
+to_float(PyObject *self, PyObject *arg)
+{
+ if (arg == Py_None)
+ Py_RETURN_NONE;
+
+ return PyNumber_Float(arg);
+}
+
+static PyObject *
+str_to_datetime(PyObject *self, PyObject *arg)
+{
+ const char *str;
+ unsigned int year, month, day, hour, minute, second, microsecond = 0;
+
+ if (arg == Py_None)
+ Py_RETURN_NONE;
+
+ str = PyString_AsString(arg);
+ if (str == NULL)
+ return NULL;
+
+ /* microseconds are optional */
+ /*
+ TODO: this is slightly less picky than the Python version which would
+ not accept "2000-01-01 00:00:00.". I don't know which is better, but they
+ should be coherent.
+ */
+ if (sscanf(str, "%4u-%2u-%2u %2u:%2u:%2u.%6u", &year, &month, &day,
+ &hour, &minute, &second, &microsecond) < 6) {
+ PyErr_SetString(PyExc_ValueError, "Couldn't parse datetime string.");
+ return NULL;
+ }
+ return PyDateTime_FromDateAndTime(year, month, day,
+ hour, minute, second, microsecond);
+}
+
+static PyObject *
+str_to_time(PyObject *self, PyObject *arg)
+{
+ const char *str;
+ unsigned int hour, minute, second, microsecond = 0;
+
+ if (arg == Py_None)
+ Py_RETURN_NONE;
+
+ str = PyString_AsString(arg);
+ if (str == NULL)
+ return NULL;
+
+ /* microseconds are optional */
+ /*
+ TODO: this is slightly less picky than the Python version which would
+ not accept "00:00:00.". I don't know which is better, but they should be
+ coherent.
+ */
+ if (sscanf(str, "%2u:%2u:%2u.%6u", &hour, &minute, &second,
+ &microsecond) < 3) {
+ PyErr_SetString(PyExc_ValueError, "Couldn't parse time string.");
+ return NULL;
+ }
+ return PyTime_FromTime(hour, minute, second, microsecond);
+}
+
+static PyObject *
+str_to_date(PyObject *self, PyObject *arg)
+{
+ const char *str;
+ unsigned int year, month, day;
+
+ if (arg == Py_None)
+ Py_RETURN_NONE;
+
+ str = PyString_AsString(arg);
+ if (str == NULL)
+ return NULL;
+
+ if (sscanf(str, "%4u-%2u-%2u", &year, &month, &day) != 3) {
+ PyErr_SetString(PyExc_ValueError, "Couldn't parse date string.");
+ return NULL;
+ }
+ return PyDate_FromDate(year, month, day);
+}
+
+
+/***********
+ * Structs *
+ ***********/
+
+typedef struct {
+ PyObject_HEAD
+ PyObject *encoding;
+ PyObject *errors;
+} UnicodeResultProcessor;
+
+typedef struct {
+ PyObject_HEAD
+ PyObject *type;
+} DecimalResultProcessor;
+
+
+
+/**************************
+ * UnicodeResultProcessor *
+ **************************/
+
+static int
+UnicodeResultProcessor_init(UnicodeResultProcessor *self, PyObject *args,
+ PyObject *kwds)
+{
+ PyObject *encoding, *errors;
+ static char *kwlist[] = {"encoding", "errors", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "S|S:init", kwlist,
+ &encoding, &errors))
+ return -1;
+
+ Py_INCREF(encoding);
+ self->encoding = encoding;
+
+ if (errors) {
+ Py_INCREF(errors);
+ } else {
+ errors = PyString_FromString("strict");
+ if (errors == NULL)
+ return -1;
+ }
+ self->errors = errors;
+
+ return 0;
+}
+
+static PyObject *
+UnicodeResultProcessor_process(UnicodeResultProcessor *self, PyObject *value)
+{
+ const char *encoding, *errors;
+ char *str;
+ Py_ssize_t len;
+
+ if (value == Py_None)
+ Py_RETURN_NONE;
+
+ if (PyString_AsStringAndSize(value, &str, &len))
+ return NULL;
+
+ encoding = PyString_AS_STRING(self->encoding);
+ errors = PyString_AS_STRING(self->errors);
+
+ return PyUnicode_Decode(str, len, encoding, errors);
+}
+
+static PyMethodDef UnicodeResultProcessor_methods[] = {
+ {"process", (PyCFunction)UnicodeResultProcessor_process, METH_O,
+ "The value processor itself."},
+ {NULL} /* Sentinel */
+};
+
+static PyTypeObject UnicodeResultProcessorType = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /* ob_size */
+ "sqlalchemy.cprocessors.UnicodeResultProcessor", /* tp_name */
+ sizeof(UnicodeResultProcessor), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ "UnicodeResultProcessor objects", /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ UnicodeResultProcessor_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)UnicodeResultProcessor_init, /* tp_init */
+ 0, /* tp_alloc */
+ 0, /* tp_new */
+};
+
+/**************************
+ * DecimalResultProcessor *
+ **************************/
+
+static int
+DecimalResultProcessor_init(DecimalResultProcessor *self, PyObject *args,
+ PyObject *kwds)
+{
+ PyObject *type;
+
+ if (!PyArg_ParseTuple(args, "O", &type))
+ return -1;
+
+ Py_INCREF(type);
+ self->type = type;
+
+ return 0;
+}
+
+static PyObject *
+DecimalResultProcessor_process(DecimalResultProcessor *self, PyObject *value)
+{
+ PyObject *str, *result;
+
+ if (value == Py_None)
+ Py_RETURN_NONE;
+
+ if (PyFloat_CheckExact(value)) {
+ /* Decimal does not accept float values directly */
+ str = PyObject_Str(value);
+ if (str == NULL)
+ return NULL;
+ result = PyObject_CallFunctionObjArgs(self->type, str, NULL);
+ Py_DECREF(str);
+ return result;
+ } else {
+ return PyObject_CallFunctionObjArgs(self->type, value, NULL);
+ }
+}
+
+static PyMethodDef DecimalResultProcessor_methods[] = {
+ {"process", (PyCFunction)DecimalResultProcessor_process, METH_O,
+ "The value processor itself."},
+ {NULL} /* Sentinel */
+};
+
+static PyTypeObject DecimalResultProcessorType = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /* ob_size */
+ "sqlalchemy.DecimalResultProcessor", /* tp_name */
+ sizeof(DecimalResultProcessor), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ "DecimalResultProcessor objects", /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ DecimalResultProcessor_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)DecimalResultProcessor_init, /* tp_init */
+ 0, /* tp_alloc */
+ 0, /* tp_new */
+};
+
+#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
+#define PyMODINIT_FUNC void
+#endif
+
+
+static PyMethodDef module_methods[] = {
+ {"int_to_boolean", int_to_boolean, METH_O,
+ "Convert an integer to a boolean."},
+ {"to_str", to_str, METH_O,
+ "Convert any value to its string representation."},
+ {"to_float", to_float, METH_O,
+ "Convert any value to its floating point representation."},
+ {"str_to_datetime", str_to_datetime, METH_O,
+ "Convert an ISO string to a datetime.datetime object."},
+ {"str_to_time", str_to_time, METH_O,
+ "Convert an ISO string to a datetime.time object."},
+ {"str_to_date", str_to_date, METH_O,
+ "Convert an ISO string to a datetime.date object."},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
+
+PyMODINIT_FUNC
+initcprocessors(void)
+{
+ PyObject *m;
+
+ UnicodeResultProcessorType.tp_new = PyType_GenericNew;
+ if (PyType_Ready(&UnicodeResultProcessorType) < 0)
+ return;
+
+ DecimalResultProcessorType.tp_new = PyType_GenericNew;
+ if (PyType_Ready(&DecimalResultProcessorType) < 0)
+ return;
+
+ m = Py_InitModule3("cprocessors", module_methods,
+ "Module containing C versions of data processing functions.");
+ if (m == NULL)
+ return;
+
+ PyDateTime_IMPORT;
+
+ Py_INCREF(&UnicodeResultProcessorType);
+ PyModule_AddObject(m, "UnicodeResultProcessor",
+ (PyObject *)&UnicodeResultProcessorType);
+
+ Py_INCREF(&DecimalResultProcessorType);
+ PyModule_AddObject(m, "DecimalResultProcessor",
+ (PyObject *)&DecimalResultProcessorType);
+}
+
diff --git a/lib/sqlalchemy/cextension/resultproxy.c b/lib/sqlalchemy/cextension/resultproxy.c
new file mode 100644
index 000000000..14ea1828e
--- /dev/null
+++ b/lib/sqlalchemy/cextension/resultproxy.c
@@ -0,0 +1,586 @@
+/*
+resultproxy.c
+Copyright (C) 2010 Gaetan de Menten gdementen@gmail.com
+
+This module is part of SQLAlchemy and is released under
+the MIT License: http://www.opensource.org/licenses/mit-license.php
+*/
+
+#include <Python.h>
+
+
+/***********
+ * Structs *
+ ***********/
+
+typedef struct {
+ PyObject_HEAD
+ PyObject *parent;
+ PyObject *row;
+ PyObject *processors;
+ PyObject *keymap;
+} BaseRowProxy;
+
+/****************
+ * BaseRowProxy *
+ ****************/
+
+static PyObject *
+rowproxy_reconstructor(PyObject *self, PyObject *args)
+{
+ PyObject *cls, *state, *tmp;
+ BaseRowProxy *obj;
+
+ if (!PyArg_ParseTuple(args, "OO", &cls, &state))
+ return NULL;
+
+ obj = (BaseRowProxy *)PyObject_CallMethod(cls, "__new__", "O", cls);
+ if (obj == NULL)
+ return NULL;
+
+ tmp = PyObject_CallMethod((PyObject *)obj, "__setstate__", "O", state);
+ if (tmp == NULL) {
+ Py_DECREF(obj);
+ return NULL;
+ }
+ Py_DECREF(tmp);
+
+ if (obj->parent == NULL || obj->row == NULL ||
+ obj->processors == NULL || obj->keymap == NULL) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "__setstate__ for BaseRowProxy subclasses must set values "
+ "for parent, row, processors and keymap");
+ Py_DECREF(obj);
+ return NULL;
+ }
+
+ return (PyObject *)obj;
+}
+
+static int
+BaseRowProxy_init(BaseRowProxy *self, PyObject *args, PyObject *kwds)
+{
+ PyObject *parent, *row, *processors, *keymap;
+
+ if (!PyArg_UnpackTuple(args, "BaseRowProxy", 4, 4,
+ &parent, &row, &processors, &keymap))
+ return -1;
+
+ Py_INCREF(parent);
+ self->parent = parent;
+
+ if (!PyTuple_CheckExact(row)) {
+ PyErr_SetString(PyExc_TypeError, "row must be a tuple");
+ return -1;
+ }
+ Py_INCREF(row);
+ self->row = row;
+
+ if (!PyList_CheckExact(processors)) {
+ PyErr_SetString(PyExc_TypeError, "processors must be a list");
+ return -1;
+ }
+ Py_INCREF(processors);
+ self->processors = processors;
+
+ if (!PyDict_CheckExact(keymap)) {
+ PyErr_SetString(PyExc_TypeError, "keymap must be a dict");
+ return -1;
+ }
+ Py_INCREF(keymap);
+ self->keymap = keymap;
+
+ return 0;
+}
+
+/* We need the reduce method because otherwise the default implementation
+ * does very weird stuff for pickle protocol 0 and 1. It calls
+ * BaseRowProxy.__new__(RowProxy_instance) upon *pickling*.
+ */
+static PyObject *
+BaseRowProxy_reduce(PyObject *self)
+{
+ PyObject *method, *state;
+ PyObject *module, *reconstructor, *cls;
+
+ method = PyObject_GetAttrString(self, "__getstate__");
+ if (method == NULL)
+ return NULL;
+
+ state = PyObject_CallObject(method, NULL);
+ Py_DECREF(method);
+ if (state == NULL)
+ return NULL;
+
+ module = PyImport_ImportModule("sqlalchemy.engine.base");
+ if (module == NULL)
+ return NULL;
+
+ reconstructor = PyObject_GetAttrString(module, "rowproxy_reconstructor");
+ Py_DECREF(module);
+ if (reconstructor == NULL) {
+ Py_DECREF(state);
+ return NULL;
+ }
+
+ cls = PyObject_GetAttrString(self, "__class__");
+ if (cls == NULL) {
+ Py_DECREF(reconstructor);
+ Py_DECREF(state);
+ return NULL;
+ }
+
+ return Py_BuildValue("(N(NN))", reconstructor, cls, state);
+}
+
+static void
+BaseRowProxy_dealloc(BaseRowProxy *self)
+{
+ Py_XDECREF(self->parent);
+ Py_XDECREF(self->row);
+ Py_XDECREF(self->processors);
+ Py_XDECREF(self->keymap);
+ self->ob_type->tp_free((PyObject *)self);
+}
+
+static PyObject *
+BaseRowProxy_processvalues(PyObject *values, PyObject *processors, int astuple)
+{
+ Py_ssize_t num_values, num_processors;
+ PyObject **valueptr, **funcptr, **resultptr;
+ PyObject *func, *result, *processed_value;
+
+ num_values = Py_SIZE(values);
+ num_processors = Py_SIZE(processors);
+ if (num_values != num_processors) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "number of values in row difer from number of column processors");
+ return NULL;
+ }
+
+ if (astuple) {
+ result = PyTuple_New(num_values);
+ } else {
+ result = PyList_New(num_values);
+ }
+ if (result == NULL)
+ return NULL;
+
+ /* we don't need to use PySequence_Fast as long as values, processors and
+ * result are simple tuple or lists. */
+ valueptr = PySequence_Fast_ITEMS(values);
+ funcptr = PySequence_Fast_ITEMS(processors);
+ resultptr = PySequence_Fast_ITEMS(result);
+ while (--num_values >= 0) {
+ func = *funcptr;
+ if (func != Py_None) {
+ processed_value = PyObject_CallFunctionObjArgs(func, *valueptr,
+ NULL);
+ if (processed_value == NULL) {
+ Py_DECREF(result);
+ return NULL;
+ }
+ *resultptr = processed_value;
+ } else {
+ Py_INCREF(*valueptr);
+ *resultptr = *valueptr;
+ }
+ valueptr++;
+ funcptr++;
+ resultptr++;
+ }
+ return result;
+}
+
+static PyListObject *
+BaseRowProxy_values(BaseRowProxy *self)
+{
+ return (PyListObject *)BaseRowProxy_processvalues(self->row,
+ self->processors, 0);
+}
+
+static PyTupleObject *
+BaseRowProxy_tuplevalues(BaseRowProxy *self)
+{
+ return (PyTupleObject *)BaseRowProxy_processvalues(self->row,
+ self->processors, 1);
+}
+
+static PyObject *
+BaseRowProxy_iter(BaseRowProxy *self)
+{
+ PyObject *values, *result;
+
+ values = (PyObject *)BaseRowProxy_tuplevalues(self);
+ if (values == NULL)
+ return NULL;
+
+ result = PyObject_GetIter(values);
+ Py_DECREF(values);
+ if (result == NULL)
+ return NULL;
+
+ return result;
+}
+
+static Py_ssize_t
+BaseRowProxy_length(BaseRowProxy *self)
+{
+ return Py_SIZE(self->row);
+}
+
+static PyObject *
+BaseRowProxy_subscript(BaseRowProxy *self, PyObject *key)
+{
+ PyObject *processors, *values;
+ PyObject *processor, *value;
+ PyObject *record, *result, *indexobject;
+ PyObject *exc_module, *exception;
+ char *cstr_key;
+ long index;
+
+ if (PyInt_CheckExact(key)) {
+ index = PyInt_AS_LONG(key);
+ } else if (PyLong_CheckExact(key)) {
+ index = PyLong_AsLong(key);
+ if ((index == -1) && PyErr_Occurred())
+ /* -1 can be either the actual value, or an error flag. */
+ return NULL;
+ } else if (PySlice_Check(key)) {
+ values = PyObject_GetItem(self->row, key);
+ if (values == NULL)
+ return NULL;
+
+ processors = PyObject_GetItem(self->processors, key);
+ if (processors == NULL) {
+ Py_DECREF(values);
+ return NULL;
+ }
+
+ result = BaseRowProxy_processvalues(values, processors, 1);
+ Py_DECREF(values);
+ Py_DECREF(processors);
+ return result;
+ } else {
+ record = PyDict_GetItem((PyObject *)self->keymap, key);
+ if (record == NULL) {
+ record = PyObject_CallMethod(self->parent, "_key_fallback",
+ "O", key);
+ if (record == NULL)
+ return NULL;
+ }
+
+ indexobject = PyTuple_GetItem(record, 1);
+ if (indexobject == NULL)
+ return NULL;
+
+ if (indexobject == Py_None) {
+ exc_module = PyImport_ImportModule("sqlalchemy.exc");
+ if (exc_module == NULL)
+ return NULL;
+
+ exception = PyObject_GetAttrString(exc_module,
+ "InvalidRequestError");
+ Py_DECREF(exc_module);
+ if (exception == NULL)
+ return NULL;
+
+ cstr_key = PyString_AsString(key);
+ if (cstr_key == NULL)
+ return NULL;
+
+ PyErr_Format(exception,
+ "Ambiguous column name '%s' in result set! "
+ "try 'use_labels' option on select statement.", cstr_key);
+ return NULL;
+ }
+
+ index = PyInt_AsLong(indexobject);
+ if ((index == -1) && PyErr_Occurred())
+ /* -1 can be either the actual value, or an error flag. */
+ return NULL;
+ }
+ processor = PyList_GetItem(self->processors, index);
+ if (processor == NULL)
+ return NULL;
+
+ value = PyTuple_GetItem(self->row, index);
+ if (value == NULL)
+ return NULL;
+
+ if (processor != Py_None) {
+ return PyObject_CallFunctionObjArgs(processor, value, NULL);
+ } else {
+ Py_INCREF(value);
+ return value;
+ }
+}
+
+static PyObject *
+BaseRowProxy_getattro(BaseRowProxy *self, PyObject *name)
+{
+ PyObject *tmp;
+
+ if (!(tmp = PyObject_GenericGetAttr((PyObject *)self, name))) {
+ if (!PyErr_ExceptionMatches(PyExc_AttributeError))
+ return NULL;
+ PyErr_Clear();
+ }
+ else
+ return tmp;
+
+ return BaseRowProxy_subscript(self, name);
+}
+
+/***********************
+ * getters and setters *
+ ***********************/
+
+static PyObject *
+BaseRowProxy_getparent(BaseRowProxy *self, void *closure)
+{
+ Py_INCREF(self->parent);
+ return self->parent;
+}
+
+static int
+BaseRowProxy_setparent(BaseRowProxy *self, PyObject *value, void *closure)
+{
+ PyObject *module, *cls;
+
+ if (value == NULL) {
+ PyErr_SetString(PyExc_TypeError,
+ "Cannot delete the 'parent' attribute");
+ return -1;
+ }
+
+ module = PyImport_ImportModule("sqlalchemy.engine.base");
+ if (module == NULL)
+ return -1;
+
+ cls = PyObject_GetAttrString(module, "ResultMetaData");
+ Py_DECREF(module);
+ if (cls == NULL)
+ return -1;
+
+ if (PyObject_IsInstance(value, cls) != 1) {
+ PyErr_SetString(PyExc_TypeError,
+ "The 'parent' attribute value must be an instance of "
+ "ResultMetaData");
+ return -1;
+ }
+ Py_DECREF(cls);
+ Py_XDECREF(self->parent);
+ Py_INCREF(value);
+ self->parent = value;
+
+ return 0;
+}
+
+static PyObject *
+BaseRowProxy_getrow(BaseRowProxy *self, void *closure)
+{
+ Py_INCREF(self->row);
+ return self->row;
+}
+
+static int
+BaseRowProxy_setrow(BaseRowProxy *self, PyObject *value, void *closure)
+{
+ if (value == NULL) {
+ PyErr_SetString(PyExc_TypeError,
+ "Cannot delete the 'row' attribute");
+ return -1;
+ }
+
+ if (!PyTuple_CheckExact(value)) {
+ PyErr_SetString(PyExc_TypeError,
+ "The 'row' attribute value must be a tuple");
+ return -1;
+ }
+
+ Py_XDECREF(self->row);
+ Py_INCREF(value);
+ self->row = value;
+
+ return 0;
+}
+
+static PyObject *
+BaseRowProxy_getprocessors(BaseRowProxy *self, void *closure)
+{
+ Py_INCREF(self->processors);
+ return self->processors;
+}
+
+static int
+BaseRowProxy_setprocessors(BaseRowProxy *self, PyObject *value, void *closure)
+{
+ if (value == NULL) {
+ PyErr_SetString(PyExc_TypeError,
+ "Cannot delete the 'processors' attribute");
+ return -1;
+ }
+
+ if (!PyList_CheckExact(value)) {
+ PyErr_SetString(PyExc_TypeError,
+ "The 'processors' attribute value must be a list");
+ return -1;
+ }
+
+ Py_XDECREF(self->processors);
+ Py_INCREF(value);
+ self->processors = value;
+
+ return 0;
+}
+
+static PyObject *
+BaseRowProxy_getkeymap(BaseRowProxy *self, void *closure)
+{
+ Py_INCREF(self->keymap);
+ return self->keymap;
+}
+
+static int
+BaseRowProxy_setkeymap(BaseRowProxy *self, PyObject *value, void *closure)
+{
+ if (value == NULL) {
+ PyErr_SetString(PyExc_TypeError,
+ "Cannot delete the 'keymap' attribute");
+ return -1;
+ }
+
+ if (!PyDict_CheckExact(value)) {
+ PyErr_SetString(PyExc_TypeError,
+ "The 'keymap' attribute value must be a dict");
+ return -1;
+ }
+
+ Py_XDECREF(self->keymap);
+ Py_INCREF(value);
+ self->keymap = value;
+
+ return 0;
+}
+
+static PyGetSetDef BaseRowProxy_getseters[] = {
+ {"_parent",
+ (getter)BaseRowProxy_getparent, (setter)BaseRowProxy_setparent,
+ "ResultMetaData",
+ NULL},
+ {"_row",
+ (getter)BaseRowProxy_getrow, (setter)BaseRowProxy_setrow,
+ "Original row tuple",
+ NULL},
+ {"_processors",
+ (getter)BaseRowProxy_getprocessors, (setter)BaseRowProxy_setprocessors,
+ "list of type processors",
+ NULL},
+ {"_keymap",
+ (getter)BaseRowProxy_getkeymap, (setter)BaseRowProxy_setkeymap,
+ "Key to (processor, index) dict",
+ NULL},
+ {NULL}
+};
+
+static PyMethodDef BaseRowProxy_methods[] = {
+ {"values", (PyCFunction)BaseRowProxy_values, METH_NOARGS,
+ "Return the values represented by this BaseRowProxy as a list."},
+ {"__reduce__", (PyCFunction)BaseRowProxy_reduce, METH_NOARGS,
+ "Pickle support method."},
+ {NULL} /* Sentinel */
+};
+
+static PySequenceMethods BaseRowProxy_as_sequence = {
+ (lenfunc)BaseRowProxy_length, /* sq_length */
+ 0, /* sq_concat */
+ 0, /* sq_repeat */
+ 0, /* sq_item */
+ 0, /* sq_slice */
+ 0, /* sq_ass_item */
+ 0, /* sq_ass_slice */
+ 0, /* sq_contains */
+ 0, /* sq_inplace_concat */
+ 0, /* sq_inplace_repeat */
+};
+
+static PyMappingMethods BaseRowProxy_as_mapping = {
+ (lenfunc)BaseRowProxy_length, /* mp_length */
+ (binaryfunc)BaseRowProxy_subscript, /* mp_subscript */
+ 0 /* mp_ass_subscript */
+};
+
+static PyTypeObject BaseRowProxyType = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /* ob_size */
+ "sqlalchemy.cresultproxy.BaseRowProxy", /* tp_name */
+ sizeof(BaseRowProxy), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ (destructor)BaseRowProxy_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ &BaseRowProxy_as_sequence, /* tp_as_sequence */
+ &BaseRowProxy_as_mapping, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ (getattrofunc)BaseRowProxy_getattro,/* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ "BaseRowProxy is a abstract base class for RowProxy", /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ (getiterfunc)BaseRowProxy_iter, /* tp_iter */
+ 0, /* tp_iternext */
+ BaseRowProxy_methods, /* tp_methods */
+ 0, /* tp_members */
+ BaseRowProxy_getseters, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)BaseRowProxy_init, /* tp_init */
+ 0, /* tp_alloc */
+ 0 /* tp_new */
+};
+
+
+#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
+#define PyMODINIT_FUNC void
+#endif
+
+
+static PyMethodDef module_methods[] = {
+ {"rowproxy_reconstructor", rowproxy_reconstructor, METH_VARARGS,
+ "reconstruct a RowProxy instance from its pickled form."},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
+
+PyMODINIT_FUNC
+initcresultproxy(void)
+{
+ PyObject *m;
+
+ BaseRowProxyType.tp_new = PyType_GenericNew;
+ if (PyType_Ready(&BaseRowProxyType) < 0)
+ return;
+
+ m = Py_InitModule3("cresultproxy", module_methods,
+ "Module containing C versions of core ResultProxy classes.");
+ if (m == NULL)
+ return;
+
+ Py_INCREF(&BaseRowProxyType);
+ PyModule_AddObject(m, "BaseRowProxy", (PyObject *)&BaseRowProxyType);
+
+}
+
diff --git a/lib/sqlalchemy/dialects/access/base.py b/lib/sqlalchemy/dialects/access/base.py
index a46ad247a..c10e77011 100644
--- a/lib/sqlalchemy/dialects/access/base.py
+++ b/lib/sqlalchemy/dialects/access/base.py
@@ -17,23 +17,17 @@ This dialect is *not* tested on SQLAlchemy 0.6.
from sqlalchemy import sql, schema, types, exc, pool
from sqlalchemy.sql import compiler, expression
from sqlalchemy.engine import default, base
-
+from sqlalchemy import processors
class AcNumeric(types.Numeric):
- def result_processor(self, dialect, coltype):
- return None
+ def get_col_spec(self):
+ return "NUMERIC"
def bind_processor(self, dialect):
- def process(value):
- if value is None:
- # Not sure that this exception is needed
- return value
- else:
- return str(value)
- return process
+ return processors.to_str
- def get_col_spec(self):
- return "NUMERIC"
+ def result_processor(self, dialect, coltype):
+ return None
class AcFloat(types.Float):
def get_col_spec(self):
@@ -41,11 +35,7 @@ class AcFloat(types.Float):
def bind_processor(self, dialect):
"""By converting to string, we can use Decimal types round-trip."""
- def process(value):
- if not value is None:
- return str(value)
- return None
- return process
+ return processors.to_str
class AcInteger(types.Integer):
def get_col_spec(self):
@@ -103,25 +93,6 @@ class AcBoolean(types.Boolean):
def get_col_spec(self):
return "YESNO"
- def result_processor(self, dialect, coltype):
- def process(value):
- if value is None:
- return None
- return value and True or False
- return process
-
- def bind_processor(self, dialect):
- def process(value):
- if value is True:
- return 1
- elif value is False:
- return 0
- elif value is None:
- return None
- else:
- return value and True or False
- return process
-
class AcTimeStamp(types.TIMESTAMP):
def get_col_spec(self):
return "TIMESTAMP"
@@ -443,4 +414,4 @@ dialect.poolclass = pool.SingletonThreadPool
dialect.statement_compiler = AccessCompiler
dialect.ddlcompiler = AccessDDLCompiler
dialect.preparer = AccessIdentifierPreparer
-dialect.execution_ctx_cls = AccessExecutionContext \ No newline at end of file
+dialect.execution_ctx_cls = AccessExecutionContext
diff --git a/lib/sqlalchemy/dialects/informix/base.py b/lib/sqlalchemy/dialects/informix/base.py
index 2802d493a..54aae6eb3 100644
--- a/lib/sqlalchemy/dialects/informix/base.py
+++ b/lib/sqlalchemy/dialects/informix/base.py
@@ -302,4 +302,4 @@ class InformixDialect(default.DefaultDialect):
@reflection.cache
def get_indexes(self, connection, table_name, schema, **kw):
# TODO
- return [] \ No newline at end of file
+ return []
diff --git a/lib/sqlalchemy/dialects/maxdb/base.py b/lib/sqlalchemy/dialects/maxdb/base.py
index 2e0b9518b..f409f3213 100644
--- a/lib/sqlalchemy/dialects/maxdb/base.py
+++ b/lib/sqlalchemy/dialects/maxdb/base.py
@@ -60,7 +60,7 @@ this.
"""
import datetime, itertools, re
-from sqlalchemy import exc, schema, sql, util
+from sqlalchemy import exc, schema, sql, util, processors
from sqlalchemy.sql import operators as sql_operators, expression as sql_expr
from sqlalchemy.sql import compiler, visitors
from sqlalchemy.engine import base as engine_base, default
@@ -86,6 +86,12 @@ class _StringType(sqltypes.String):
return process
def result_processor(self, dialect, coltype):
+ #XXX: this code is probably very slow and one should try (if at all
+ # possible) to determine the correct code path on a per-connection
+ # basis (ie, here in result_processor, instead of inside the processor
+ # function itself) and probably also use a few generic
+ # processors, or possibly per query (though there is no mechanism
+ # for that yet).
def process(value):
while True:
if value is None:
@@ -152,6 +158,7 @@ class MaxNumeric(sqltypes.Numeric):
def bind_processor(self, dialect):
return None
+
class MaxTimestamp(sqltypes.DateTime):
def bind_processor(self, dialect):
def process(value):
@@ -172,25 +179,30 @@ class MaxTimestamp(sqltypes.DateTime):
return process
def result_processor(self, dialect, coltype):
- def process(value):
- if value is None:
- return None
- elif dialect.datetimeformat == 'internal':
- return datetime.datetime(
- *[int(v)
- for v in (value[0:4], value[4:6], value[6:8],
- value[8:10], value[10:12], value[12:14],
- value[14:])])
- elif dialect.datetimeformat == 'iso':
- return datetime.datetime(
- *[int(v)
- for v in (value[0:4], value[5:7], value[8:10],
- value[11:13], value[14:16], value[17:19],
- value[20:])])
- else:
- raise exc.InvalidRequestError(
- "datetimeformat '%s' is not supported." % (
- dialect.datetimeformat,))
+ if dialect.datetimeformat == 'internal':
+ def process(value):
+ if value is None:
+ return None
+ else:
+ return datetime.datetime(
+ *[int(v)
+ for v in (value[0:4], value[4:6], value[6:8],
+ value[8:10], value[10:12], value[12:14],
+ value[14:])])
+ elif dialect.datetimeformat == 'iso':
+ def process(value):
+ if value is None:
+ return None
+ else:
+ return datetime.datetime(
+ *[int(v)
+ for v in (value[0:4], value[5:7], value[8:10],
+ value[11:13], value[14:16], value[17:19],
+ value[20:])])
+ else:
+ raise exc.InvalidRequestError(
+ "datetimeformat '%s' is not supported." %
+ dialect.datetimeformat)
return process
@@ -212,19 +224,24 @@ class MaxDate(sqltypes.Date):
return process
def result_processor(self, dialect, coltype):
- def process(value):
- if value is None:
- return None
- elif dialect.datetimeformat == 'internal':
- return datetime.date(
- *[int(v) for v in (value[0:4], value[4:6], value[6:8])])
- elif dialect.datetimeformat == 'iso':
- return datetime.date(
- *[int(v) for v in (value[0:4], value[5:7], value[8:10])])
- else:
- raise exc.InvalidRequestError(
- "datetimeformat '%s' is not supported." % (
- dialect.datetimeformat,))
+ if dialect.datetimeformat == 'internal':
+ def process(value):
+ if value is None:
+ return None
+ else:
+ return datetime.date(int(value[0:4]), int(value[4:6]),
+ int(value[6:8]))
+ elif dialect.datetimeformat == 'iso':
+ def process(value):
+ if value is None:
+ return None
+ else:
+ return datetime.date(int(value[0:4]), int(value[5:7]),
+ int(value[8:10]))
+ else:
+ raise exc.InvalidRequestError(
+ "datetimeformat '%s' is not supported." %
+ dialect.datetimeformat)
return process
@@ -246,31 +263,30 @@ class MaxTime(sqltypes.Time):
return process
def result_processor(self, dialect, coltype):
- def process(value):
- if value is None:
- return None
- elif dialect.datetimeformat == 'internal':
- t = datetime.time(
- *[int(v) for v in (value[0:4], value[4:6], value[6:8])])
- return t
- elif dialect.datetimeformat == 'iso':
- return datetime.time(
- *[int(v) for v in (value[0:4], value[5:7], value[8:10])])
- else:
- raise exc.InvalidRequestError(
- "datetimeformat '%s' is not supported." % (
- dialect.datetimeformat,))
+ if dialect.datetimeformat == 'internal':
+ def process(value):
+ if value is None:
+ return None
+ else:
+ return datetime.time(int(value[0:4]), int(value[4:6]),
+ int(value[6:8]))
+ elif dialect.datetimeformat == 'iso':
+ def process(value):
+ if value is None:
+ return None
+ else:
+ return datetime.time(int(value[0:4]), int(value[5:7]),
+ int(value[8:10]))
+ else:
+ raise exc.InvalidRequestError(
+ "datetimeformat '%s' is not supported." %
+ dialect.datetimeformat)
return process
class MaxBlob(sqltypes.LargeBinary):
def bind_processor(self, dialect):
- def process(value):
- if value is None:
- return None
- else:
- return str(value)
- return process
+ return processors.to_str
def result_processor(self, dialect, coltype):
def process(value):
diff --git a/lib/sqlalchemy/dialects/mssql/base.py b/lib/sqlalchemy/dialects/mssql/base.py
index 4e58d64b3..3f4e0b9f3 100644
--- a/lib/sqlalchemy/dialects/mssql/base.py
+++ b/lib/sqlalchemy/dialects/mssql/base.py
@@ -233,11 +233,10 @@ from sqlalchemy.sql import select, compiler, expression, \
functions as sql_functions, util as sql_util
from sqlalchemy.engine import default, base, reflection
from sqlalchemy import types as sqltypes
-from decimal import Decimal as _python_Decimal
+from sqlalchemy import processors
from sqlalchemy.types import INTEGER, BIGINT, SMALLINT, DECIMAL, NUMERIC, \
FLOAT, TIMESTAMP, DATETIME, DATE, BINARY,\
VARBINARY, BLOB
-
from sqlalchemy.dialects.mssql import information_schema as ischema
@@ -280,22 +279,12 @@ RESERVED_WORDS = set(
class _MSNumeric(sqltypes.Numeric):
def result_processor(self, dialect, coltype):
if self.asdecimal:
- def process(value):
- if value is not None:
- return _python_Decimal(str(value))
- else:
- return value
- return process
+ return processors.to_decimal_processor_factory(decimal.Decimal)
else:
#XXX: if the DBAPI returns a float (this is likely, given the
# processor when asdecimal is True), this should be a None
# processor instead.
- def process(value):
- if value is not None:
- return float(value)
- else:
- return value
- return process
+ return processors.to_float
def bind_processor(self, dialect):
def process(value):
diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py
index eb348f1a1..82a4af941 100644
--- a/lib/sqlalchemy/dialects/mysql/base.py
+++ b/lib/sqlalchemy/dialects/mysql/base.py
@@ -351,7 +351,8 @@ class DECIMAL(_NumericType, sqltypes.DECIMAL):
numeric.
"""
- super(DECIMAL, self).__init__(precision=precision, scale=scale, asdecimal=asdecimal, **kw)
+ super(DECIMAL, self).__init__(precision=precision, scale=scale,
+ asdecimal=asdecimal, **kw)
class DOUBLE(_FloatType):
@@ -375,7 +376,8 @@ class DOUBLE(_FloatType):
numeric.
"""
- super(DOUBLE, self).__init__(precision=precision, scale=scale, asdecimal=asdecimal, **kw)
+ super(DOUBLE, self).__init__(precision=precision, scale=scale,
+ asdecimal=asdecimal, **kw)
class REAL(_FloatType):
"""MySQL REAL type."""
@@ -398,7 +400,8 @@ class REAL(_FloatType):
numeric.
"""
- super(REAL, self).__init__(precision=precision, scale=scale, asdecimal=asdecimal, **kw)
+ super(REAL, self).__init__(precision=precision, scale=scale,
+ asdecimal=asdecimal, **kw)
class FLOAT(_FloatType, sqltypes.FLOAT):
"""MySQL FLOAT type."""
@@ -421,7 +424,8 @@ class FLOAT(_FloatType, sqltypes.FLOAT):
numeric.
"""
- super(FLOAT, self).__init__(precision=precision, scale=scale, asdecimal=asdecimal, **kw)
+ super(FLOAT, self).__init__(precision=precision, scale=scale,
+ asdecimal=asdecimal, **kw)
def bind_processor(self, dialect):
return None
@@ -2459,6 +2463,7 @@ class _DecodingRowProxy(object):
def __init__(self, rowproxy, charset):
self.rowproxy = rowproxy
self.charset = charset
+
def __getitem__(self, index):
item = self.rowproxy[index]
if isinstance(item, _array):
@@ -2467,6 +2472,7 @@ class _DecodingRowProxy(object):
return item.decode(self.charset)
else:
return item
+
def __getattr__(self, attr):
item = getattr(self.rowproxy, attr)
if isinstance(item, _array):
diff --git a/lib/sqlalchemy/dialects/mysql/mysqldb.py b/lib/sqlalchemy/dialects/mysql/mysqldb.py
index c07ed8713..8cfd5930f 100644
--- a/lib/sqlalchemy/dialects/mysql/mysqldb.py
+++ b/lib/sqlalchemy/dialects/mysql/mysqldb.py
@@ -28,6 +28,7 @@ from sqlalchemy.dialects.mysql.base import (DECIMAL, MySQLDialect, MySQLExecutio
from sqlalchemy.engine import base as engine_base, default
from sqlalchemy.sql import operators as sql_operators
from sqlalchemy import exc, log, schema, sql, types as sqltypes, util
+from sqlalchemy import processors
class MySQL_mysqldbExecutionContext(MySQLExecutionContext):
@@ -51,12 +52,7 @@ class _DecimalType(_NumericType):
def result_processor(self, dialect, coltype):
if self.asdecimal:
return None
- def process(value):
- if value is not None:
- return float(value)
- else:
- return value
- return process
+ return processors.to_float
class _MySQLdbNumeric(_DecimalType, NUMERIC):
diff --git a/lib/sqlalchemy/dialects/mysql/oursql.py b/lib/sqlalchemy/dialects/mysql/oursql.py
index a03aa988e..1fca6850a 100644
--- a/lib/sqlalchemy/dialects/mysql/oursql.py
+++ b/lib/sqlalchemy/dialects/mysql/oursql.py
@@ -29,18 +29,14 @@ from sqlalchemy.dialects.mysql.base import (BIT, MySQLDialect, MySQLExecutionCon
from sqlalchemy.engine import base as engine_base, default
from sqlalchemy.sql import operators as sql_operators
from sqlalchemy import exc, log, schema, sql, types as sqltypes, util
+from sqlalchemy import processors
class _oursqlNumeric(NUMERIC):
def result_processor(self, dialect, coltype):
if self.asdecimal:
return None
- def process(value):
- if value is not None:
- return float(value)
- else:
- return value
- return process
+ return processors.to_float
class _oursqlBIT(BIT):
diff --git a/lib/sqlalchemy/dialects/oracle/zxjdbc.py b/lib/sqlalchemy/dialects/oracle/zxjdbc.py
index 22a1f443c..fba16288a 100644
--- a/lib/sqlalchemy/dialects/oracle/zxjdbc.py
+++ b/lib/sqlalchemy/dialects/oracle/zxjdbc.py
@@ -32,6 +32,9 @@ class _ZxJDBCDate(sqltypes.Date):
class _ZxJDBCNumeric(sqltypes.Numeric):
def result_processor(self, dialect, coltype):
+ #XXX: does the dialect return Decimal or not???
+ # if it does (in all cases), we could use a None processor as well as
+ # the to_float generic processor
if self.asdecimal:
def process(value):
if isinstance(value, decimal.Decimal):
diff --git a/lib/sqlalchemy/dialects/postgresql/pg8000.py b/lib/sqlalchemy/dialects/postgresql/pg8000.py
index e90bebb6b..079b05530 100644
--- a/lib/sqlalchemy/dialects/postgresql/pg8000.py
+++ b/lib/sqlalchemy/dialects/postgresql/pg8000.py
@@ -19,31 +19,23 @@ Interval
Passing data from/to the Interval type is not supported as of yet.
"""
-from sqlalchemy.engine import default
import decimal
+
+from sqlalchemy.engine import default
from sqlalchemy import util, exc
+from sqlalchemy import processors
from sqlalchemy import types as sqltypes
from sqlalchemy.dialects.postgresql.base import PGDialect, \
PGCompiler, PGIdentifierPreparer, PGExecutionContext
class _PGNumeric(sqltypes.Numeric):
def bind_processor(self, dialect):
- def process(value):
- if value is not None:
- return float(value)
- else:
- return value
- return process
+ return processors.to_float
def result_processor(self, dialect, coltype):
if self.asdecimal:
if coltype in (700, 701):
- def process(value):
- if value is not None:
- return decimal.Decimal(str(value))
- else:
- return value
- return process
+ return processors.to_decimal_processor_factory(decimal.Decimal)
elif coltype == 1700:
# pg8000 returns Decimal natively for 1700
return None
@@ -54,12 +46,7 @@ class _PGNumeric(sqltypes.Numeric):
# pg8000 returns float natively for 701
return None
elif coltype == 1700:
- def process(value):
- if value is not None:
- return float(value)
- else:
- return value
- return process
+ return processors.to_float
else:
raise exc.InvalidRequestError("Unknown PG numeric type: %d" % coltype)
diff --git a/lib/sqlalchemy/dialects/postgresql/psycopg2.py b/lib/sqlalchemy/dialects/postgresql/psycopg2.py
index bb6562dea..712124288 100644
--- a/lib/sqlalchemy/dialects/postgresql/psycopg2.py
+++ b/lib/sqlalchemy/dialects/postgresql/psycopg2.py
@@ -46,8 +46,11 @@ The following per-statement execution options are respected:
"""
-import decimal, random, re
+import random, re
+import decimal
+
from sqlalchemy import util
+from sqlalchemy import processors
from sqlalchemy.engine import base, default
from sqlalchemy.sql import expression
from sqlalchemy.sql import operators as sql_operators
@@ -63,12 +66,7 @@ class _PGNumeric(sqltypes.Numeric):
def result_processor(self, dialect, coltype):
if self.asdecimal:
if coltype in (700, 701):
- def process(value):
- if value is not None:
- return decimal.Decimal(str(value))
- else:
- return value
- return process
+ return processors.to_decimal_processor_factory(decimal.Decimal)
elif coltype == 1700:
# pg8000 returns Decimal natively for 1700
return None
@@ -79,12 +77,7 @@ class _PGNumeric(sqltypes.Numeric):
# pg8000 returns float natively for 701
return None
elif coltype == 1700:
- def process(value):
- if value is not None:
- return float(value)
- else:
- return value
- return process
+ return processors.to_float
else:
raise exc.InvalidRequestError("Unknown PG numeric type: %d" % coltype)
diff --git a/lib/sqlalchemy/dialects/postgresql/pypostgresql.py b/lib/sqlalchemy/dialects/postgresql/pypostgresql.py
index 77ed44512..88f1acde7 100644
--- a/lib/sqlalchemy/dialects/postgresql/pypostgresql.py
+++ b/lib/sqlalchemy/dialects/postgresql/pypostgresql.py
@@ -12,6 +12,7 @@ import decimal
from sqlalchemy import util
from sqlalchemy import types as sqltypes
from sqlalchemy.dialects.postgresql.base import PGDialect, PGExecutionContext
+from sqlalchemy import processors
class PGNumeric(sqltypes.Numeric):
def bind_processor(self, dialect):
@@ -21,12 +22,7 @@ class PGNumeric(sqltypes.Numeric):
if self.asdecimal:
return None
else:
- def process(value):
- if value is not None:
- return float(value)
- else:
- return value
- return process
+ return processors.to_float
class PostgreSQL_pypostgresqlExecutionContext(PGExecutionContext):
pass
diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py
index 696f65a6c..e987439c5 100644
--- a/lib/sqlalchemy/dialects/sqlite/base.py
+++ b/lib/sqlalchemy/dialects/sqlite/base.py
@@ -54,6 +54,7 @@ from sqlalchemy import types as sqltypes
from sqlalchemy import util
from sqlalchemy.sql import compiler, functions as sql_functions
from sqlalchemy.util import NoneType
+from sqlalchemy import processors
from sqlalchemy.types import BLOB, BOOLEAN, CHAR, DATE, DATETIME, DECIMAL,\
FLOAT, INTEGER, NUMERIC, SMALLINT, TEXT, TIME,\
@@ -62,13 +63,10 @@ from sqlalchemy.types import BLOB, BOOLEAN, CHAR, DATE, DATETIME, DECIMAL,\
class _NumericMixin(object):
def bind_processor(self, dialect):
- type_ = self.asdecimal and str or float
- def process(value):
- if value is not None:
- return type_(value)
- else:
- return value
- return process
+ if self.asdecimal:
+ return processors.to_str
+ else:
+ return processors.to_float
class _SLNumeric(_NumericMixin, sqltypes.Numeric):
pass
@@ -86,19 +84,7 @@ class _DateTimeMixin(object):
if storage_format is not None:
self._storage_format = storage_format
- def _result_processor(self, fn):
- rmatch = self._reg.match
- # Even on python2.6 datetime.strptime is both slower than this code
- # and it does not support microseconds.
- def process(value):
- if value is not None:
- return fn(*map(int, rmatch(value).groups(0)))
- else:
- return None
- return process
-
class DATETIME(_DateTimeMixin, sqltypes.DateTime):
- _reg = re.compile(r"(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)(?:\.(\d+))?")
_storage_format = "%04d-%02d-%02d %02d:%02d:%02d.%06d"
def bind_processor(self, dialect):
@@ -121,10 +107,13 @@ class DATETIME(_DateTimeMixin, sqltypes.DateTime):
return process
def result_processor(self, dialect, coltype):
- return self._result_processor(datetime.datetime)
+ if self._reg:
+ return processors.str_to_datetime_processor_factory(
+ self._reg, datetime.datetime)
+ else:
+ return processors.str_to_datetime
class DATE(_DateTimeMixin, sqltypes.Date):
- _reg = re.compile(r"(\d+)-(\d+)-(\d+)")
_storage_format = "%04d-%02d-%02d"
def bind_processor(self, dialect):
@@ -141,10 +130,13 @@ class DATE(_DateTimeMixin, sqltypes.Date):
return process
def result_processor(self, dialect, coltype):
- return self._result_processor(datetime.date)
+ if self._reg:
+ return processors.str_to_datetime_processor_factory(
+ self._reg, datetime.date)
+ else:
+ return processors.str_to_date
class TIME(_DateTimeMixin, sqltypes.Time):
- _reg = re.compile(r"(\d+):(\d+):(\d+)(?:\.(\d+))?")
_storage_format = "%02d:%02d:%02d.%06d"
def bind_processor(self, dialect):
@@ -162,7 +154,11 @@ class TIME(_DateTimeMixin, sqltypes.Time):
return process
def result_processor(self, dialect, coltype):
- return self._result_processor(datetime.time)
+ if self._reg:
+ return processors.str_to_datetime_processor_factory(
+ self._reg, datetime.time)
+ else:
+ return processors.str_to_time
colspecs = {
sqltypes.Date: DATE,
diff --git a/lib/sqlalchemy/dialects/sybase/base.py b/lib/sqlalchemy/dialects/sybase/base.py
index cdbf6138d..886a773d8 100644
--- a/lib/sqlalchemy/dialects/sybase/base.py
+++ b/lib/sqlalchemy/dialects/sybase/base.py
@@ -115,24 +115,7 @@ class SybaseUniqueIdentifier(sqltypes.TypeEngine):
__visit_name__ = "UNIQUEIDENTIFIER"
class SybaseBoolean(sqltypes.Boolean):
- def result_processor(self, dialect, coltype):
- def process(value):
- if value is None:
- return None
- return value and True or False
- return process
-
- def bind_processor(self, dialect):
- def process(value):
- if value is True:
- return 1
- elif value is False:
- return 0
- elif value is None:
- return None
- else:
- return value and True or False
- return process
+ pass
class SybaseTypeCompiler(compiler.GenericTypeCompiler):
def visit_large_binary(self, type_):
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py
index 844183628..4dc9665c0 100644
--- a/lib/sqlalchemy/engine/base.py
+++ b/lib/sqlalchemy/engine/base.py
@@ -20,6 +20,7 @@ __all__ = [
'connection_memoize']
import inspect, StringIO, sys, operator
+from itertools import izip
from sqlalchemy import exc, schema, util, types, log
from sqlalchemy.sql import expression
@@ -1536,16 +1537,20 @@ class Engine(Connectable):
def _proxy_connection_cls(cls, proxy):
class ProxyConnection(cls):
def execute(self, object, *multiparams, **params):
- return proxy.execute(self, super(ProxyConnection, self).execute, object, *multiparams, **params)
+ return proxy.execute(self, super(ProxyConnection, self).execute,
+ object, *multiparams, **params)
def _execute_clauseelement(self, elem, multiparams=None, params=None):
- return proxy.execute(self, super(ProxyConnection, self).execute, elem, *(multiparams or []), **(params or {}))
+ return proxy.execute(self, super(ProxyConnection, self).execute,
+ elem, *(multiparams or []), **(params or {}))
def _cursor_execute(self, cursor, statement, parameters, context=None):
- return proxy.cursor_execute(super(ProxyConnection, self)._cursor_execute, cursor, statement, parameters, context, False)
+ return proxy.cursor_execute(super(ProxyConnection, self)._cursor_execute,
+ cursor, statement, parameters, context, False)
def _cursor_executemany(self, cursor, statement, parameters, context=None):
- return proxy.cursor_execute(super(ProxyConnection, self)._cursor_executemany, cursor, statement, parameters, context, True)
+ return proxy.cursor_execute(super(ProxyConnection, self)._cursor_executemany,
+ cursor, statement, parameters, context, True)
def _begin_impl(self):
return proxy.begin(self, super(ProxyConnection, self)._begin_impl)
@@ -1560,27 +1565,125 @@ def _proxy_connection_cls(cls, proxy):
return proxy.savepoint(self, super(ProxyConnection, self)._savepoint_impl, name=name)
def _rollback_to_savepoint_impl(self, name, context):
- return proxy.rollback_savepoint(self, super(ProxyConnection, self)._rollback_to_savepoint_impl, name, context)
+ return proxy.rollback_savepoint(self,
+ super(ProxyConnection, self)._rollback_to_savepoint_impl,
+ name, context)
def _release_savepoint_impl(self, name, context):
- return proxy.release_savepoint(self, super(ProxyConnection, self)._release_savepoint_impl, name, context)
+ return proxy.release_savepoint(self,
+ super(ProxyConnection, self)._release_savepoint_impl,
+ name, context)
def _begin_twophase_impl(self, xid):
- return proxy.begin_twophase(self, super(ProxyConnection, self)._begin_twophase_impl, xid)
+ return proxy.begin_twophase(self,
+ super(ProxyConnection, self)._begin_twophase_impl, xid)
def _prepare_twophase_impl(self, xid):
- return proxy.prepare_twophase(self, super(ProxyConnection, self)._prepare_twophase_impl, xid)
+ return proxy.prepare_twophase(self,
+ super(ProxyConnection, self)._prepare_twophase_impl, xid)
def _rollback_twophase_impl(self, xid, is_prepared):
- return proxy.rollback_twophase(self, super(ProxyConnection, self)._rollback_twophase_impl, xid, is_prepared)
+ return proxy.rollback_twophase(self,
+ super(ProxyConnection, self)._rollback_twophase_impl,
+ xid, is_prepared)
def _commit_twophase_impl(self, xid, is_prepared):
- return proxy.commit_twophase(self, super(ProxyConnection, self)._commit_twophase_impl, xid, is_prepared)
+ return proxy.commit_twophase(self,
+ super(ProxyConnection, self)._commit_twophase_impl,
+ xid, is_prepared)
return ProxyConnection
+# This reconstructor is necessary so that pickles with the C extension or
+# without use the same Binary format.
+# We need a different reconstructor on the C extension so that we can
+# add extra checks that fields have correctly been initialized by
+# __setstate__.
+try:
+ from sqlalchemy.cresultproxy import rowproxy_reconstructor
+
+ # this is a hack so that the reconstructor function is pickled with the
+ # same name as without the C extension.
+ # BUG: It fails for me if I run the "python" interpreter and
+ # then say "import sqlalchemy":
+ # TypeError: 'builtin_function_or_method' object has only read-only attributes (assign to .__module__)
+ # However, if I run the tests with nosetests, it succeeds !
+ # I've verified with pdb etc. that this is the case.
+ #rowproxy_reconstructor.__module__ = 'sqlalchemy.engine.base'
+
+except ImportError:
+ def rowproxy_reconstructor(cls, state):
+ obj = cls.__new__(cls)
+ obj.__setstate__(state)
+ return obj
+
+try:
+ from sqlalchemy.cresultproxy import BaseRowProxy
+except ImportError:
+ class BaseRowProxy(object):
+ __slots__ = ('_parent', '_row', '_processors', '_keymap')
+
+ def __init__(self, parent, row, processors, keymap):
+ """RowProxy objects are constructed by ResultProxy objects."""
+
+ self._parent = parent
+ self._row = row
+ self._processors = processors
+ self._keymap = keymap
+
+ def __reduce__(self):
+ return (rowproxy_reconstructor,
+ (self.__class__, self.__getstate__()))
+
+ def values(self):
+ """Return the values represented by this RowProxy as a list."""
+ return list(self)
+
+ def __iter__(self):
+ for processor, value in izip(self._processors, self._row):
+ if processor is None:
+ yield value
+ else:
+ yield processor(value)
+
+ def __len__(self):
+ return len(self._row)
-class RowProxy(object):
+ def __getitem__(self, key):
+ try:
+ processor, index = self._keymap[key]
+ except KeyError:
+ processor, index = self._parent._key_fallback(key)
+ except TypeError:
+ if isinstance(key, slice):
+ l = []
+ for processor, value in izip(self._processors[key],
+ self._row[key]):
+ if processor is None:
+ l.append(value)
+ else:
+ l.append(processor(value))
+ return tuple(l)
+ else:
+ raise
+ if index is None:
+ raise exc.InvalidRequestError(
+ "Ambiguous column name '%s' in result set! "
+ "try 'use_labels' option on select statement." % key)
+ if processor is not None:
+ return processor(self._row[index])
+ else:
+ return self._row[index]
+
+ def __getattr__(self, name):
+ try:
+ # TODO: no test coverage here
+ return self[name]
+ except KeyError, e:
+ raise AttributeError(e.args[0])
+
+
+class RowProxy(BaseRowProxy):
"""Proxy values from a single cursor row.
Mostly follows "ordered dictionary" behavior, mapping result
@@ -1589,38 +1692,22 @@ class RowProxy(object):
mapped to the original Columns that produced this result set (for
results that correspond to constructed SQL expressions).
"""
+ __slots__ = ()
- __slots__ = ['__parent', '__row', '__colfuncs']
-
- def __init__(self, parent, row):
-
- self.__parent = parent
- self.__row = row
- self.__colfuncs = parent._colfuncs
- if self.__parent._echo:
- self.__parent.logger.debug("Row %r", row)
-
def __contains__(self, key):
- return self.__parent._has_key(self.__row, key)
+ return self._parent._has_key(self._row, key)
- def __len__(self):
- return len(self.__row)
-
def __getstate__(self):
return {
- '__row':[self.__colfuncs[i][0](self.__row) for i in xrange(len(self.__row))],
- '__parent':self.__parent
+ '_parent': self._parent,
+ '_row': tuple(self)
}
-
- def __setstate__(self, d):
- self.__row = d['__row']
- self.__parent = d['__parent']
- self.__colfuncs = self.__parent._colfuncs
-
- def __iter__(self):
- row = self.__row
- for func in self.__parent._colfunc_list:
- yield func(row)
+
+ def __setstate__(self, state):
+ self._parent = parent = state['_parent']
+ self._row = state['_row']
+ self._processors = parent._processors
+ self._keymap = parent._keymap
__hash__ = None
@@ -1636,33 +1723,7 @@ class RowProxy(object):
def has_key(self, key):
"""Return True if this RowProxy contains the given key."""
- return self.__parent._has_key(self.__row, key)
-
- def __getitem__(self, key):
- # the fallback and slices are only useful for __getitem__ anyway
- try:
- return self.__colfuncs[key][0](self.__row)
- except KeyError:
- k = self.__parent._key_fallback(key)
- if k is None:
- raise exc.NoSuchColumnError(
- "Could not locate column in row for column '%s'" % key)
- else:
- # save on KeyError + _key_fallback() lookup next time around
- self.__colfuncs[key] = k
- return k[0](self.__row)
- except TypeError:
- if isinstance(key, slice):
- return tuple(func(self.__row) for func in self.__parent._colfunc_list[key])
- else:
- raise
-
- def __getattr__(self, name):
- try:
- # TODO: no test coverage here
- return self[name]
- except KeyError, e:
- raise AttributeError(e.args[0])
+ return self._parent._has_key(self._row, key)
def items(self):
"""Return a list of tuples, each tuple containing a key/value pair."""
@@ -1672,24 +1733,25 @@ class RowProxy(object):
def keys(self):
"""Return the list of keys as strings represented by this RowProxy."""
- return self.__parent.keys
+ return self._parent.keys
def iterkeys(self):
- return iter(self.__parent.keys)
-
- def values(self):
- """Return the values represented by this RowProxy as a list."""
-
- return list(self)
+ return iter(self._parent.keys)
def itervalues(self):
return iter(self)
+
class ResultMetaData(object):
"""Handle cursor.description, applying additional info from an execution context."""
def __init__(self, parent, metadata):
- self._colfuncs = colfuncs = {}
+ self._processors = processors = []
+
+ # We do not strictly need to store the processor in the key mapping,
+ # though it is faster in the Python version (probably because of the
+ # saved attribute lookup self._processors)
+ self._keymap = keymap = {}
self.keys = []
self._echo = parent._echo
context = parent.context
@@ -1720,29 +1782,25 @@ class ResultMetaData(object):
processor = type_.dialect_impl(dialect).\
result_processor(dialect, coltype)
- if processor:
- def make_colfunc(processor, index):
- def getcol(row):
- return processor(row[index])
- return getcol
- rec = (make_colfunc(processor, i), i, "colfunc")
- else:
- rec = (operator.itemgetter(i), i, "itemgetter")
+ processors.append(processor)
+ rec = (processor, i)
- # indexes as keys
- colfuncs[i] = rec
+ # indexes as keys. This is only needed for the Python version of
+ # RowProxy (the C version uses a faster path for integer indexes).
+ keymap[i] = rec
# Column names as keys
- if colfuncs.setdefault(name.lower(), rec) is not rec:
- #XXX: why not raise directly? because several columns colliding
- #by name is not a problem as long as the user don't use them (ie
- #use the more precise ColumnElement
- colfuncs[name.lower()] = (self._ambiguous_processor(name), i, "ambiguous")
-
+ if keymap.setdefault(name.lower(), rec) is not rec:
+ # We do not raise an exception directly because several
+ # columns colliding by name is not a problem as long as the
+ # user does not try to access them (ie use an index directly,
+ # or the more precise ColumnElement)
+ keymap[name.lower()] = (processor, None)
+
# store the "origname" if we truncated (sqlite only)
if origname and \
- colfuncs.setdefault(origname.lower(), rec) is not rec:
- colfuncs[origname.lower()] = (self._ambiguous_processor(origname), i, "ambiguous")
+ keymap.setdefault(origname.lower(), rec) is not rec:
+ keymap[origname.lower()] = (processor, None)
if dialect.requires_name_normalize:
colname = dialect.normalize_name(colname)
@@ -1750,76 +1808,67 @@ class ResultMetaData(object):
self.keys.append(colname)
if obj:
for o in obj:
- colfuncs[o] = rec
+ keymap[o] = rec
if self._echo:
self.logger = context.engine.logger
self.logger.debug(
"Col %r", tuple(x[0] for x in metadata))
- @util.memoized_property
- def _colfunc_list(self):
- funcs = self._colfuncs
- return [funcs[i][0] for i in xrange(len(self.keys))]
-
def _key_fallback(self, key):
- funcs = self._colfuncs
-
+ map = self._keymap
+ result = None
if isinstance(key, basestring):
- key = key.lower()
- if key in funcs:
- return funcs[key]
-
+ result = map.get(key.lower())
# fallback for targeting a ColumnElement to a textual expression
# this is a rare use case which only occurs when matching text()
- # constructs to ColumnElements
- if isinstance(key, expression.ColumnElement):
- if key._label and key._label.lower() in funcs:
- return funcs[key._label.lower()]
- elif hasattr(key, 'name') and key.name.lower() in funcs:
- return funcs[key.name.lower()]
-
- return None
+ # constructs to ColumnElements, and after a pickle/unpickle roundtrip
+ elif isinstance(key, expression.ColumnElement):
+ if key._label and key._label.lower() in map:
+ result = map[key._label.lower()]
+ elif hasattr(key, 'name') and key.name.lower() in map:
+ result = map[key.name.lower()]
+ if result is None:
+ raise exc.NoSuchColumnError(
+ "Could not locate column in row for column '%s'" % key)
+ else:
+ map[key] = result
+ return result
def _has_key(self, row, key):
- if key in self._colfuncs:
+ if key in self._keymap:
return True
else:
- key = self._key_fallback(key)
- return key is not None
+ try:
+ self._key_fallback(key)
+ return True
+ except exc.NoSuchColumnError:
+ return False
- @classmethod
- def _ambiguous_processor(cls, colname):
- def process(value):
- raise exc.InvalidRequestError(
- "Ambiguous column name '%s' in result set! "
- "try 'use_labels' option on select statement." % colname)
- return process
-
def __len__(self):
return len(self.keys)
def __getstate__(self):
return {
- '_pickled_colfuncs':dict(
- (key, (i, type_))
- for key, (fn, i, type_) in self._colfuncs.iteritems()
+ '_pickled_keymap': dict(
+ (key, index)
+ for key, (processor, index) in self._keymap.iteritems()
if isinstance(key, (basestring, int))
),
- 'keys':self.keys
+ 'keys': self.keys
}
def __setstate__(self, state):
- pickled_colfuncs = state['_pickled_colfuncs']
- self._colfuncs = d = {}
- for key, (index, type_) in pickled_colfuncs.iteritems():
- if type_ == 'ambiguous':
- d[key] = (self._ambiguous_processor(key), index, type_)
- else:
- d[key] = (operator.itemgetter(index), index, "itemgetter")
+ # the row has been processed at pickling time so we don't need any
+ # processor anymore
+ self._processors = [None for _ in xrange(len(state['keys']))]
+ self._keymap = keymap = {}
+ for key, index in state['_pickled_keymap'].iteritems():
+ keymap[key] = (None, index)
self.keys = state['keys']
self._echo = False
-
+
+
class ResultProxy(object):
"""Wraps a DB-API cursor object to provide easier access to row columns.
@@ -2031,13 +2080,27 @@ class ResultProxy(object):
def _fetchall_impl(self):
return self.cursor.fetchall()
+ def process_rows(self, rows):
+ process_row = self._process_row
+ metadata = self._metadata
+ keymap = metadata._keymap
+ processors = metadata._processors
+ if self._echo:
+ log = self.context.engine.logger.debug
+ l = []
+ for row in rows:
+ log("Row %r", row)
+ l.append(process_row(metadata, row, processors, keymap))
+ return l
+ else:
+ return [process_row(metadata, row, processors, keymap)
+ for row in rows]
+
def fetchall(self):
"""Fetch all rows, just like DB-API ``cursor.fetchall()``."""
try:
- process_row = self._process_row
- metadata = self._metadata
- l = [process_row(metadata, row) for row in self._fetchall_impl()]
+ l = self.process_rows(self._fetchall_impl())
self.close()
return l
except Exception, e:
@@ -2053,9 +2116,7 @@ class ResultProxy(object):
"""
try:
- process_row = self._process_row
- metadata = self._metadata
- l = [process_row(metadata, row) for row in self._fetchmany_impl(size)]
+ l = self.process_rows(self._fetchmany_impl(size))
if len(l) == 0:
self.close()
return l
@@ -2074,7 +2135,7 @@ class ResultProxy(object):
try:
row = self._fetchone_impl()
if row is not None:
- return self._process_row(self._metadata, row)
+ return self.process_rows([row])[0]
else:
self.close()
return None
@@ -2096,13 +2157,12 @@ class ResultProxy(object):
try:
if row is not None:
- return self._process_row(self._metadata, row)
+ return self.process_rows([row])[0]
else:
return None
finally:
self.close()
-
def scalar(self):
"""Fetch the first column of the first row, and close the result set.
@@ -2210,9 +2270,18 @@ class FullyBufferedResultProxy(ResultProxy):
return ret
class BufferedColumnRow(RowProxy):
- def __init__(self, parent, row):
- row = [parent._orig_colfuncs[i][0](row) for i in xrange(len(row))]
- super(BufferedColumnRow, self).__init__(parent, row)
+ def __init__(self, parent, row, processors, keymap):
+ # preprocess row
+ row = list(row)
+ # this is a tad faster than using enumerate
+ index = 0
+ for processor in parent._orig_processors:
+ if processor is not None:
+ row[index] = processor(row[index])
+ index += 1
+ row = tuple(row)
+ super(BufferedColumnRow, self).__init__(parent, row,
+ processors, keymap)
class BufferedColumnResultProxy(ResultProxy):
"""A ResultProxy with column buffering behavior.
@@ -2221,7 +2290,7 @@ class BufferedColumnResultProxy(ResultProxy):
fetchone() is called. If fetchmany() or fetchall() are called,
the full grid of results is fetched. This is to operate with
databases where result rows contain "live" results that fall out
- of scope unless explicitly fetched. Currently this includes
+ of scope unless explicitly fetched. Currently this includes
cx_Oracle LOB objects.
"""
@@ -2230,17 +2299,16 @@ class BufferedColumnResultProxy(ResultProxy):
def _init_metadata(self):
super(BufferedColumnResultProxy, self)._init_metadata()
- self._metadata._orig_colfuncs = self._metadata._colfuncs
- self._metadata._colfuncs = colfuncs = {}
- # replace the parent's _colfuncs dict, replacing
- # column processors with straight itemgetters.
- # the original _colfuncs dict is used when each row
- # is constructed.
- for k, (colfunc, index, type_) in self._metadata._orig_colfuncs.iteritems():
- if type_ == "colfunc":
- colfuncs[k] = (operator.itemgetter(index), index, "itemgetter")
- else:
- colfuncs[k] = (colfunc, index, type_)
+ metadata = self._metadata
+ # orig_processors will be used to preprocess each row when they are
+ # constructed.
+ metadata._orig_processors = metadata._processors
+ # replace the all type processors by None processors.
+ metadata._processors = [None for _ in xrange(len(metadata.keys))]
+ keymap = {}
+ for k, (func, index) in metadata._keymap.iteritems():
+ keymap[k] = (None, index)
+ self._metadata._keymap = keymap
def fetchall(self):
# can't call cursor.fetchall(), since rows must be
diff --git a/lib/sqlalchemy/processors.py b/lib/sqlalchemy/processors.py
new file mode 100644
index 000000000..cb4b72545
--- /dev/null
+++ b/lib/sqlalchemy/processors.py
@@ -0,0 +1,90 @@
+# processors.py
+# Copyright (C) 2010 Gaetan de Menten gdementen@gmail.com
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
+"""defines generic type conversion functions, as used in result processors.
+
+They all share one common characteristic: None is passed through unchanged.
+
+"""
+
+import codecs
+import re
+import datetime
+
+def str_to_datetime_processor_factory(regexp, type_):
+ rmatch = regexp.match
+ # Even on python2.6 datetime.strptime is both slower than this code
+ # and it does not support microseconds.
+ def process(value):
+ if value is None:
+ return None
+ else:
+ return type_(*map(int, rmatch(value).groups(0)))
+ return process
+
+try:
+ from sqlalchemy.cprocessors import UnicodeResultProcessor, \
+ DecimalResultProcessor, \
+ to_float, to_str, int_to_boolean, \
+ str_to_datetime, str_to_time, \
+ str_to_date
+
+ def to_unicode_processor_factory(encoding):
+ return UnicodeResultProcessor(encoding).process
+
+ def to_decimal_processor_factory(target_class):
+ return DecimalResultProcessor(target_class).process
+
+except ImportError:
+ def to_unicode_processor_factory(encoding):
+ decoder = codecs.getdecoder(encoding)
+
+ def process(value):
+ if value is None:
+ return None
+ else:
+ # decoder returns a tuple: (value, len). Simply dropping the
+ # len part is safe: it is done that way in the normal
+ # 'xx'.decode(encoding) code path.
+ # cfr python-source/Python/codecs.c:PyCodec_Decode
+ return decoder(value)[0]
+ return process
+
+ def to_decimal_processor_factory(target_class):
+ def process(value):
+ if value is None:
+ return None
+ else:
+ return target_class(str(value))
+ return process
+
+ def to_float(value):
+ if value is None:
+ return None
+ else:
+ return float(value)
+
+ def to_str(value):
+ if value is None:
+ return None
+ else:
+ return str(value)
+
+ def int_to_boolean(value):
+ if value is None:
+ return None
+ else:
+ return value and True or False
+
+ DATETIME_RE = re.compile("(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)(?:\.(\d+))?")
+ TIME_RE = re.compile("(\d+):(\d+):(\d+)(?:\.(\d+))?")
+ DATE_RE = re.compile("(\d+)-(\d+)-(\d+)")
+
+ str_to_datetime = str_to_datetime_processor_factory(DATETIME_RE,
+ datetime.datetime)
+ str_to_time = str_to_datetime_processor_factory(TIME_RE, datetime.time)
+ str_to_date = str_to_datetime_processor_factory(DATE_RE, datetime.date)
+
diff --git a/lib/sqlalchemy/test/profiling.py b/lib/sqlalchemy/test/profiling.py
index 8cab6ceba..c5256affa 100644
--- a/lib/sqlalchemy/test/profiling.py
+++ b/lib/sqlalchemy/test/profiling.py
@@ -93,9 +93,16 @@ def function_call_count(count=None, versions={}, variance=0.05):
version_info = list(sys.version_info)
py_version = '.'.join([str(v) for v in sys.version_info])
-
+ try:
+ from sqlalchemy.cprocessors import to_float
+ cextension = True
+ except ImportError:
+ cextension = False
+
while version_info:
version = '.'.join([str(v) for v in version_info])
+ if cextension:
+ version += "+cextension"
if version in versions:
count = versions[version]
break
diff --git a/lib/sqlalchemy/types.py b/lib/sqlalchemy/types.py
index 465454df9..36302cae3 100644
--- a/lib/sqlalchemy/types.py
+++ b/lib/sqlalchemy/types.py
@@ -32,6 +32,7 @@ schema.types = expression.sqltypes =sys.modules['sqlalchemy.types']
from sqlalchemy.util import pickle
from sqlalchemy.sql.visitors import Visitable
from sqlalchemy import util
+from sqlalchemy import processors
NoneType = type(None)
if util.jython:
@@ -608,14 +609,7 @@ class String(Concatenable, TypeEngine):
if needs_convert:
# note we *assume* that we do not have a unicode object
# here, instead of an expensive isinstance() check.
- decoder = codecs.getdecoder(dialect.encoding)
- def process(value):
- if value is not None:
- # decoder returns a tuple: (value, len)
- return decoder(value)[0]
- else:
- return value
- return process
+ return processors.to_unicode_processor_factory(dialect.encoding)
else:
return None
@@ -810,21 +804,15 @@ class Numeric(_DateAffinity, TypeEngine):
return dbapi.NUMBER
def bind_processor(self, dialect):
- def process(value):
- if value is not None:
- return float(value)
- else:
- return value
- return process
+ return processors.to_float
def result_processor(self, dialect, coltype):
if self.asdecimal:
- def process(value):
- if value is not None:
- return _python_Decimal(str(value))
- else:
- return value
- return process
+ #XXX: use decimal from http://www.bytereef.org/libmpdec.html
+# try:
+# from fastdec import mpd as Decimal
+# except ImportError:
+ return processors.to_decimal_processor_factory(_python_Decimal)
else:
return None
@@ -991,11 +979,7 @@ class _Binary(TypeEngine):
else:
return None
else:
- def process(value):
- if value is not None:
- return str(value)
- else:
- return None
+ process = processors.to_str
return process
# end Py2K
@@ -1349,11 +1333,7 @@ class Boolean(TypeEngine, SchemaType):
if dialect.supports_native_boolean:
return None
else:
- def process(value):
- if value is None:
- return None
- return value and True or False
- return process
+ return processors.int_to_boolean
class Interval(_DateAffinity, TypeDecorator):
"""A type for ``datetime.timedelta()`` objects.
@@ -1419,7 +1399,7 @@ class Interval(_DateAffinity, TypeDecorator):
if impl_processor:
def process(value):
value = impl_processor(value)
- if value is None:
+ if value is None:
return None
return value - epoch
else:
diff --git a/setup.py b/setup.py
index 3a1b5f1dd..20da456d9 100644
--- a/setup.py
+++ b/setup.py
@@ -15,9 +15,11 @@ if sys.version_info >= (3, 0):
)
try:
- from setuptools import setup
+ from setuptools import setup, Extension
except ImportError:
- from distutils.core import setup
+ from distutils.core import setup, Extension
+
+BUILD_CEXTENSIONS = False
def find_packages(dir_):
packages = []
@@ -46,6 +48,12 @@ setup(name = "SQLAlchemy",
license = "MIT License",
tests_require = ['nose >= 0.10'],
test_suite = "nose.collector",
+ ext_modules = (BUILD_CEXTENSIONS and
+ [Extension('sqlalchemy.cprocessors',
+ sources=['lib/sqlalchemy/cextension/processors.c']),
+ Extension('sqlalchemy.cresultproxy',
+ sources=['lib/sqlalchemy/cextension/resultproxy.c'])
+ ]),
entry_points = {
'nose.plugins.0.10': [
'sqlalchemy = sqlalchemy.test.noseplugin:NoseSQLAlchemy',
diff --git a/test/aaa_profiling/test_resultset.py b/test/aaa_profiling/test_resultset.py
index 83901b7f7..459a8e4c4 100644
--- a/test/aaa_profiling/test_resultset.py
+++ b/test/aaa_profiling/test_resultset.py
@@ -29,13 +29,13 @@ class ResultSetTest(TestBase, AssertsExecutionResults):
def teardown(self):
metadata.drop_all()
- @profiling.function_call_count(14416, versions={'2.4':13214})
+ @profiling.function_call_count(14416, versions={'2.4':13214, '2.6+cextension':409})
def test_string(self):
[tuple(row) for row in t.select().execute().fetchall()]
# sqlite3 returns native unicode. so shouldn't be an
# increase here.
- @profiling.function_call_count(14396, versions={'2.4':13214})
+ @profiling.function_call_count(14396, versions={'2.4':13214, '2.6+cextension':409})
def test_unicode(self):
[tuple(row) for row in t2.select().execute().fetchall()]
diff --git a/test/aaa_profiling/test_zoomark.py b/test/aaa_profiling/test_zoomark.py
index 66bb45f31..706f8e470 100644
--- a/test/aaa_profiling/test_zoomark.py
+++ b/test/aaa_profiling/test_zoomark.py
@@ -339,7 +339,7 @@ class ZooMarkTest(TestBase):
def test_profile_3_properties(self):
self.test_baseline_3_properties()
- @profiling.function_call_count(13341, {'2.4': 7963})
+ @profiling.function_call_count(13341, {'2.4': 7963, '2.6+cextension':12447})
def test_profile_4_expressions(self):
self.test_baseline_4_expressions()
@@ -351,7 +351,7 @@ class ZooMarkTest(TestBase):
def test_profile_6_editing(self):
self.test_baseline_6_editing()
- @profiling.function_call_count(2641, {'2.4': 1673})
+ @profiling.function_call_count(2641, {'2.4': 1673, '2.6+cextension':2502})
def test_profile_7_multiview(self):
self.test_baseline_7_multiview()
diff --git a/test/perf/stress_all.py b/test/perf/stress_all.py
new file mode 100644
index 000000000..ad074ee53
--- /dev/null
+++ b/test/perf/stress_all.py
@@ -0,0 +1,226 @@
+# -*- encoding: utf8 -*-
+from datetime import *
+from decimal import Decimal
+#from fastdec import mpd as Decimal
+from cPickle import dumps, loads
+
+#from sqlalchemy.dialects.postgresql.base import ARRAY
+
+from stresstest import *
+
+# ---
+test_types = False
+test_methods = True
+test_pickle = False
+test_orm = False
+# ---
+verbose = True
+
+def values_results(raw_results):
+ return [tuple(r.values()) for r in raw_results]
+
+def getitem_str_results(raw_results):
+ return [
+ (r['id'],
+ r['field0'], r['field1'], r['field2'], r['field3'], r['field4'],
+ r['field5'], r['field6'], r['field7'], r['field8'], r['field9'])
+ for r in raw_results]
+
+def getitem_fallback_results(raw_results):
+ return [
+ (r['ID'],
+ r['FIELD0'], r['FIELD1'], r['FIELD2'], r['FIELD3'], r['FIELD4'],
+ r['FIELD5'], r['FIELD6'], r['FIELD7'], r['FIELD8'], r['FIELD9'])
+ for r in raw_results]
+
+def getitem_int_results(raw_results):
+ return [
+ (r[0],
+ r[1], r[2], r[3], r[4], r[5],
+ r[6], r[7], r[8], r[9], r[10])
+ for r in raw_results]
+
+def getitem_long_results(raw_results):
+ return [
+ (r[0L],
+ r[1L], r[2L], r[3L], r[4L], r[5L],
+ r[6L], r[7L], r[8L], r[9L], r[10L])
+ for r in raw_results]
+
+def getitem_obj_results(raw_results):
+ c = test_table.c
+ fid, f0, f1, f2, f3, f4, f5, f6, f7, f8, f9 = (
+ c.id, c.field0, c.field1, c.field2, c.field3, c.field4,
+ c.field5, c.field6, c.field7, c.field8, c.field9)
+ return [
+ (r[fid],
+ r[f0], r[f1], r[f2], r[f3], r[f4],
+ r[f5], r[f6], r[f7], r[f8], r[f9])
+ for r in raw_results]
+
+def slice_results(raw_results):
+ return [row[0:6] + row[6:11] for row in raw_results]
+
+# ---------- #
+# Test types #
+# ---------- #
+
+# Array
+#def genarrayvalue(rnum, fnum):
+# return [fnum, fnum + 1, fnum + 2]
+#arraytest = (ARRAY(Integer), genarrayvalue,
+# dict(num_fields=100, num_records=1000,
+# engineurl='postgresql:///test'))
+
+# Boolean
+def genbooleanvalue(rnum, fnum):
+ if rnum % 4:
+ return bool(fnum % 2)
+ else:
+ return None
+booleantest = (Boolean, genbooleanvalue, dict(num_records=100000))
+
+# Datetime
+def gendatetimevalue(rnum, fnum):
+ return (rnum % 4) and datetime(2005, 3, 3) or None
+datetimetest = (DateTime, gendatetimevalue, dict(num_records=10000))
+
+# Decimal
+def gendecimalvalue(rnum, fnum):
+ if rnum % 4:
+ return Decimal(str(0.25 * fnum))
+ else:
+ return None
+decimaltest = (Numeric(10, 2), gendecimalvalue, dict(num_records=10000))
+
+# Interval
+
+# no microseconds because Postgres does not seem to support it
+from_epoch = timedelta(14643, 70235)
+def genintervalvalue(rnum, fnum):
+ return from_epoch
+intervaltest = (Interval, genintervalvalue,
+ dict(num_fields=2, num_records=100000))
+
+# PickleType
+def genpicklevalue(rnum, fnum):
+ return (rnum % 4) and {'str': "value%d" % fnum, 'int': rnum} or None
+pickletypetest = (PickleType, genpicklevalue,
+ dict(num_fields=1, num_records=100000))
+
+# TypeDecorator
+class MyIntType(TypeDecorator):
+ impl = Integer
+
+ def process_bind_param(self, value, dialect):
+ return value * 10
+
+ def process_result_value(self, value, dialect):
+ return value / 10
+
+ def copy(self):
+ return MyIntType()
+
+def genmyintvalue(rnum, fnum):
+ return rnum + fnum
+typedecoratortest = (MyIntType, genmyintvalue,
+ dict(num_records=100000))
+
+# Unicode
+def genunicodevalue(rnum, fnum):
+ return (rnum % 4) and (u"value%d" % fnum) or None
+unicodetest = (Unicode(20, assert_unicode=False), genunicodevalue,
+ dict(num_records=100000))
+# dict(engineurl='mysql:///test', freshdata=False))
+
+# do the tests
+if test_types:
+ tests = [booleantest, datetimetest, decimaltest, intervaltest,
+ pickletypetest, typedecoratortest, unicodetest]
+ for engineurl in ('postgresql://scott:tiger@localhost/test',
+ 'sqlite://', 'mysql://scott:tiger@localhost/test'):
+ print "\n%s\n" % engineurl
+ for datatype, genvalue, kwargs in tests:
+ print "%s:" % getattr(datatype, '__name__',
+ datatype.__class__.__name__),
+ profile_and_time_dbfunc(iter_results, datatype, genvalue,
+ profile=False, engineurl=engineurl,
+ verbose=verbose, **kwargs)
+
+# ---------------------- #
+# test row proxy methods #
+# ---------------------- #
+
+if test_methods:
+ methods = [iter_results, values_results, getattr_results,
+ getitem_str_results, getitem_fallback_results,
+ getitem_int_results, getitem_long_results, getitem_obj_results,
+ slice_results]
+ for engineurl in ('postgresql://scott:tiger@localhost/test',
+ 'sqlite://', 'mysql://scott:tiger@localhost/test'):
+ print "\n%s\n" % engineurl
+ test_table = prepare(Unicode(20, assert_unicode=False),
+ genunicodevalue,
+ num_fields=10, num_records=100000,
+ verbose=verbose, engineurl=engineurl)
+ for method in methods:
+ print "%s:" % method.__name__,
+ time_dbfunc(test_table, method, genunicodevalue,
+ num_fields=10, num_records=100000, profile=False,
+ verbose=verbose)
+
+# --------------------------------
+# test pickling Rowproxy instances
+# --------------------------------
+
+def pickletofile_results(raw_results):
+ from cPickle import dump, load
+ for protocol in (0, 1, 2):
+ print "dumping protocol %d..." % protocol
+ f = file('noext.pickle%d' % protocol, 'wb')
+ dump(raw_results, f, protocol)
+ f.close()
+ return raw_results
+
+def pickle_results(raw_results):
+ return loads(dumps(raw_results, 2))
+
+def pickle_meta(raw_results):
+ pickled = dumps(raw_results[0]._parent, 2)
+ metadata = loads(pickled)
+ return raw_results
+
+def pickle_rows(raw_results):
+ return [loads(dumps(row, 2)) for row in raw_results]
+
+if test_pickle:
+ test_table = prepare(Unicode, genunicodevalue,
+ num_fields=10, num_records=10000)
+ funcs = [pickle_rows, pickle_results]
+ for func in funcs:
+ print "%s:" % func.__name__,
+ time_dbfunc(test_table, func, genunicodevalue,
+ num_records=10000, profile=False, verbose=verbose)
+
+# --------------------------------
+# test ORM
+# --------------------------------
+
+if test_orm:
+ from sqlalchemy.orm import *
+
+ class Test(object):
+ pass
+
+ Session = sessionmaker()
+ session = Session()
+
+ def get_results():
+ return session.query(Test).all()
+ print "ORM:",
+ for engineurl in ('postgresql:///test', 'sqlite://', 'mysql:///test'):
+ print "\n%s\n" % engineurl
+ profile_and_time_dbfunc(getattr_results, Unicode(20), genunicodevalue,
+ class_=Test, getresults_func=get_results,
+ engineurl=engineurl, #freshdata=False,
+ num_records=10000, verbose=verbose)
diff --git a/test/perf/stresstest.py b/test/perf/stresstest.py
new file mode 100644
index 000000000..cf9404f53
--- /dev/null
+++ b/test/perf/stresstest.py
@@ -0,0 +1,174 @@
+import gc
+import sys
+import timeit
+import cProfile
+
+from sqlalchemy import MetaData, Table, Column
+from sqlalchemy.types import *
+from sqlalchemy.orm import mapper, clear_mappers
+
+metadata = MetaData()
+
+def gen_table(num_fields, field_type, metadata):
+ return Table('test', metadata,
+ Column('id', Integer, primary_key=True),
+ *[Column("field%d" % fnum, field_type)
+ for fnum in range(num_fields)])
+
+def insert(test_table, num_fields, num_records, genvalue, verbose=True):
+ if verbose:
+ print "building insert values...",
+ sys.stdout.flush()
+ values = [dict(("field%d" % fnum, genvalue(rnum, fnum))
+ for fnum in range(num_fields))
+ for rnum in range(num_records)]
+ if verbose:
+ print "inserting...",
+ sys.stdout.flush()
+ def db_insert():
+ test_table.insert().execute(values)
+ sys.modules['__main__'].db_insert = db_insert
+ timing = timeit.timeit("db_insert()",
+ "from __main__ import db_insert",
+ number=1)
+ if verbose:
+ print "%s" % round(timing, 3)
+
+def check_result(results, num_fields, genvalue, verbose=True):
+ if verbose:
+ print "checking...",
+ sys.stdout.flush()
+ for rnum, row in enumerate(results):
+ expected = tuple([rnum + 1] +
+ [genvalue(rnum, fnum) for fnum in range(num_fields)])
+ assert row == expected, "got: %s\nexpected: %s" % (row, expected)
+ return True
+
+def avgdev(values, comparison):
+ return sum(value - comparison for value in values) / len(values)
+
+def nicer_res(values, printvalues=False):
+ if printvalues:
+ print values
+ min_time = min(values)
+ return round(min_time, 3), round(avgdev(values, min_time), 2)
+
+def profile_func(func_name, verbose=True):
+ if verbose:
+ print "profiling...",
+ sys.stdout.flush()
+ cProfile.run('%s()' % func_name, 'prof')
+
+def time_func(func_name, num_tests=1, verbose=True):
+ if verbose:
+ print "timing...",
+ sys.stdout.flush()
+ timings = timeit.repeat('%s()' % func_name,
+ "from __main__ import %s" % func_name,
+ number=num_tests, repeat=5)
+ avg, dev = nicer_res(timings)
+ if verbose:
+ print "%s (%s)" % (avg, dev)
+ else:
+ print avg
+
+def profile_and_time(func_name, num_tests=1):
+ profile_func(func_name)
+ time_func(func_name, num_tests)
+
+def iter_results(raw_results):
+ return [tuple(row) for row in raw_results]
+
+def getattr_results(raw_results):
+ return [
+ (r.id,
+ r.field0, r.field1, r.field2, r.field3, r.field4,
+ r.field5, r.field6, r.field7, r.field8, r.field9)
+ for r in raw_results]
+
+def fetchall(test_table):
+ def results():
+ return test_table.select().order_by(test_table.c.id).execute() \
+ .fetchall()
+ return results
+
+def hashable_set(l):
+ hashables = []
+ for o in l:
+ try:
+ hash(o)
+ hashables.append(o)
+ except:
+ pass
+ return set(hashables)
+
+def prepare(field_type, genvalue, engineurl='sqlite://',
+ num_fields=10, num_records=1000, freshdata=True, verbose=True):
+ global metadata
+ metadata.clear()
+ metadata.bind = engineurl
+ test_table = gen_table(num_fields, field_type, metadata)
+ if freshdata:
+ metadata.drop_all()
+ metadata.create_all()
+ insert(test_table, num_fields, num_records, genvalue, verbose)
+ return test_table
+
+def time_dbfunc(test_table, test_func, genvalue,
+ class_=None,
+ getresults_func=None,
+ num_fields=10, num_records=1000, num_tests=1,
+ check_results=check_result, profile=True,
+ check_leaks=True, print_leaks=False, verbose=True):
+ if verbose:
+ print "testing '%s'..." % test_func.__name__,
+ sys.stdout.flush()
+ if class_ is not None:
+ clear_mappers()
+ mapper(class_, test_table)
+ if getresults_func is None:
+ getresults_func = fetchall(test_table)
+ def test():
+ return test_func(getresults_func())
+ sys.modules['__main__'].test = test
+ if check_leaks:
+ gc.collect()
+ objects_before = gc.get_objects()
+ num_objects_before = len(objects_before)
+ hashable_objects_before = hashable_set(objects_before)
+# gc.set_debug(gc.DEBUG_LEAK)
+ if check_results:
+ check_results(test(), num_fields, genvalue, verbose)
+ if check_leaks:
+ gc.collect()
+ objects_after = gc.get_objects()
+ num_objects_after = len(objects_after)
+ num_leaks = num_objects_after - num_objects_before
+ hashable_objects_after = hashable_set(objects_after)
+ diff = hashable_objects_after - hashable_objects_before
+ ldiff = len(diff)
+ if print_leaks and ldiff < num_records:
+ print "\n*** hashable objects leaked (%d) ***" % ldiff
+ print '\n'.join(map(str, diff))
+ print "***\n"
+
+ if num_leaks > num_records:
+ print "(leaked: %d !)" % num_leaks,
+ if profile:
+ profile_func('test', verbose)
+ time_func('test', num_tests, verbose)
+
+def profile_and_time_dbfunc(test_func, field_type, genvalue,
+ class_=None,
+ getresults_func=None,
+ engineurl='sqlite://', freshdata=True,
+ num_fields=10, num_records=1000, num_tests=1,
+ check_results=check_result, profile=True,
+ check_leaks=True, print_leaks=False, verbose=True):
+ test_table = prepare(field_type, genvalue, engineurl,
+ num_fields, num_records, freshdata, verbose)
+ time_dbfunc(test_table, test_func, genvalue, class_,
+ getresults_func,
+ num_fields, num_records, num_tests,
+ check_results, profile,
+ check_leaks, print_leaks, verbose)
diff --git a/test/sql/test_query.py b/test/sql/test_query.py
index 345ecef67..5433cb92f 100644
--- a/test/sql/test_query.py
+++ b/test/sql/test_query.py
@@ -701,21 +701,21 @@ class QueryTest(TestBase):
Column('shadow_name', VARCHAR(20)),
Column('parent', VARCHAR(20)),
Column('row', VARCHAR(40)),
- Column('__parent', VARCHAR(20)),
- Column('__row', VARCHAR(20)),
+ Column('_parent', VARCHAR(20)),
+ Column('_row', VARCHAR(20)),
)
shadowed.create(checkfirst=True)
try:
- shadowed.insert().execute(shadow_id=1, shadow_name='The Shadow', parent='The Light', row='Without light there is no shadow', __parent='Hidden parent', __row='Hidden row')
+ shadowed.insert().execute(shadow_id=1, shadow_name='The Shadow', parent='The Light', row='Without light there is no shadow', _parent='Hidden parent', _row='Hidden row')
r = shadowed.select(shadowed.c.shadow_id==1).execute().first()
self.assert_(r.shadow_id == r['shadow_id'] == r[shadowed.c.shadow_id] == 1)
self.assert_(r.shadow_name == r['shadow_name'] == r[shadowed.c.shadow_name] == 'The Shadow')
self.assert_(r.parent == r['parent'] == r[shadowed.c.parent] == 'The Light')
self.assert_(r.row == r['row'] == r[shadowed.c.row] == 'Without light there is no shadow')
- self.assert_(r['__parent'] == 'Hidden parent')
- self.assert_(r['__row'] == 'Hidden row')
+ self.assert_(r['_parent'] == 'Hidden parent')
+ self.assert_(r['_row'] == 'Hidden row')
try:
- print r.__parent, r.__row
+ print r._parent, r._row
self.fail('Should not allow access to private attributes')
except AttributeError:
pass # expected