summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/engine/base.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/engine/base.py')
-rw-r--r--lib/sqlalchemy/engine/base.py322
1 files changed, 147 insertions, 175 deletions
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py
index 2210ba408..2789ea4d9 100644
--- a/lib/sqlalchemy/engine/base.py
+++ b/lib/sqlalchemy/engine/base.py
@@ -1570,7 +1570,7 @@ def _proxy_connection_cls(cls, proxy):
class RowProxy(object):
- """Proxy a single cursor row for a parent ResultProxy.
+ """Proxy values from a single cursor row.
Mostly follows "ordered dictionary" behavior, mapping result
values to the string-based column name, the integer position of
@@ -1582,19 +1582,13 @@ class RowProxy(object):
__slots__ = ['__parent', '__row', '__colfuncs']
def __init__(self, parent, row):
- """RowProxy objects are constructed by ResultProxy objects."""
self.__parent = parent
self.__row = row
self.__colfuncs = parent._colfuncs
if self.__parent._echo:
- self.__parent.context.engine.logger.debug("Row %r", row)
+ self.__parent.logger.debug("Row %r", row)
- def close(self):
- """Close the parent ResultProxy."""
-
- self.__parent.close()
-
def __contains__(self, key):
return self.__parent._has_key(self.__row, key)
@@ -1604,7 +1598,7 @@ class RowProxy(object):
def __getstate__(self):
return {
'__row':[self.__colfuncs[i][0](self.__row) for i in xrange(len(self.__row))],
- '__parent':PickledResultProxy(self.__parent)
+ '__parent':self.__parent
}
def __setstate__(self, d):
@@ -1680,62 +1674,140 @@ class RowProxy(object):
def itervalues(self):
return iter(self)
-class PickledResultProxy(object):
- """a 'mock' ResultProxy used by a RowProxy being pickled."""
-
- _echo = False
+class ResultMetaData(object):
+ """Handle cursor.description, applying additional info from an execution context."""
- def __init__(self, resultproxy):
- self._pickled_colfuncs = \
- dict(
- (key, (i, type_))
- for key, (fn, i, type_) in resultproxy._colfuncs.iteritems()
- if isinstance(key, (basestring, int))
- )
- self._keys = resultproxy.keys
-
- @util.memoized_property
- def _colfuncs(self):
- d = {}
- for key, (index, type_) in self._pickled_colfuncs.iteritems():
- if type_ == 'ambiguous':
- d[key] = (ResultProxy._ambiguous_processor(key), index, type_)
+ def __init__(self, parent, metadata):
+ self._colfuncs = colfuncs = {}
+ self.keys = []
+ self._echo = parent._echo
+ context = parent.context
+ dialect = context.dialect
+ typemap = dialect.dbapi_type_map
+
+ for i, (colname, coltype) in enumerate(m[0:2] for m in metadata):
+ if dialect.description_encoding:
+ colname = colname.decode(dialect.description_encoding)
+
+ if '.' in colname:
+ # sqlite will in some circumstances prepend table name to
+ # colnames, so strip
+ origname = colname
+ colname = colname.split('.')[-1]
else:
- d[key] = (operator.itemgetter(index), index, "itemgetter")
- return d
-
+ origname = None
+
+ if context.result_map:
+ try:
+ name, obj, type_ = context.result_map[colname.lower()]
+ except KeyError:
+ name, obj, type_ = \
+ colname, None, typemap.get(coltype, types.NULLTYPE)
+ else:
+ name, obj, type_ = (colname, None, typemap.get(coltype, types.NULLTYPE))
+
+ 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")
+
+ # indexes as keys
+ colfuncs[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")
+
+ # store the "origname" if we truncated (sqlite only)
+ if origname and \
+ colfuncs.setdefault(origname.lower(), rec) is not rec:
+ colfuncs[name.lower()] = (self._ambiguous_processor(origname), i, "ambiguous")
+
+ if dialect.requires_name_normalize:
+ colname = dialect.normalize_name(colname)
+
+ self.keys.append(colname)
+ if obj:
+ for o in obj:
+ colfuncs[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):
- if key in self._colfuncs:
- return self._colfuncs[key]
-
+ funcs = self._colfuncs
+
if isinstance(key, basestring):
key = key.lower()
- if key in self._colfuncs:
- return self._colfuncs[key]
+ if key in funcs:
+ return funcs[key]
+ # 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 self._colfuncs:
- return self._colfuncs[key._label.lower()]
- elif hasattr(key, 'name') and key.name.lower() in self._colfuncs:
- return self._colfuncs[key.name.lower()]
-
+ 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
-
- def close(self):
- pass
-
+
def _has_key(self, row, key):
- return self._key_fallback(key) is not None
-
- @property
- def keys(self):
- return self._keys
-
+ if key in self._colfuncs:
+ return True
+ else:
+ key = self._key_fallback(key)
+ return key is not None
+
+ @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()
+ if isinstance(key, (basestring, int))
+ ),
+ '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")
+ self.keys = state['keys']
+ self._echo = False
class ResultProxy(object):
"""Wraps a DB-API cursor object to provide easier access to row columns.
@@ -1752,11 +1824,10 @@ class ResultProxy(object):
col3 = row[mytable.c.mycol] # access via Column object.
- ResultProxy also contains a map of TypeEngine objects and will
- invoke the appropriate ``result_processor()`` method before
- returning columns, as well as the ExecutionContext corresponding
- to the statement execution. It provides several methods for which
- to obtain information from the underlying ExecutionContext.
+ ``ResultProxy`` also handles post-processing of result column
+ data using ``TypeEngine`` objects, which are referenced from
+ the originating SQL statement that produced this result set.
+
"""
_process_row = RowProxy
@@ -1770,7 +1841,14 @@ class ResultProxy(object):
self.connection = context.root_connection
self._echo = context.engine._should_log_info
self._init_metadata()
-
+
+ def _init_metadata(self):
+ metadata = self._cursor_description()
+ if metadata is None:
+ self._metadata = None
+ else:
+ self._metadata = ResultMetaData(self, metadata)
+
@util.memoized_property
def rowcount(self):
"""Return the 'rowcount' for this result.
@@ -1809,6 +1887,8 @@ class ResultProxy(object):
return self.cursor.lastrowid
def _cursor_description(self):
+ """May be overridden by subclasses."""
+
return self.cursor.description
def _autoclose(self):
@@ -1825,110 +1905,7 @@ class ResultProxy(object):
self.close() # autoclose
return self
-
- def _init_metadata(self):
- self._metadata = metadata = self._cursor_description()
- if metadata is None:
- return
-
- self._colfuncs = colfuncs = {}
- self.keys = []
-
- typemap = self.dialect.dbapi_type_map
-
- for i, (colname, coltype) in enumerate(m[0:2] for m in metadata):
- if self.dialect.description_encoding:
- colname = colname.decode(self.dialect.description_encoding)
-
- if '.' in colname:
- # sqlite will in some circumstances prepend table name to
- # colnames, so strip
- origname = colname
- colname = colname.split('.')[-1]
- else:
- origname = None
-
- if self.context.result_map:
- try:
- name, obj, type_ = self.context.result_map[colname.lower()]
- except KeyError:
- name, obj, type_ = \
- colname, None, typemap.get(coltype, types.NULLTYPE)
- else:
- name, obj, type_ = (colname, None, typemap.get(coltype, types.NULLTYPE))
-
- processor = type_.dialect_impl(self.dialect).\
- result_processor(self.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")
-
- # indexes as keys
- colfuncs[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")
-
- # store the "origname" if we truncated (sqlite only)
- if origname and \
- colfuncs.setdefault(origname.lower(), rec) is not rec:
- colfuncs[name.lower()] = (self._ambiguous_processor(origname), i, "ambiguous")
-
- if self.dialect.requires_name_normalize:
- colname = self.dialect.normalize_name(colname)
-
- self.keys.append(colname)
- if obj:
- for o in obj:
- colfuncs[o] = rec
-
- if self._echo:
- self.context.engine.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._metadata))]
-
- def _key_fallback(self, key):
- funcs = self._colfuncs
-
- if isinstance(key, basestring):
- key = key.lower()
- if key in funcs:
- return funcs[key]
-
- # 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
-
- @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 close(self):
"""Close this ResultProxy.
@@ -1953,13 +1930,6 @@ class ResultProxy(object):
if self.connection.should_close_with_result:
self.connection.close()
- def _has_key(self, row, key):
- if key in self._colfuncs:
- return True
- else:
- key = self._key_fallback(key)
- return key is not None
-
def __iter__(self):
while True:
row = self.fetchone()
@@ -2048,7 +2018,8 @@ class ResultProxy(object):
try:
process_row = self._process_row
- l = [process_row(self, row) for row in self._fetchall_impl()]
+ metadata = self._metadata
+ l = [process_row(metadata, row) for row in self._fetchall_impl()]
self.close()
return l
except Exception, e:
@@ -2065,7 +2036,8 @@ class ResultProxy(object):
try:
process_row = self._process_row
- l = [process_row(self, row) for row in self._fetchmany_impl(size)]
+ metadata = self._metadata
+ l = [process_row(metadata, row) for row in self._fetchmany_impl(size)]
if len(l) == 0:
self.close()
return l
@@ -2084,7 +2056,7 @@ class ResultProxy(object):
try:
row = self._fetchone_impl()
if row is not None:
- return self._process_row(self, row)
+ return self._process_row(self._metadata, row)
else:
self.close()
return None
@@ -2106,7 +2078,7 @@ class ResultProxy(object):
try:
if row is not None:
- return self._process_row(self, row)
+ return self._process_row(self._metadata, row)
else:
return None
finally:
@@ -2195,7 +2167,7 @@ class FullyBufferedResultProxy(ResultProxy):
def _init_metadata(self):
super(FullyBufferedResultProxy, self)._init_metadata()
self.__rowbuffer = self._buffer_rows()
-
+
def _buffer_rows(self):
return self.cursor.fetchall()
@@ -2240,13 +2212,13 @@ class BufferedColumnResultProxy(ResultProxy):
def _init_metadata(self):
super(BufferedColumnResultProxy, self)._init_metadata()
- self._orig_colfuncs = self._colfuncs
- self._colfuncs = colfuncs = {}
+ 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._orig_colfuncs.iteritems():
+ for k, (colfunc, index, type_) in self._metadata._orig_colfuncs.iteritems():
if type_ == "colfunc":
colfuncs[k] = (operator.itemgetter(index), index, "itemgetter")
else: