diff options
author | mike bayer <mike_mp@zzzcomputing.com> | 2020-06-19 15:21:39 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@bbpush.zzzcomputing.com> | 2020-06-19 15:21:39 +0000 |
commit | 15dd104d5bd1c0819628dec6d9466be38dc5130f (patch) | |
tree | c27cc01c959170895d8389408b12de8921e4b532 | |
parent | 332b8b78f6ba728b0994457420cb66e1c9bd5846 (diff) | |
parent | 699da7ecb96e9e1af8df8072d53199a560311260 (diff) | |
download | sqlalchemy-15dd104d5bd1c0819628dec6d9466be38dc5130f.tar.gz |
Merge "perf tweaks"
-rw-r--r-- | lib/sqlalchemy/engine/util.py | 9 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/context.py | 86 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/query.py | 3 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/base.py | 10 | ||||
-rw-r--r-- | test/orm/test_froms.py | 12 | ||||
-rw-r--r-- | test/orm/test_lazy_relations.py | 4 | ||||
-rw-r--r-- | test/orm/test_query.py | 35 |
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() |