summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2019-06-04 17:29:20 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2020-02-21 17:53:33 -0500
commitf559f378c47811b5528ad1769cb86925e85fd1e5 (patch)
treefd8325501a96cf1e4280c15f267f63b2af7b5f97 /lib/sqlalchemy/orm
parent93b7767d00267ebe149cabcae7246b6796352eb8 (diff)
downloadsqlalchemy-f559f378c47811b5528ad1769cb86925e85fd1e5.tar.gz
Result initial introduction
This builds on cc718cccc0bf8a01abdf4068c7ea4f3 which moved RowProxy to Row, allowing Row to be more like a named tuple. - KeyedTuple in ORM is replaced with Row - ResultSetMetaData broken out into "simple" and "cursor" versions for ORM and Core, as well as LegacyCursor version. - Row now has _mapping attribute that supplies full mapping behavior. Row and SimpleRow both have named tuple behavior otherwise. LegacyRow has some mapping features on the tuple which emit deprecation warnings (e.g. keys(), values(), etc). the biggest change for mapping->tuple is the behavior of __contains__ which moves from testing of "key in row" to "value in row". - ResultProxy breaks into ResultProxy and FutureResult (interim), the latter has the newer APIs. Made available to dialects using execution options. - internal reflection methods and most tests move off of implicit Row mapping behavior and move to row._mapping, result.mappings() method using future result - a new strategy system for cursor handling replaces the various subclasses of RowProxy - some execution context adjustments. We will leave EC in but refined things like get_result_proxy() and out parameter handling. Dialects for 1.4 will need to adjust from get_result_proxy() to get_result_cursor_strategy(), if they are using this method - out parameter handling now accommodated by get_out_parameter_values() EC method. Oracle changes for this. external dialect for DB2 for example will also need to adjust for this. - deprecate case_insensitive flag for engine / result, this feature is not used mapping-methods on Row are deprecated, and replaced with Row._mapping.<meth>, including: row.keys() -> use row._mapping.keys() row.items() -> use row._mapping.items() row.values() -> use row._mapping.values() key in row -> use key in row._mapping int in row -> use int < len(row) Fixes: #4710 Fixes: #4878 Change-Id: Ieb9085e9bcff564359095b754da9ae0af55679f0
Diffstat (limited to 'lib/sqlalchemy/orm')
-rw-r--r--lib/sqlalchemy/orm/loading.py48
-rw-r--r--lib/sqlalchemy/orm/mapper.py2
-rw-r--r--lib/sqlalchemy/orm/persistence.py4
-rw-r--r--lib/sqlalchemy/orm/query.py15
4 files changed, 38 insertions, 31 deletions
diff --git a/lib/sqlalchemy/orm/loading.py b/lib/sqlalchemy/orm/loading.py
index 617f027d9..193980e6c 100644
--- a/lib/sqlalchemy/orm/loading.py
+++ b/lib/sqlalchemy/orm/loading.py
@@ -28,6 +28,7 @@ from .util import aliased
from .util import state_str
from .. import exc as sa_exc
from .. import util
+from ..engine import result_tuple
from ..sql import util as sql_util
@@ -56,7 +57,7 @@ def instances(query, cursor, context):
)
try:
- (process, labels) = list(
+ (process, labels, extra) = list(
zip(
*[
query_entity.row_processor(query, context, cursor)
@@ -66,7 +67,7 @@ def instances(query, cursor, context):
)
if not single_entity:
- keyed_tuple = util.lightweight_named_tuple("result", labels)
+ keyed_tuple = result_tuple(labels, extra)
while True:
context.partials = {}
@@ -138,7 +139,9 @@ def merge_result(querylib, query, iterator, load=True):
]
result = []
keys = [ent._label_name for ent in query._entities]
- keyed_tuple = util.lightweight_named_tuple("result", keys)
+ keyed_tuple = result_tuple(
+ keys, [ent.entities for ent in query._entities]
+ )
for row in iterator:
newrow = list(row)
for i in mapped_entities:
@@ -190,7 +193,6 @@ def load_on_ident(
query, key, refresh_state=None, with_for_update=None, only_load_props=None
):
"""Load the given identity key from the database."""
-
if key is not None:
ident = key[1]
identity_token = key[2]
@@ -452,10 +454,19 @@ def _instance_processor(
instance_state = attributes.instance_state
instance_dict = attributes.instance_dict
session_id = context.session.hash_key
- version_check = context.version_check
runid = context.runid
identity_token = context.identity_token
+ version_check = context.version_check
+ if version_check:
+ version_id_col = mapper.version_id_col
+ if version_id_col is not None:
+ if adapter:
+ version_id_col = adapter.columns[version_id_col]
+ version_id_getter = result._getter(version_id_col)
+ else:
+ version_id_getter = None
+
if not refresh_state and _polymorphic_from is not None:
key = ("loader", path.path)
if key in context.attributes and context.attributes[key].strategy == (
@@ -539,8 +550,10 @@ def _instance_processor(
currentload = not isnew
loaded_instance = False
- if version_check and not currentload:
- _validate_version_id(mapper, state, dict_, row, adapter)
+ if version_check and version_id_getter and not currentload:
+ _validate_version_id(
+ mapper, state, dict_, row, version_id_getter
+ )
else:
# create a new instance
@@ -667,7 +680,7 @@ def _instance_processor(
def ensure_no_pk(row):
identitykey = (
identity_class,
- tuple([row[column] for column in pk_cols]),
+ tuple_getter(row),
identity_token,
)
if not is_not_primary_key(identitykey[1]):
@@ -812,20 +825,11 @@ def _populate_partial(
return to_load
-def _validate_version_id(mapper, state, dict_, row, adapter):
+def _validate_version_id(mapper, state, dict_, row, getter):
- version_id_col = mapper.version_id_col
-
- if version_id_col is None:
- return
-
- if adapter:
- version_id_col = adapter.columns[version_id_col]
-
- if (
- mapper._get_state_attr_by_column(state, dict_, mapper.version_id_col)
- != row[version_id_col]
- ):
+ if mapper._get_state_attr_by_column(
+ state, dict_, mapper.version_id_col
+ ) != getter(row):
raise orm_exc.StaleDataError(
"Instance '%s' has version id '%s' which "
"does not match database-loaded version id '%s'."
@@ -834,7 +838,7 @@ def _validate_version_id(mapper, state, dict_, row, adapter):
mapper._get_state_attr_by_column(
state, dict_, mapper.version_id_col
),
- row[version_id_col],
+ getter(row),
)
)
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index 82e68fd07..b84d41260 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -2631,7 +2631,7 @@ class Mapper(sql_base.HasCacheKey, InspectionAttr):
"""Return an identity-map key for use in storing/retrieving an
item from the identity map.
- :param row: A :class:`.RowProxy` instance. The columns which are
+ :param row: A :class:`.Row` instance. The columns which are
mapped by this :class:`.Mapper` should be locatable in the row,
preferably via the :class:`.Column` object directly (as is the case
when a :func:`.select` construct is executed), or via string names of
diff --git a/lib/sqlalchemy/orm/persistence.py b/lib/sqlalchemy/orm/persistence.py
index 31b8b0a20..95c5f8fa2 100644
--- a/lib/sqlalchemy/orm/persistence.py
+++ b/lib/sqlalchemy/orm/persistence.py
@@ -1522,7 +1522,7 @@ def _postfetch(
if returning_cols:
row = result.context.returned_defaults
if row is not None:
- for col in returning_cols:
+ for row_value, col in zip(row, returning_cols):
# pk cols returned from insert are handled
# distinctly, don't step on the values here
if col.primary_key and result.context.isinsert:
@@ -1534,7 +1534,7 @@ def _postfetch(
# when using declarative w/ single table inheritance
prop = mapper._columntoproperty.get(col)
if prop:
- dict_[prop.key] = row[col]
+ dict_[prop.key] = row_value
if refresh_flush:
load_evt_attrs.append(prop.key)
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index f19ec5673..d237aa3bf 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -47,6 +47,7 @@ from .. import inspection
from .. import log
from .. import sql
from .. import util
+from ..engine import result_tuple
from ..sql import coercions
from ..sql import expression
from ..sql import roles
@@ -56,6 +57,7 @@ from ..sql.base import _generative
from ..sql.base import ColumnCollection
from ..sql.base import Generative
from ..sql.selectable import ForUpdateArg
+from ..util import collections_abc
__all__ = ["Query", "QueryContext", "aliased"]
@@ -3320,7 +3322,7 @@ class Query(Generative):
"""
try:
ret = self.one()
- if not isinstance(ret, tuple):
+ if not isinstance(ret, collections_abc.Sequence):
return ret
return ret[0]
except orm_exc.NoResultFound:
@@ -4259,7 +4261,7 @@ class _MapperEntity(_QueryEntity):
polymorphic_discriminator=self._polymorphic_discriminator,
)
- return _instance, self._label_name
+ return _instance, self._label_name, self.entities
def setup_context(self, query, context):
adapter = self._get_entity_clauses(query, context)
@@ -4414,7 +4416,7 @@ class Bundle(InspectionAttr):
:ref:`bundles` - includes an example of subclassing.
"""
- keyed_tuple = util.lightweight_named_tuple("result", labels)
+ keyed_tuple = result_tuple(labels, [() for l in labels])
def proc(row):
return keyed_tuple([proc(row) for proc in procs])
@@ -4517,7 +4519,7 @@ class _BundleEntity(_QueryEntity):
ent.setup_context(query, context)
def row_processor(self, query, context, result):
- procs, labels = zip(
+ procs, labels, extra = zip(
*[
ent.row_processor(query, context, result)
for ent in self._entities
@@ -4526,7 +4528,7 @@ class _BundleEntity(_QueryEntity):
proc = self.bundle.create_row_processor(query, procs, labels)
- return proc, self._label_name
+ return proc, self._label_name, ()
class _ColumnEntity(_QueryEntity):
@@ -4675,7 +4677,8 @@ class _ColumnEntity(_QueryEntity):
column = context.adapter.columns[column]
getter = result._getter(column)
- return getter, self._label_name
+
+ return getter, self._label_name, (self.expr, self.column)
def setup_context(self, query, context):
column = query._adapt_clause(self.column, False, True)