]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Added new method :meth:`.Select.with_statement_hint` and ORM
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 18 Sep 2014 15:44:48 +0000 (11:44 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 18 Sep 2014 15:44:48 +0000 (11:44 -0400)
method :meth:`.Query.with_statement_hint` to support statement-level
hints that are not specific to a table.
fixes #3206

doc/build/changelog/changelog_10.rst
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/sql/compiler.py
lib/sqlalchemy/sql/selectable.py
test/orm/test_query.py
test/sql/test_compiler.py

index d6782c91731bade88929c0ebe464042d79667a3e..7d7548e1173b8886359a51c990368f5d9d27fb13 100644 (file)
     series as well.  For changes that are specific to 1.0 with an emphasis
     on compatibility concerns, see :doc:`/changelog/migration_10`.
 
+    .. change::
+        :tags: feature, sql
+        :tickets: 3206
+
+        Added new method :meth:`.Select.with_statement_hint` and ORM
+        method :meth:`.Query.with_statement_hint` to support statement-level
+        hints that are not specific to a table.
+
     .. change::
         :tags: bug, sqlite
         :tickets: 3203
index 60948293b940f1567a271d21c1fd709c3e403a8f..e6b2bf537dbb588db16f892bd1ab921cdbc596c4 100644 (file)
@@ -1145,7 +1145,8 @@ class Query(object):
 
     @_generative()
     def with_hint(self, selectable, text, dialect_name='*'):
-        """Add an indexing hint for the given entity or selectable to
+        """Add an indexing or other executional context
+        hint for the given entity or selectable to
         this :class:`.Query`.
 
         Functionality is passed straight through to
@@ -1153,11 +1154,35 @@ class Query(object):
         with the addition that ``selectable`` can be a
         :class:`.Table`, :class:`.Alias`, or ORM entity / mapped class
         /etc.
+
+        .. seealso::
+
+            :meth:`.Query.with_statement_hint`
+
         """
-        selectable = inspect(selectable).selectable
+        if selectable is not None:
+            selectable = inspect(selectable).selectable
 
         self._with_hints += ((selectable, text, dialect_name),)
 
+    def with_statement_hint(self, text, dialect_name='*'):
+        """add a statement hint to this :class:`.Select`.
+
+        This method is similar to :meth:`.Select.with_hint` except that
+        it does not require an individual table, and instead applies to the
+        statement as a whole.
+
+        This feature calls down into :meth:`.Select.with_statement_hint`.
+
+        .. versionadded:: 1.0.0
+
+        .. seealso::
+
+            :meth:`.Query.with_hint`
+
+        """
+        return self.with_hint(None, text, dialect_name)
+
     @_generative()
     def execution_options(self, **kwargs):
         """ Set non-SQL options which take effect during execution.
index 5149fa4feebcdba26ea2433a3e3528da3823530f..abda31358849f66000504ac62462e102b8457840 100644 (file)
@@ -1331,6 +1331,9 @@ class SQLCompiler(Compiled):
     def get_crud_hint_text(self, table, text):
         return None
 
+    def get_statement_hint_text(self, hint_texts):
+        return " ".join(hint_texts)
+
     def _transform_select_for_nested_joins(self, select):
         """Rewrite any "a JOIN (b JOIN c)" expression as
         "a JOIN (select * from b JOIN c) AS anon", to support
@@ -1609,6 +1612,15 @@ class SQLCompiler(Compiled):
         if select._for_update_arg is not None:
             text += self.for_update_clause(select, **kwargs)
 
+        if select._statement_hints:
+            per_dialect = [
+                ht for (dialect_name, ht)
+                in select._statement_hints
+                if dialect_name in ('*', self.dialect.name)
+            ]
+            if per_dialect:
+                text += " " + self.get_statement_hint_text(per_dialect)
+
         if self.ctes and \
                 compound_index == 0 and toplevel:
             text = self._render_cte_clause() + text
index 9e8cb3bc59f1e9793d8da5e54d402cc29aa80d1b..24804866214608b5314b27751ceb73371b658365 100644 (file)
@@ -2153,6 +2153,7 @@ class Select(HasPrefixes, GenerativeSelect):
 
     _prefixes = ()
     _hints = util.immutabledict()
+    _statement_hints = ()
     _distinct = False
     _from_cloned = None
     _correlate = ()
@@ -2525,10 +2526,30 @@ class Select(HasPrefixes, GenerativeSelect):
 
         return self._get_display_froms()
 
+    def with_statement_hint(self, text, dialect_name='*'):
+        """add a statement hint to this :class:`.Select`.
+
+        This method is similar to :meth:`.Select.with_hint` except that
+        it does not require an individual table, and instead applies to the
+        statement as a whole.
+
+        Hints here are specific to the backend database and may include
+        directives such as isolation levels, file directives, fetch directives,
+        etc.
+
+        .. versionadded:: 1.0.0
+
+        .. seealso::
+
+            :meth:`.Select.with_hint`
+
+        """
+        return self.with_hint(None, text, dialect_name)
+
     @_generative
     def with_hint(self, selectable, text, dialect_name='*'):
-        """Add an indexing hint for the given selectable to this
-        :class:`.Select`.
+        """Add an indexing or other executional context hint for the given
+        selectable to this :class:`.Select`.
 
         The text of the hint is rendered in the appropriate
         location for the database backend in use, relative
@@ -2555,9 +2576,16 @@ class Select(HasPrefixes, GenerativeSelect):
                     mytable, "+ index(%(name)s ix_mytable)", 'oracle').\\
                 with_hint(mytable, "WITH INDEX ix_mytable", 'sybase')
 
+        .. seealso::
+
+            :meth:`.Select.with_statement_hint`
+
         """
-        self._hints = self._hints.union(
-            {(selectable, dialect_name): text})
+        if selectable is None:
+            self._statement_hints += ((dialect_name, text), )
+        else:
+            self._hints = self._hints.union(
+                {(selectable, dialect_name): text})
 
     @property
     def type(self):
index c9f0a5db0febc8c9bf71016d5b9d372307053082..52e266a087a62fab7bc65000a9da3b18c10ec5c4 100644 (file)
@@ -2517,6 +2517,28 @@ class HintsTest(QueryTest, AssertsCompiledSQL):
             "ON users_1.id > users.id", dialect=dialect
         )
 
+    def test_statement_hints(self):
+        User = self.classes.User
+
+        sess = create_session()
+        stmt = sess.query(User).\
+            with_statement_hint("test hint one").\
+            with_statement_hint("test hint two").\
+            with_statement_hint("test hint three", "postgresql")
+
+        self.assert_compile(
+            stmt,
+            "SELECT users.id AS users_id, users.name AS users_name "
+            "FROM users test hint one test hint two",
+        )
+
+        self.assert_compile(
+            stmt,
+            "SELECT users.id AS users_id, users.name AS users_name "
+            "FROM users test hint one test hint two test hint three",
+            dialect='postgresql'
+        )
+
 
 class TextTest(QueryTest, AssertsCompiledSQL):
     __dialect__ = 'default'
index d47b58f1f05c6441d6cb70be4c1e99a0a97ff194..3e6b873519c69823268e387223c351dfc06e2e54 100644 (file)
@@ -2420,6 +2420,23 @@ class SelectTest(fixtures.TestBase, AssertsCompiledSQL):
                 dialect=dialect
             )
 
+    def test_statement_hints(self):
+
+        stmt = select([table1.c.myid]).\
+            with_statement_hint("test hint one").\
+            with_statement_hint("test hint two", 'mysql')
+
+        self.assert_compile(
+            stmt,
+            "SELECT mytable.myid FROM mytable test hint one",
+        )
+
+        self.assert_compile(
+            stmt,
+            "SELECT mytable.myid FROM mytable test hint one test hint two",
+            dialect='mysql'
+        )
+
     def test_literal_as_text_fromstring(self):
         self.assert_compile(
             and_(text("a"), text("b")),