diff options
| -rw-r--r-- | doc/build/core/defaults.rst | 9 | ||||
| -rw-r--r-- | lib/sqlalchemy/dialects/mysql/base.py | 87 | ||||
| -rw-r--r-- | lib/sqlalchemy/ext/baked.py | 19 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/interfaces.py | 28 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/strategy_options.py | 89 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/schema.py | 6 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/selectable.py | 10 | ||||
| -rw-r--r-- | test/aaa_profiling/test_orm.py | 41 | ||||
| -rw-r--r-- | test/ext/test_baked.py | 9 | ||||
| -rw-r--r-- | test/orm/test_options.py | 800 | ||||
| -rw-r--r-- | test/orm/test_update_delete.py | 13 | ||||
| -rw-r--r-- | test/profiles.txt | 22 |
12 files changed, 139 insertions, 994 deletions
diff --git a/doc/build/core/defaults.rst b/doc/build/core/defaults.rst index 3d6124b43..3884faa86 100644 --- a/doc/build/core/defaults.rst +++ b/doc/build/core/defaults.rst @@ -292,6 +292,8 @@ behaviors such as seen with TIMESTAMP columns on some platforms, as well as custom triggers that invoke upon INSERT or UPDATE to generate a new value, may be called out using :class:`.FetchedValue` as a marker:: + from sqlalchemy.schema import FetchedValue + t = Table('test', meta, Column('id', Integer, primary_key=True), Column('abc', TIMESTAMP, server_default=FetchedValue()), @@ -312,6 +314,13 @@ returned. For details on using :class:`.FetchedValue` with the ORM, see :ref:`orm_server_defaults`. +.. warning:: The :paramref:`_schema.Column.server_onupdate` directive + **does not** currently produce MySQL's + "ON UPDATE CURRENT_TIMESTAMP()" clause. See + :ref:`mysql_timestamp_onupdate` for background on how to produce + this clause. + + .. seealso:: :ref:`orm_server_defaults` diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py index b34422e65..d3d7a8cce 100644 --- a/lib/sqlalchemy/dialects/mysql/base.py +++ b/lib/sqlalchemy/dialects/mysql/base.py @@ -670,10 +670,95 @@ with the ``unique=True`` setting present in the :attr:`_schema.Table.indexes` collection. +TIMESTAMP / DATETIME issues +--------------------------- + +.. _mysql_timestamp_onupdate: + +Rendering ON UPDATE CURRENT TIMESTAMP for MySQL's explicit_defaults_for_timestamp +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +MySQL has historically expanded the DDL for the :class:`_types.TIMESTAMP` +datatype into the phrase "TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE +CURRENT_TIMESTAMP", which includes non-standard SQL that automatically updates +the column with the current timestamp when an UPDATE occurs, eliminating the +usual need to use a trigger in such a case where server-side update changes are +desired. + +MySQL 5.6 introduced a new flag `explicit_defaults_for_timestamp +<http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html +#sysvar_explicit_defaults_for_timestamp>`_ which disables the above behavior, +and in MySQL 8 this flag defaults to true, meaning in order to get a MySQL +"on update timestamp" without changing this flag, the above DDL must be +rendered explicitly. Additionally, the same DDL is valid for use of the +``DATETIME`` datatype as well. + +SQLAlchemy's MySQL dialect does not yet have an option to generate +MySQL's "ON UPDATE CURRENT_TIMESTAMP" clause, noting that this is not a general +purpose "ON UPDATE" as there is no such syntax in standard SQL. SQLAlchemy's +:paramref:`_schema.Column.server_onupdate` parameter is currently not related +to this special MySQL behavior. + +To generate this DDL, make use of the :paramref:`_schema.Column.server_default` +parameter and pass a textual clause that also includes the ON UPDATE clause:: + + from sqlalchemy import Table, MetaData, Column, Integer, String, TIMESTAMP + from sqlalchemy import text + + metadata = MetaData() + + mytable = Table( + "mytable", + metadata, + Column('id', Integer, primary_key=True), + Column('data', String(50)), + Column( + 'last_updated', + TIMESTAMP, + server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") + ) + ) + +The same instructions apply to use of the :class:`_types.DateTime` and +:class:`_types.DATETIME` datatypes:: + + from sqlalchemy import DateTime + + mytable = Table( + "mytable", + metadata, + Column('id', Integer, primary_key=True), + Column('data', String(50)), + Column( + 'last_updated', + DateTime, + server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") + ) + ) + + +Even though the :paramref:`_schema.Column.server_onupdate` feature does not +generate this DDL, it still may be desirable to signal to the ORM that this +updated value should be fetched. This syntax looks like the following:: + + from sqlalchemy.schema import FetchedValue + + class MyClass(Base): + __tablename__ = 'mytable' + + id = Column(Integer, primary_key=True) + data = Column(String(50)) + last_updated = Column( + TIMESTAMP, + server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"), + server_onupdate=FetchedValue() + ) + + .. _mysql_timestamp_null: TIMESTAMP Columns and NULL --------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MySQL historically enforces that a column which specifies the TIMESTAMP datatype implicitly includes a default value of diff --git a/lib/sqlalchemy/ext/baked.py b/lib/sqlalchemy/ext/baked.py index 4f40637c5..ecbf871e2 100644 --- a/lib/sqlalchemy/ext/baked.py +++ b/lib/sqlalchemy/ext/baked.py @@ -194,18 +194,13 @@ class BakedQuery(object): if not cache_path: cache_path = effective_path - if cache_path.path[0].is_aliased_class: - # paths that are against an AliasedClass are unsafe to cache - # with since the AliasedClass is an ad-hoc object. - self.spoil(full=True) - else: - for opt in options: - if opt._is_legacy_option or opt._is_compile_state: - cache_key = opt._generate_path_cache_key(cache_path) - if cache_key is False: - self.spoil(full=True) - elif cache_key is not None: - key += cache_key + for opt in options: + if opt._is_legacy_option or opt._is_compile_state: + ck = opt._generate_cache_key() + if ck is None: + self.spoil(full=True) + else: + key += ck[0] self.add_criteria( lambda q: q._with_current_path(effective_path).options(*options), diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 9782d92b7..e569c0603 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -715,15 +715,6 @@ class LoaderOption(ORMOption): def process_compile_state(self, compile_state): """Apply a modification to a given :class:`.CompileState`.""" - def _generate_path_cache_key(self, path): - """Used by the "baked lazy loader" to see if this option can be cached. - - .. deprecated:: 2.0 this method is to suit the baked extension which - is itself not part of 2.0. - - """ - return False - class UserDefinedOption(ORMOption): """Base class for a user-defined option that can be consumed from the @@ -782,25 +773,6 @@ class MapperOption(ORMOption): self.process_query(query) - def _generate_path_cache_key(self, path): - """Used by the "baked lazy loader" to see if this option can be cached. - - By default, this method returns the value ``False``, which means - the :class:`.BakedQuery` generated by the lazy loader will - not cache the SQL when this :class:`.MapperOption` is present. - This is the safest option and ensures both that the option is - invoked every time, and also that the cache isn't filled up with - an unlimited number of :class:`_query.Query` objects for an unlimited - number of :class:`.MapperOption` objects. - - For caching support it is recommended to use the - :class:`.UserDefinedOption` class in conjunction with - the :meth:`.Session.do_orm_execute` method so that statements may - be modified before they are cached. - - """ - return False - class LoaderStrategy(object): """Describe the loading behavior of a StrategizedProperty object. diff --git a/lib/sqlalchemy/orm/strategy_options.py b/lib/sqlalchemy/orm/strategy_options.py index a059c67ac..3cc1b97f9 100644 --- a/lib/sqlalchemy/orm/strategy_options.py +++ b/lib/sqlalchemy/orm/strategy_options.py @@ -114,76 +114,6 @@ class Load(Generative, LoaderOption): serialized.append(loader_path + (obj,)) return serialized - def _generate_path_cache_key(self, path): - if path.path[0].is_aliased_class: - return False - - serialized = [] - for (key, loader_path), obj in self.context.items(): - if key != "loader": - continue - - for local_elem, obj_elem in zip(self.path.path, loader_path): - if local_elem is not obj_elem: - break - else: - endpoint = obj._of_type or obj.path.path[-1] - chopped = self._chop_path(loader_path, path) - - if ( - # means loader_path and path are unrelated, - # this does not need to be part of a cache key - chopped - is None - ) or ( - # means no additional path with loader_path + path - # and the endpoint isn't using of_type so isn't modified - # into an alias or other unsafe entity - not chopped - and not obj._of_type - ): - continue - - serialized_path = [] - - for token in chopped: - if isinstance(token, util.string_types): - serialized_path.append(token) - elif token.is_aliased_class: - return False - elif token.is_property: - serialized_path.append(token.key) - else: - assert token.is_mapper - serialized_path.append(token.class_) - - if not serialized_path or endpoint != serialized_path[-1]: - if endpoint.is_mapper: - serialized_path.append(endpoint.class_) - elif endpoint.is_aliased_class: - return False - - serialized.append( - ( - tuple(serialized_path) - + (obj.strategy or ()) - + ( - tuple( - [ - (key, obj.local_opts[key]) - for key in sorted(obj.local_opts) - ] - ) - if obj.local_opts - else () - ) - ) - ) - if not serialized: - return None - else: - return tuple(serialized) - def _generate(self): cloned = super(Load, self)._generate() cloned.local_opts = {} @@ -588,25 +518,6 @@ class _UnboundLoad(Load): _is_chain_link = False - def _generate_path_cache_key(self, path): - serialized = () - for val in self._to_bind: - for local_elem, val_elem in zip(self.path, val.path): - if local_elem is not val_elem: - break - else: - opt = val._bind_loader([path.path[0]], None, None, False) - if opt: - c_key = opt._generate_path_cache_key(path) - if c_key is False: - return False - elif c_key: - serialized += c_key - if not serialized: - return None - else: - return serialized - def _set_path_strategy(self): self._to_bind.append(self) diff --git a/lib/sqlalchemy/sql/schema.py b/lib/sqlalchemy/sql/schema.py index ac6276c1c..0b04ff0da 100644 --- a/lib/sqlalchemy/sql/schema.py +++ b/lib/sqlalchemy/sql/schema.py @@ -1288,6 +1288,12 @@ class Column(DialectKWArgs, SchemaItem, ColumnClause): implement any kind of generation function within the database, which instead must be specified separately. + + .. warning:: This directive **does not** currently produce MySQL's + "ON UPDATE CURRENT_TIMESTAMP()" clause. See + :ref:`mysql_timestamp_onupdate` for background on how to + produce this clause. + .. seealso:: :ref:`triggered_columns` diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index bf6f20436..59c292a07 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -4929,7 +4929,15 @@ class Exists(UnaryExpression): # use on an existing select() s = select([table.c.col1]).where(table.c.col2==5) - s = exists(s) + s_e = exists(s) + + # an exists is usually used in a where of another select + # to produce a WHERE EXISTS (SELECT ... ) + select([table.c.col1]).where(s_e) + + # but can also be used in a select to produce a + # SELECT EXISTS (SELECT ... ) query + select([s_e]) # construct a select() at once exists(['*'], **select_arguments).where(criterion) diff --git a/test/aaa_profiling/test_orm.py b/test/aaa_profiling/test_orm.py index 13e92f5c4..850159323 100644 --- a/test/aaa_profiling/test_orm.py +++ b/test/aaa_profiling/test_orm.py @@ -1,6 +1,5 @@ from sqlalchemy import and_ from sqlalchemy import ForeignKey -from sqlalchemy import inspect from sqlalchemy import Integer from sqlalchemy import join from sqlalchemy import String @@ -1104,46 +1103,6 @@ class BranchedOptionTest(NoCache, fixtures.MappedTest): configure_mappers() - def test_generate_path_cache_key_unbound_branching(self): - A, B, C, D, E, F, G = self.classes("A", "B", "C", "D", "E", "F", "G") - - base = joinedload(A.bs) - opts = [ - base.joinedload(B.cs), - base.joinedload(B.ds), - base.joinedload(B.es), - base.joinedload(B.fs), - ] - - cache_path = inspect(A)._path_registry - - @profiling.function_call_count(warmup=1) - def go(): - for opt in opts: - opt._generate_path_cache_key(cache_path) - - go() - - def test_generate_path_cache_key_bound_branching(self): - A, B, C, D, E, F, G = self.classes("A", "B", "C", "D", "E", "F", "G") - - base = Load(A).joinedload(A.bs) - opts = [ - base.joinedload(B.cs), - base.joinedload(B.ds), - base.joinedload(B.es), - base.joinedload(B.fs), - ] - - cache_path = inspect(A)._path_registry - - @profiling.function_call_count(warmup=1) - def go(): - for opt in opts: - opt._generate_path_cache_key(cache_path) - - go() - def test_query_opts_unbound_branching(self): A, B, C, D, E, F, G = self.classes("A", "B", "C", "D", "E", "F", "G") diff --git a/test/ext/test_baked.py b/test/ext/test_baked.py index 5bd380bb8..15919765c 100644 --- a/test/ext/test_baked.py +++ b/test/ext/test_baked.py @@ -1179,7 +1179,7 @@ class LazyLoaderTest(testing.AssertsCompiledSQL, BakedTest): u1.addresses eq_(m1.mock_calls, [mock.call(User), mock.call(Address)]) - def test_unsafe_unbound_option_cancels_bake(self): + def test_aliased_unbound_are_now_safe_to_cache(self): User, Address, Dingaling = self._o2m_twolevel_fixture(lazy="joined") class SubDingaling(Dingaling): @@ -1206,9 +1206,9 @@ class LazyLoaderTest(testing.AssertsCompiledSQL, BakedTest): ad.dingalings l2 = len(lru) eq_(l1, 0) - eq_(l2, 0) + eq_(l2, 2) - def test_unsafe_bound_option_cancels_bake(self): + def test_aliased_bound_are_now_safe_to_cache(self): User, Address, Dingaling = self._o2m_twolevel_fixture(lazy="joined") class SubDingaling(Dingaling): @@ -1237,7 +1237,7 @@ class LazyLoaderTest(testing.AssertsCompiledSQL, BakedTest): ad.dingalings l2 = len(lru) eq_(l1, 0) - eq_(l2, 0) + eq_(l2, 2) def test_safe_unbound_option_allows_bake(self): User, Address, Dingaling = self._o2m_twolevel_fixture(lazy="joined") @@ -1307,6 +1307,7 @@ class LazyLoaderTest(testing.AssertsCompiledSQL, BakedTest): propagate_to_loaders=True, _gen_cache_key=lambda *args: ("hi",), _generate_path_cache_key=lambda path: ("hi",), + _generate_cache_key=lambda *args: (("hi",), []), ) u1 = sess.query(User).options(mock_opt).first() diff --git a/test/orm/test_options.py b/test/orm/test_options.py index 034e940d9..74a8677bf 100644 --- a/test/orm/test_options.py +++ b/test/orm/test_options.py @@ -13,7 +13,6 @@ from sqlalchemy.orm import defaultload from sqlalchemy.orm import defer from sqlalchemy.orm import exc as orm_exc from sqlalchemy.orm import joinedload -from sqlalchemy.orm import lazyload from sqlalchemy.orm import Load from sqlalchemy.orm import load_only from sqlalchemy.orm import mapper @@ -1841,802 +1840,3 @@ class SubOptionsTest(PathTest, QueryTest): joinedload(User.orders).options, Load(Order).joinedload(Order.items), ) - - -class PathedCacheKeyTest(PathTest, QueryTest): - - run_create_tables = False - run_inserts = None - run_deletes = None - - def test_unbound_cache_key_included_safe(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "orders"]) - - opt = joinedload(User.orders).joinedload(Order.items) - eq_( - opt._generate_path_cache_key(query_path), - (((Order, "items", Item, ("lazy", "joined")),)), - ) - - def test_unbound_cache_key_included_safe_multipath(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "orders"]) - - base = joinedload(User.orders) - opt1 = base.joinedload(Order.items) - opt2 = base.joinedload(Order.address) - - eq_( - opt1._generate_path_cache_key(query_path), - (((Order, "items", Item, ("lazy", "joined")),)), - ) - - eq_( - opt2._generate_path_cache_key(query_path), - (((Order, "address", Address, ("lazy", "joined")),)), - ) - - def test_bound_cache_key_included_safe_multipath(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "orders"]) - - base = Load(User).joinedload(User.orders) - opt1 = base.joinedload(Order.items) - opt2 = base.joinedload(Order.address) - - eq_( - opt1._generate_path_cache_key(query_path), - (((Order, "items", Item, ("lazy", "joined")),)), - ) - - eq_( - opt2._generate_path_cache_key(query_path), - (((Order, "address", Address, ("lazy", "joined")),)), - ) - - def test_bound_cache_key_included_safe(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "orders"]) - - opt = Load(User).joinedload(User.orders).joinedload(Order.items) - eq_( - opt._generate_path_cache_key(query_path), - (((Order, "items", Item, ("lazy", "joined")),)), - ) - - def test_unbound_cache_key_excluded_on_other(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "addresses"]) - - opt = joinedload(User.orders).joinedload(Order.items) - eq_(opt._generate_path_cache_key(query_path), None) - - def test_bound_cache_key_excluded_on_other(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "addresses"]) - - opt = Load(User).joinedload(User.orders).joinedload(Order.items) - eq_(opt._generate_path_cache_key(query_path), None) - - def test_unbound_cache_key_excluded_on_aliased(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - # query of: - # - # query(User).options( - # joinedload(aliased(User).orders).joinedload(Order.items)) - # - # we are lazy loading Order objects from User.orders - # the path excludes our option so cache key should - # be None - - query_path = self._make_path_registry([User, "orders"]) - - opt = joinedload(aliased(User).orders).joinedload(Order.items) - eq_(opt._generate_path_cache_key(query_path), None) - - def test_bound_cache_key_wildcard_one(self): - # do not change this test, it is testing - # a specific condition in Load._chop_path(). - User, Address = self.classes("User", "Address") - - query_path = self._make_path_registry([User, "addresses"]) - - opt = Load(User).lazyload("*") - eq_(opt._generate_path_cache_key(query_path), None) - - def test_unbound_cache_key_wildcard_one(self): - User, Address = self.classes("User", "Address") - - query_path = self._make_path_registry([User, "addresses"]) - - opt = lazyload("*") - eq_( - opt._generate_path_cache_key(query_path), - (("relationship:_sa_default", ("lazy", "select")),), - ) - - def test_bound_cache_key_wildcard_two(self): - User, Address, Order, Item, SubItem, Keyword = self.classes( - "User", "Address", "Order", "Item", "SubItem", "Keyword" - ) - - query_path = self._make_path_registry([User]) - - opt = Load(User).lazyload("orders").lazyload("*") - eq_( - opt._generate_path_cache_key(query_path), - ( - ("orders", Order, ("lazy", "select")), - ("orders", Order, "relationship:*", ("lazy", "select")), - ), - ) - - def test_unbound_cache_key_wildcard_two(self): - User, Address, Order, Item, SubItem, Keyword = self.classes( - "User", "Address", "Order", "Item", "SubItem", "Keyword" - ) - - query_path = self._make_path_registry([User]) - - opt = lazyload("orders").lazyload("*") - eq_( - opt._generate_path_cache_key(query_path), - ( - ("orders", Order, ("lazy", "select")), - ("orders", Order, "relationship:*", ("lazy", "select")), - ), - ) - - def test_unbound_cache_key_of_type_subclass_relationship(self): - User, Address, Order, Item, SubItem, Keyword = self.classes( - "User", "Address", "Order", "Item", "SubItem", "Keyword" - ) - - query_path = self._make_path_registry([Order, "items", Item]) - - opt = subqueryload(Order.items.of_type(SubItem)).subqueryload( - SubItem.extra_keywords - ) - - eq_( - opt._generate_path_cache_key(query_path), - ( - (SubItem, ("lazy", "subquery")), - ("extra_keywords", Keyword, ("lazy", "subquery")), - ), - ) - - def test_unbound_cache_key_of_type_subclass_relationship_stringattr(self): - User, Address, Order, Item, SubItem, Keyword = self.classes( - "User", "Address", "Order", "Item", "SubItem", "Keyword" - ) - - query_path = self._make_path_registry([Order, "items", Item]) - - opt = subqueryload(Order.items.of_type(SubItem)).subqueryload( - "extra_keywords" - ) - - eq_( - opt._generate_path_cache_key(query_path), - ( - (SubItem, ("lazy", "subquery")), - ("extra_keywords", Keyword, ("lazy", "subquery")), - ), - ) - - def test_bound_cache_key_of_type_subclass_relationship(self): - User, Address, Order, Item, SubItem, Keyword = self.classes( - "User", "Address", "Order", "Item", "SubItem", "Keyword" - ) - - query_path = self._make_path_registry([Order, "items", Item]) - - opt = ( - Load(Order) - .subqueryload(Order.items.of_type(SubItem)) - .subqueryload(SubItem.extra_keywords) - ) - - eq_( - opt._generate_path_cache_key(query_path), - ( - (SubItem, ("lazy", "subquery")), - ("extra_keywords", Keyword, ("lazy", "subquery")), - ), - ) - - def test_bound_cache_key_of_type_subclass_string_relationship(self): - User, Address, Order, Item, SubItem, Keyword = self.classes( - "User", "Address", "Order", "Item", "SubItem", "Keyword" - ) - - query_path = self._make_path_registry([Order, "items", Item]) - - opt = ( - Load(Order) - .subqueryload(Order.items.of_type(SubItem)) - .subqueryload("extra_keywords") - ) - - eq_( - opt._generate_path_cache_key(query_path), - ( - (SubItem, ("lazy", "subquery")), - ("extra_keywords", Keyword, ("lazy", "subquery")), - ), - ) - - def test_unbound_cache_key_excluded_of_type_safe(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - # query of: - # - # query(User).options( - # subqueryload(User.orders). - # subqueryload(Order.items.of_type(SubItem))) - # - # - # we are lazy loading Address objects from User.addresses - # the path excludes our option so cache key should - # be None - - query_path = self._make_path_registry([User, "addresses"]) - - opt = subqueryload(User.orders).subqueryload( - Order.items.of_type(SubItem) - ) - eq_(opt._generate_path_cache_key(query_path), None) - - def test_unbound_cache_key_excluded_of_type_unsafe(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - # query of: - # - # query(User).options( - # subqueryload(User.orders). - # subqueryload(Order.items.of_type(aliased(SubItem)))) - # - # - # we are lazy loading Address objects from User.addresses - # the path excludes our option so cache key should - # be None - - query_path = self._make_path_registry([User, "addresses"]) - - opt = subqueryload(User.orders).subqueryload( - Order.items.of_type(aliased(SubItem)) - ) - eq_(opt._generate_path_cache_key(query_path), None) - - def test_bound_cache_key_excluded_of_type_safe(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - # query of: - # - # query(User).options( - # subqueryload(User.orders). - # subqueryload(Order.items.of_type(SubItem))) - # - # - # we are lazy loading Address objects from User.addresses - # the path excludes our option so cache key should - # be None - - query_path = self._make_path_registry([User, "addresses"]) - - opt = ( - Load(User) - .subqueryload(User.orders) - .subqueryload(Order.items.of_type(SubItem)) - ) - eq_(opt._generate_path_cache_key(query_path), None) - - def test_bound_cache_key_excluded_of_type_unsafe(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - # query of: - # - # query(User).options( - # subqueryload(User.orders). - # subqueryload(Order.items.of_type(aliased(SubItem)))) - # - # - # we are lazy loading Address objects from User.addresses - # the path excludes our option so cache key should - # be None - - query_path = self._make_path_registry([User, "addresses"]) - - opt = ( - Load(User) - .subqueryload(User.orders) - .subqueryload(Order.items.of_type(aliased(SubItem))) - ) - eq_(opt._generate_path_cache_key(query_path), None) - - def test_unbound_cache_key_included_of_type_safe(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "orders"]) - - opt = joinedload(User.orders).joinedload(Order.items.of_type(SubItem)) - eq_( - opt._generate_path_cache_key(query_path), - ((Order, "items", SubItem, ("lazy", "joined")),), - ) - - def test_bound_cache_key_included_of_type_safe(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "orders"]) - - opt = ( - Load(User) - .joinedload(User.orders) - .joinedload(Order.items.of_type(SubItem)) - ) - - eq_( - opt._generate_path_cache_key(query_path), - ((Order, "items", SubItem, ("lazy", "joined")),), - ) - - def test_unbound_cache_key_included_unsafe_option_one(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "orders"]) - - opt = joinedload(User.orders).joinedload( - Order.items.of_type(aliased(SubItem)) - ) - eq_(opt._generate_path_cache_key(query_path), False) - - def test_unbound_cache_key_included_unsafe_option_two(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "orders", Order]) - - opt = joinedload(User.orders).joinedload( - Order.items.of_type(aliased(SubItem)) - ) - eq_(opt._generate_path_cache_key(query_path), False) - - def test_unbound_cache_key_included_unsafe_option_three(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "orders", Order, "items"]) - - opt = joinedload(User.orders).joinedload( - Order.items.of_type(aliased(SubItem)) - ) - eq_(opt._generate_path_cache_key(query_path), False) - - def test_unbound_cache_key_included_unsafe_query(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - au = aliased(User) - query_path = self._make_path_registry([inspect(au), "orders"]) - - opt = joinedload(au.orders).joinedload(Order.items) - eq_(opt._generate_path_cache_key(query_path), False) - - def test_unbound_cache_key_included_safe_w_deferred(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "addresses"]) - - opt = ( - joinedload(User.addresses) - .defer(Address.email_address) - .defer(Address.user_id) - ) - eq_( - opt._generate_path_cache_key(query_path), - ( - ( - Address, - "email_address", - ("deferred", True), - ("instrument", True), - ), - (Address, "user_id", ("deferred", True), ("instrument", True)), - ), - ) - - def test_unbound_cache_key_included_safe_w_deferred_multipath(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "orders"]) - - base = joinedload(User.orders) - opt1 = base.joinedload(Order.items) - opt2 = ( - base.joinedload(Order.address) - .defer(Address.email_address) - .defer(Address.user_id) - ) - - eq_( - opt1._generate_path_cache_key(query_path), - ((Order, "items", Item, ("lazy", "joined")),), - ) - - eq_( - opt2._generate_path_cache_key(query_path), - ( - (Order, "address", Address, ("lazy", "joined")), - ( - Order, - "address", - Address, - "email_address", - ("deferred", True), - ("instrument", True), - ), - ( - Order, - "address", - Address, - "user_id", - ("deferred", True), - ("instrument", True), - ), - ), - ) - - def test_bound_cache_key_included_safe_w_deferred(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "addresses"]) - - opt = ( - Load(User) - .joinedload(User.addresses) - .defer(Address.email_address) - .defer(Address.user_id) - ) - eq_( - opt._generate_path_cache_key(query_path), - ( - ( - Address, - "email_address", - ("deferred", True), - ("instrument", True), - ), - (Address, "user_id", ("deferred", True), ("instrument", True)), - ), - ) - - def test_bound_cache_key_included_safe_w_deferred_multipath(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "orders"]) - - base = Load(User).joinedload(User.orders) - opt1 = base.joinedload(Order.items) - opt2 = ( - base.joinedload(Order.address) - .defer(Address.email_address) - .defer(Address.user_id) - ) - - eq_( - opt1._generate_path_cache_key(query_path), - ((Order, "items", Item, ("lazy", "joined")),), - ) - - eq_( - opt2._generate_path_cache_key(query_path), - ( - (Order, "address", Address, ("lazy", "joined")), - ( - Order, - "address", - Address, - "email_address", - ("deferred", True), - ("instrument", True), - ), - ( - Order, - "address", - Address, - "user_id", - ("deferred", True), - ("instrument", True), - ), - ), - ) - - def test_unbound_cache_key_included_safe_w_option(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - opt = ( - defaultload("orders") - .joinedload("items", innerjoin=True) - .defer("description") - ) - query_path = self._make_path_registry([User, "orders"]) - - eq_( - opt._generate_path_cache_key(query_path), - ( - ( - Order, - "items", - Item, - ("lazy", "joined"), - ("innerjoin", True), - ), - ( - Order, - "items", - Item, - "description", - ("deferred", True), - ("instrument", True), - ), - ), - ) - - def test_bound_cache_key_excluded_on_aliased(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "orders"]) - - au = aliased(User) - opt = Load(au).joinedload(au.orders).joinedload(Order.items) - eq_(opt._generate_path_cache_key(query_path), None) - - def test_bound_cache_key_included_unsafe_option_one(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "orders"]) - - opt = ( - Load(User) - .joinedload(User.orders) - .joinedload(Order.items.of_type(aliased(SubItem))) - ) - eq_(opt._generate_path_cache_key(query_path), False) - - def test_bound_cache_key_included_unsafe_option_two(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "orders", Order]) - - opt = ( - Load(User) - .joinedload(User.orders) - .joinedload(Order.items.of_type(aliased(SubItem))) - ) - eq_(opt._generate_path_cache_key(query_path), False) - - def test_bound_cache_key_included_unsafe_option_three(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "orders", Order, "items"]) - - opt = ( - Load(User) - .joinedload(User.orders) - .joinedload(Order.items.of_type(aliased(SubItem))) - ) - eq_(opt._generate_path_cache_key(query_path), False) - - def test_bound_cache_key_included_unsafe_query(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - au = aliased(User) - query_path = self._make_path_registry([inspect(au), "orders"]) - - opt = Load(au).joinedload(au.orders).joinedload(Order.items) - eq_(opt._generate_path_cache_key(query_path), False) - - def test_bound_cache_key_included_safe_w_option(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - opt = ( - Load(User) - .defaultload("orders") - .joinedload("items", innerjoin=True) - .defer("description") - ) - query_path = self._make_path_registry([User, "orders"]) - - eq_( - opt._generate_path_cache_key(query_path), - ( - ( - Order, - "items", - Item, - ("lazy", "joined"), - ("innerjoin", True), - ), - ( - Order, - "items", - Item, - "description", - ("deferred", True), - ("instrument", True), - ), - ), - ) - - def test_unbound_cache_key_included_safe_w_loadonly_strs(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "addresses"]) - - opt = defaultload(User.addresses).load_only("id", "email_address") - eq_( - opt._generate_path_cache_key(query_path), - ( - (Address, "id", ("deferred", False), ("instrument", True)), - ( - Address, - "email_address", - ("deferred", False), - ("instrument", True), - ), - ( - Address, - "column:*", - ("deferred", True), - ("instrument", True), - ("undefer_pks", True), - ), - ), - ) - - def test_unbound_cache_key_included_safe_w_loadonly_props(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "addresses"]) - - opt = defaultload(User.addresses).load_only( - Address.id, Address.email_address - ) - eq_( - opt._generate_path_cache_key(query_path), - ( - (Address, "id", ("deferred", False), ("instrument", True)), - ( - Address, - "email_address", - ("deferred", False), - ("instrument", True), - ), - ( - Address, - "column:*", - ("deferred", True), - ("instrument", True), - ("undefer_pks", True), - ), - ), - ) - - def test_bound_cache_key_included_safe_w_loadonly(self): - User, Address, Order, Item, SubItem = self.classes( - "User", "Address", "Order", "Item", "SubItem" - ) - - query_path = self._make_path_registry([User, "addresses"]) - - opt = ( - Load(User) - .defaultload(User.addresses) - .load_only("id", "email_address") - ) - eq_( - opt._generate_path_cache_key(query_path), - ( - (Address, "id", ("deferred", False), ("instrument", True)), - ( - Address, - "email_address", - ("deferred", False), - ("instrument", True), - ), - ( - Address, - "column:*", - ("deferred", True), - ("instrument", True), - ("undefer_pks", True), - ), - ), - ) - - def test_unbound_cache_key_undefer_group(self): - User, Address = self.classes("User", "Address") - - query_path = self._make_path_registry([User, "addresses"]) - - opt = defaultload(User.addresses).undefer_group("xyz") - - eq_( - opt._generate_path_cache_key(query_path), - ((Address, "column:*", ("undefer_group_xyz", True)),), - ) - - def test_bound_cache_key_undefer_group(self): - User, Address = self.classes("User", "Address") - - query_path = self._make_path_registry([User, "addresses"]) - - opt = Load(User).defaultload(User.addresses).undefer_group("xyz") - - eq_( - opt._generate_path_cache_key(query_path), - ((Address, "column:*", ("undefer_group_xyz", True)),), - ) diff --git a/test/orm/test_update_delete.py b/test/orm/test_update_delete.py index 310b17047..b0d718315 100644 --- a/test/orm/test_update_delete.py +++ b/test/orm/test_update_delete.py @@ -607,6 +607,19 @@ class UpdateDeleteTest(fixtures.MappedTest): in_(jill, sess) not_in_(jane, sess) + def test_update_with_filter_statement(self): + """test for [ticket:4556] """ + + User = self.classes.User + + sess = Session() + assert_raises( + exc.ArgumentError, + lambda: sess.query(User.name == "filter").update( + {"name": "update"} + ), + ) + def test_update_without_load(self): User = self.classes.User diff --git a/test/profiles.txt b/test/profiles.txt index 09f226804..79097197f 100644 --- a/test/profiles.txt +++ b/test/profiles.txt @@ -240,20 +240,6 @@ test.aaa_profiling.test_orm.AttributeOverheadTest.test_collection_append_remove test.aaa_profiling.test_orm.AttributeOverheadTest.test_collection_append_remove 3.8_sqlite_pysqlite_dbapiunicode_cextensions 5828 test.aaa_profiling.test_orm.AttributeOverheadTest.test_collection_append_remove 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 5828 -# TEST: test.aaa_profiling.test_orm.BranchedOptionTest.test_generate_path_cache_key_bound_branching - -test.aaa_profiling.test_orm.BranchedOptionTest.test_generate_path_cache_key_bound_branching 2.7_sqlite_pysqlite_dbapiunicode_cextensions 104 -test.aaa_profiling.test_orm.BranchedOptionTest.test_generate_path_cache_key_bound_branching 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 104 -test.aaa_profiling.test_orm.BranchedOptionTest.test_generate_path_cache_key_bound_branching 3.8_sqlite_pysqlite_dbapiunicode_cextensions 81 -test.aaa_profiling.test_orm.BranchedOptionTest.test_generate_path_cache_key_bound_branching 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 81 - -# TEST: test.aaa_profiling.test_orm.BranchedOptionTest.test_generate_path_cache_key_unbound_branching - -test.aaa_profiling.test_orm.BranchedOptionTest.test_generate_path_cache_key_unbound_branching 2.7_sqlite_pysqlite_dbapiunicode_cextensions 588 -test.aaa_profiling.test_orm.BranchedOptionTest.test_generate_path_cache_key_unbound_branching 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 588 -test.aaa_profiling.test_orm.BranchedOptionTest.test_generate_path_cache_key_unbound_branching 3.8_sqlite_pysqlite_dbapiunicode_cextensions 561 -test.aaa_profiling.test_orm.BranchedOptionTest.test_generate_path_cache_key_unbound_branching 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 561 - # TEST: test.aaa_profiling.test_orm.BranchedOptionTest.test_query_opts_key_bound_branching test.aaa_profiling.test_orm.BranchedOptionTest.test_query_opts_key_bound_branching 2.7_sqlite_pysqlite_dbapiunicode_cextensions 61 @@ -368,10 +354,10 @@ test.aaa_profiling.test_orm.QueryTest.test_query_cols 3.8_sqlite_pysqlite_dbapiu # TEST: test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results -test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_sqlite_pysqlite_dbapiunicode_cextensions 172835 -test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 189061 -test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 3.8_sqlite_pysqlite_dbapiunicode_cextensions 177706 -test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 195128 +test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_sqlite_pysqlite_dbapiunicode_cextensions 152251 +test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 168676 +test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 3.8_sqlite_pysqlite_dbapiunicode_cextensions 159629 +test.aaa_profiling.test_orm.SelectInEagerLoadTest.test_round_trip_results 3.8_sqlite_pysqlite_dbapiunicode_nocextensions 177451 # TEST: test.aaa_profiling.test_orm.SessionTest.test_expire_lots |
