summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/engine/base.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-01-20 17:48:43 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2010-01-20 17:48:43 +0000
commitc3702fa5169fef29ca13427abea2f6d41449c658 (patch)
treecf6fdf3ea8d1ab8d75189aa552c7d7b1566766fb /lib/sqlalchemy/engine/base.py
parent81372486d9a10305f47f76c4455a5e6df02e586c (diff)
downloadsqlalchemy-c3702fa5169fef29ca13427abea2f6d41449c658.tar.gz
moved the metadata step of ResultProxy into a ResultMetaData object. this also replaces PickledResultProxy.
Allows RowProxy objects to reference just the metadata they need and provides the "core" of ResultProxy detached from the object itself, allowing ResultProxy implementations to vary more easily. will also enable [ticket:1635]
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: