summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormike bayer <mike_mp@zzzcomputing.com>2020-06-19 15:21:39 +0000
committerGerrit Code Review <gerrit@bbpush.zzzcomputing.com>2020-06-19 15:21:39 +0000
commit15dd104d5bd1c0819628dec6d9466be38dc5130f (patch)
treec27cc01c959170895d8389408b12de8921e4b532
parent332b8b78f6ba728b0994457420cb66e1c9bd5846 (diff)
parent699da7ecb96e9e1af8df8072d53199a560311260 (diff)
downloadsqlalchemy-15dd104d5bd1c0819628dec6d9466be38dc5130f.tar.gz
Merge "perf tweaks"
-rw-r--r--lib/sqlalchemy/engine/util.py9
-rw-r--r--lib/sqlalchemy/orm/context.py86
-rw-r--r--lib/sqlalchemy/orm/query.py3
-rw-r--r--lib/sqlalchemy/sql/base.py10
-rw-r--r--test/orm/test_froms.py12
-rw-r--r--test/orm/test_lazy_relations.py4
-rw-r--r--test/orm/test_query.py35
7 files changed, 107 insertions, 52 deletions
diff --git a/lib/sqlalchemy/engine/util.py b/lib/sqlalchemy/engine/util.py
index 8fb04646f..fc0260ae2 100644
--- a/lib/sqlalchemy/engine/util.py
+++ b/lib/sqlalchemy/engine/util.py
@@ -8,6 +8,7 @@
from .. import exc
from .. import util
from ..util import collections_abc
+from ..util import immutabledict
def connection_memoize(key):
@@ -85,9 +86,11 @@ _no_kw = util.immutabledict()
def _distill_params_20(params):
+ # TODO: this has to be in C
if params is None:
return _no_tuple, _no_kw, []
- elif isinstance(params, collections_abc.MutableSequence): # list
+ elif isinstance(params, list):
+ # collections_abc.MutableSequence): # avoid abc.__instancecheck__
if params and not isinstance(
params[0], (collections_abc.Mapping, tuple)
):
@@ -99,7 +102,9 @@ def _distill_params_20(params):
return tuple(params), _no_kw, params
elif isinstance(
params,
- (collections_abc.Sequence, collections_abc.Mapping), # tuple or dict
+ (tuple, dict, immutabledict),
+ # avoid abc.__instancecheck__
+ # (collections_abc.Sequence, collections_abc.Mapping),
):
return _no_tuple, params, [params]
else:
diff --git a/lib/sqlalchemy/orm/context.py b/lib/sqlalchemy/orm/context.py
index 588b83571..f380229e1 100644
--- a/lib/sqlalchemy/orm/context.py
+++ b/lib/sqlalchemy/orm/context.py
@@ -2245,7 +2245,7 @@ class _BundleEntity(_QueryEntity):
class _ColumnEntity(_QueryEntity):
- __slots__ = ()
+ __slots__ = ("_fetch_column", "_row_processor")
@classmethod
def _for_columns(cls, compile_state, columns, parent_bundle=None):
@@ -2275,6 +2275,44 @@ class _ColumnEntity(_QueryEntity):
def use_id_for_hash(self):
return not self.column.type.hashable
+ def row_processor(self, context, result):
+ compile_state = context.compile_state
+
+ # the resulting callable is entirely cacheable so just return
+ # it if we already made one
+ if self._row_processor is not None:
+ return self._row_processor
+
+ # retrieve the column that would have been set up in
+ # setup_compile_state, to avoid doing redundant work
+ if self._fetch_column is not None:
+ column = self._fetch_column
+ else:
+ # fetch_column will be None when we are doing a from_statement
+ # and setup_compile_state may not have been called.
+ column = self.column
+
+ # previously, the RawColumnEntity didn't look for from_obj_alias
+ # however I can't think of a case where we would be here and
+ # we'd want to ignore it if this is the from_statement use case.
+ # it's not really a use case to have raw columns + from_statement
+ if compile_state._from_obj_alias:
+ column = compile_state._from_obj_alias.columns[column]
+
+ if column._annotations:
+ # annotated columns perform more slowly in compiler and
+ # result due to the __eq__() method, so use deannotated
+ column = column._deannotate()
+
+ if compile_state.compound_eager_adapter:
+ column = compile_state.compound_eager_adapter.columns[column]
+
+ getter = result._getter(column)
+
+ ret = getter, self._label_name, self._extra_entities
+ self._row_processor = ret
+ return ret
+
class _RawColumnEntity(_ColumnEntity):
entity_zero = None
@@ -2303,28 +2341,11 @@ class _RawColumnEntity(_ColumnEntity):
self.column._from_objects[0] if self.column._from_objects else None
)
self._extra_entities = (self.expr, self.column)
+ self._fetch_column = self._row_processor = None
def corresponds_to(self, entity):
return False
- def row_processor(self, context, result):
- if ("fetch_column", self) in context.attributes:
- column = context.attributes[("fetch_column", self)]
- else:
- column = self.column
-
- if column._annotations:
- # annotated columns perform more slowly in compiler and
- # result due to the __eq__() method, so use deannotated
- column = column._deannotate()
-
- compile_state = context.compile_state
- if compile_state.compound_eager_adapter:
- column = compile_state.compound_eager_adapter.columns[column]
-
- getter = result._getter(column)
- return getter, self._label_name, self._extra_entities
-
def setup_compile_state(self, compile_state):
current_adapter = compile_state._get_current_adapter()
if current_adapter:
@@ -2338,7 +2359,7 @@ class _RawColumnEntity(_ColumnEntity):
column = column._deannotate()
compile_state.primary_columns.append(column)
- compile_state.attributes[("fetch_column", self)] = column
+ self._fetch_column = column
class _ORMColumnEntity(_ColumnEntity):
@@ -2386,6 +2407,7 @@ class _ORMColumnEntity(_ColumnEntity):
compile_state._has_orm_entities = True
self.column = column
+ self._fetch_column = self._row_processor = None
self._extra_entities = (self.expr, self.column)
@@ -2407,27 +2429,6 @@ class _ORMColumnEntity(_ColumnEntity):
self.entity_zero
) and entity.common_parent(self.entity_zero)
- def row_processor(self, context, result):
- compile_state = context.compile_state
-
- if ("fetch_column", self) in context.attributes:
- column = context.attributes[("fetch_column", self)]
- else:
- column = self.column
- if compile_state._from_obj_alias:
- column = compile_state._from_obj_alias.columns[column]
-
- if column._annotations:
- # annotated columns perform more slowly in compiler and
- # result due to the __eq__() method, so use deannotated
- column = column._deannotate()
-
- if compile_state.compound_eager_adapter:
- column = compile_state.compound_eager_adapter.columns[column]
-
- getter = result._getter(column)
- return getter, self._label_name, self._extra_entities
-
def setup_compile_state(self, compile_state):
current_adapter = compile_state._get_current_adapter()
if current_adapter:
@@ -2460,5 +2461,4 @@ class _ORMColumnEntity(_ColumnEntity):
compile_state._fallback_from_clauses.append(ezero.selectable)
compile_state.primary_columns.append(column)
-
- compile_state.attributes[("fetch_column", self)] = column
+ self._fetch_column = column
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index abbba4172..458217e22 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -1637,8 +1637,7 @@ class Query(
"params() takes zero or one positional argument, "
"which is a dictionary."
)
- params = dict(self.load_options._params)
- params.update(kwargs)
+ params = self.load_options._params.union(kwargs)
self.load_options += {"_params": params}
@_generative
diff --git a/lib/sqlalchemy/sql/base.py b/lib/sqlalchemy/sql/base.py
index 5f2ce8f14..4c603b6dd 100644
--- a/lib/sqlalchemy/sql/base.py
+++ b/lib/sqlalchemy/sql/base.py
@@ -495,8 +495,14 @@ class Generative(HasMemoized):
def _generate(self):
skip = self._memoized_keys
- s = self.__class__.__new__(self.__class__)
- s.__dict__ = {k: v for k, v in self.__dict__.items() if k not in skip}
+ cls = self.__class__
+ s = cls.__new__(cls)
+ if skip:
+ s.__dict__ = {
+ k: v for k, v in self.__dict__.items() if k not in skip
+ }
+ else:
+ s.__dict__ = self.__dict__.copy()
return s
diff --git a/test/orm/test_froms.py b/test/orm/test_froms.py
index 1fde343d8..62227ece2 100644
--- a/test/orm/test_froms.py
+++ b/test/orm/test_froms.py
@@ -2310,6 +2310,18 @@ class MixedEntitiesTest(QueryTest, AssertsCompiledSQL):
)
assert result == expected
+ def test_multi_columns_3(self):
+ User = self.classes.User
+ users = self.tables.users
+
+ sess = create_session()
+
+ q = sess.query(User.id, User.name)
+ stmt = select([users]).order_by(users.c.id)
+ q = q.from_statement(stmt)
+
+ eq_(q.all(), [(7, "jack"), (8, "ed"), (9, "fred"), (10, "chuck")])
+
def test_raw_columns(self):
addresses, users, User = (
self.tables.addresses,
diff --git a/test/orm/test_lazy_relations.py b/test/orm/test_lazy_relations.py
index 65158dbd4..c30086bea 100644
--- a/test/orm/test_lazy_relations.py
+++ b/test/orm/test_lazy_relations.py
@@ -439,9 +439,7 @@ class LazyTest(_fixtures.FixtureTest):
def process_query_conditionally(self, query):
"""process query during a lazyload"""
canary()
- params = dict(query.load_options._params)
- query.load_options += {"_params": params}
- query.load_options._params.update(dict(name=self.crit))
+ query.params.non_generative(query, dict(name=self.crit))
s = Session()
ed = s.query(User).options(MyOption("ed")).filter_by(name="ed").one()
diff --git a/test/orm/test_query.py b/test/orm/test_query.py
index a7cc82ddf..c2f0c4424 100644
--- a/test/orm/test_query.py
+++ b/test/orm/test_query.py
@@ -4440,6 +4440,26 @@ class TextTest(QueryTest, AssertsCompiledSQL):
self.assert_sql_count(testing.db, go, 1)
+ def test_textual_select_orm_columns(self):
+ # test that columns using column._label match, as well as that
+ # ordering doesn't matter.
+ User = self.classes.User
+ Address = self.classes.Address
+ users = self.tables.users
+ addresses = self.tables.addresses
+
+ s = create_session()
+ q = s.query(User.name, User.id, Address.id).from_statement(
+ text(
+ "select users.name AS users_name, users.id AS users_id, "
+ "addresses.id AS addresses_id FROM users JOIN addresses "
+ "ON users.id = addresses.user_id WHERE users.id=8 "
+ "ORDER BY addresses.id"
+ ).columns(users.c.name, users.c.id, addresses.c.id)
+ )
+
+ eq_(q.all(), [("ed", 8, 2), ("ed", 8, 3), ("ed", 8, 4)])
+
@testing.combinations(
(
False,
@@ -4564,6 +4584,21 @@ class TextTest(QueryTest, AssertsCompiledSQL):
[User(id=7), User(id=8), User(id=9), User(id=10)],
)
+ def test_columns_via_textasfrom_from_statement(self):
+ User = self.classes.User
+ s = create_session()
+
+ eq_(
+ s.query(User.id, User.name)
+ .from_statement(
+ text("select * from users order by id").columns(
+ id=Integer, name=String
+ )
+ )
+ .all(),
+ [(7, "jack"), (8, "ed"), (9, "fred"), (10, "chuck")],
+ )
+
def test_via_textasfrom_use_mapped_columns(self):
User = self.classes.User
s = create_session()