diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-09-01 20:19:54 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-09-01 20:19:54 -0400 |
| commit | 7c6a45c480a865ac9580eb33fcca2dae5b19dd11 (patch) | |
| tree | 870c078707cde0af769a940b1fc1a15ce7966691 /doc | |
| parent | 382f82538b5484b1c384c71fbf84438312cbe34f (diff) | |
| download | sqlalchemy-7c6a45c480a865ac9580eb33fcca2dae5b19dd11.tar.gz | |
- The :func:`~.expression.column` and :func:`~.expression.table`
constructs are now importable from the "from sqlalchemy" namespace,
just like every other Core construct.
- The implicit conversion of strings to :func:`.text` constructs
when passed to most builder methods of :func:`.select` as
well as :class:`.Query` now emits a warning with just the
plain string sent. The textual conversion still proceeds normally,
however. The only method that accepts a string without a warning
are the "label reference" methods like order_by(), group_by();
these functions will now at compile time attempt to resolve a single
string argument to a column or label expression present in the
selectable; if none is located, the expression still renders, but
you get the warning again. The rationale here is that the implicit
conversion from string to text is more unexpected than not these days,
and it is better that the user send more direction to the Core / ORM
when passing a raw string as to what direction should be taken.
Core/ORM tutorials have been updated to go more in depth as to how text
is handled.
fixes #2992
Diffstat (limited to 'doc')
| -rw-r--r-- | doc/build/changelog/changelog_10.rst | 36 | ||||
| -rw-r--r-- | doc/build/changelog/migration_10.rst | 151 | ||||
| -rw-r--r-- | doc/build/conf.py | 2 | ||||
| -rw-r--r-- | doc/build/core/tutorial.rst | 5 | ||||
| -rw-r--r-- | doc/build/orm/tutorial.rst | 5 |
5 files changed, 195 insertions, 4 deletions
diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index 7594c7c32..643035477 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -22,6 +22,38 @@ on compatibility concerns, see :doc:`/changelog/migration_10`. .. change:: + :tags: changed, sql + + The :func:`~.expression.column` and :func:`~.expression.table` + constructs are now importable from the "from sqlalchemy" namespace, + just like every other Core construct. + + .. change:: + :tags: changed, sql + :tickets: 2992 + + The implicit conversion of strings to :func:`.text` constructs + when passed to most builder methods of :func:`.select` as + well as :class:`.Query` now emits a warning with just the + plain string sent. The textual conversion still proceeds normally, + however. The only method that accepts a string without a warning + are the "label reference" methods like order_by(), group_by(); + these functions will now at compile time attempt to resolve a single + string argument to a column or label expression present in the + selectable; if none is located, the expression still renders, but + you get the warning again. The rationale here is that the implicit + conversion from string to text is more unexpected than not these days, + and it is better that the user send more direction to the Core / ORM + when passing a raw string as to what direction should be taken. + Core/ORM tutorials have been updated to go more in depth as to how text + is handled. + + .. seealso:: + + :ref:`migration_2992` + + + .. change:: :tags: feature, engine :tickets: 3178 @@ -68,7 +100,7 @@ :ref:`migration_3177` .. change:: - :tags: change, orm + :tags: changed, orm The ``proc()`` callable passed to the ``create_row_processor()`` method of custom :class:`.Bundle` classes now accepts only a single @@ -79,7 +111,7 @@ :ref:`bundle_api_change` .. change:: - :tags: change, orm + :tags: changed, orm Deprecated event hooks removed: ``populate_instance``, ``create_instance``, ``translate_row``, ``append_result`` diff --git a/doc/build/changelog/migration_10.rst b/doc/build/changelog/migration_10.rst index a3f0748ab..8f01e99e6 100644 --- a/doc/build/changelog/migration_10.rst +++ b/doc/build/changelog/migration_10.rst @@ -104,6 +104,155 @@ symbol, and no change to the object's state occurs. :ticket:`3061` +.. _migration_2992: + +Warnings emitted when coercing full SQL fragments into text() +------------------------------------------------------------- + +Since SQLAlchemy's inception, there has always been an emphasis on not getting +in the way of the usage of plain text. The Core and ORM expression systems +were intended to allow any number of points at which the user can just +use plain text SQL expressions, not just in the sense that you can send a +full SQL string to :meth:`.Connection.execute`, but that you can send strings +with SQL expressions into many functions, such as :meth:`.Select.where`, +:meth:`.Query.filter`, and :meth:`.Select.order_by`. + +Note that by "SQL expressions" we mean a **full fragment of a SQL string**, +such as:: + + # the argument sent to where() is a full SQL expression + stmt = select([sometable]).where("somecolumn = 'value'") + +and we are **not talking about string arguments**, that is, the normal +behavior of passing string values that become parameterized:: + + # This is a normal Core expression with a string argument - + # we aren't talking about this!! + stmt = select([sometable]).where(sometable.c.somecolumn == 'value') + +The Core tutorial has long featured an example of the use of this technique, +using a :func:`.select` construct where virtually all components of it +are specified as straight strings. However, despite this long-standing +behavior and example, users are apparently surprised that this behavior +exists, and when asking around the community, I was unable to find any user +that was in fact *not* surprised that you can send a full string into a method +like :meth:`.Query.filter`. + +So the change here is to encourage the user to qualify textual strings when +composing SQL that is partially or fully composed from textual fragments. +When composing a select as below:: + + stmt = select(["a", "b"]).where("a = b").select_from("sometable") + +The statement is built up normally, with all the same coercions as before. +However, one will see the following warnings emitted:: + + SAWarning: Textual column expression 'a' should be explicitly declared + with text('a'), or use column('a') for more specificity + (this warning may be suppressed after 10 occurrences) + + SAWarning: Textual column expression 'b' should be explicitly declared + with text('b'), or use column('b') for more specificity + (this warning may be suppressed after 10 occurrences) + + SAWarning: Textual SQL expression 'a = b' should be explicitly declared + as text('a = b') (this warning may be suppressed after 10 occurrences) + + SAWarning: Textual SQL FROM expression 'sometable' should be explicitly + declared as text('sometable'), or use table('sometable') for more + specificity (this warning may be suppressed after 10 occurrences) + +These warnings attempt to show exactly where the issue is by displaying +the parameters as well as where the string was received. +The warnings make use of the :ref:`feature_3178` so that parameterized warnings +can be emitted safely without running out of memory, and as always, if +one wishes the warnings to be exceptions, the +`Python Warnings Filter <https://docs.python.org/2/library/warnings.html>`_ +should be used:: + + import warnings + warnings.simplefilter("error") # all warnings raise an exception + +Given the above warnings, our statement works just fine, but +to get rid of the warnings we would rewrite our statement as follows:: + + from sqlalchemy import select, text + stmt = select([ + text("a"), + text("b") + ]).where(text("a = b")).select_from(text("sometable")) + +and as the warnings suggest, we can give our statement more specificity +about the text if we use :func:`.column` and :func:`.table`:: + + from sqlalchemy import select, text, column, table + + stmt = select([column("a"), column("b")]).\\ + where(text("a = b")).select_from(table("sometable")) + +Where note also that :func:`.table` and :func:`.column` can now +be imported from "sqlalchemy" without the "sql" part. + +The behavior here applies to :func:`.select` as well as to key methods +on :class:`.Query`, including :meth:`.Query.filter`, +:meth:`.Query.from_statement` and :meth:`.Query.having`. + +ORDER BY and GROUP BY are special cases +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +There is one case where usage of a string has special meaning, and as part +of this change we have enhanced its functionality. When we have a +:func:`.select` or :class:`.Query` that refers to some column name or named +label, we might want to GROUP BY and/or ORDER BY known columns or labels:: + + stmt = select([ + user.c.name, + func.count(user.c.id).label("id_count") + ]).group_by("name").order_by("id_count") + +In the above statement we expect to see "ORDER BY id_count", as opposed to a +re-statement of the function. The string argument given is actively +matched to an entry in the columns clause during compilation, so the above +statement would produce as we expect, without warnings:: + + SELECT users.name, count(users.id) AS id_count + FROM users GROUP BY users.name ORDER BY id_count + +However, if we refer to a name that cannot be located, then we get +the warning again, as below:: + + stmt = select([ + user.c.name, + func.count(user.c.id).label("id_count") + ]).order_by("some_label") + +The output does what we say, but again it warns us:: + + SAWarning: Can't resolve label reference 'some_label'; converting to + text() (this warning may be suppressed after 10 occurrences) + + SELECT users.name, count(users.id) AS id_count + FROM users ORDER BY some_label + +The above behavior applies to all those places where we might want to refer +to a so-called "label reference"; ORDER BY and GROUP BY, but also within an +OVER clause as well as a DISTINCT ON clause that refers to columns (e.g. the +Postgresql syntax). + +We can still specify any arbitrary expression for ORDER BY or others using +:func:`.text`:: + + stmt = select([users]).order_by(text("some special expression")) + +The upshot of the whole change is that SQLAlchemy now would like us +to tell it when a string is sent that this string is explicitly +a :func:`.text` construct, or a column, table, etc., and if we use it as a +label name in an order by, group by, or other expression, SQLAlchemy expects +that the string resolves to something known, else it should again +be qualified with :func:`.text` or similar. + +:ticket:`2992` + .. _migration_yield_per_eager_loading: Joined/Subquery eager loading explicitly disallowed with yield_per @@ -482,7 +631,7 @@ of times; beyond that, the Python warnings registry will begin recording them as duplicates. To illustrate, the following test script will show only ten warnings being -emitted for ten of the parameter sets, out of a total of 1000: +emitted for ten of the parameter sets, out of a total of 1000:: from sqlalchemy import create_engine, Unicode, select, cast import random diff --git a/doc/build/conf.py b/doc/build/conf.py index fa69abfac..5277134e7 100644 --- a/doc/build/conf.py +++ b/doc/build/conf.py @@ -61,7 +61,7 @@ changelog_sections = ["general", "orm", "orm declarative", "orm querying", \ "postgresql", "mysql", "sqlite", "mssql", \ "oracle", "firebird"] # tags to sort on inside of sections -changelog_inner_tag_sort = ["feature", "bug", "moved", "changed", "removed"] +changelog_inner_tag_sort = ["feature", "changed", "removed", "bug", "moved"] # how to render changelog links changelog_render_ticket = "http://www.sqlalchemy.org/trac/ticket/%s" diff --git a/doc/build/core/tutorial.rst b/doc/build/core/tutorial.rst index 5a1eb0b7a..04a25b174 100644 --- a/doc/build/core/tutorial.rst +++ b/doc/build/core/tutorial.rst @@ -835,6 +835,11 @@ need to refer to any pre-established :class:`.Table` metadata: the less flexibility and ability for manipulation/transformation the statement will have. +.. versionchanged:: 1.0.0 + The :func:`.select` construct emits warnings when string SQL + fragments are coerced to :func:`.text`, and :func:`.text` should + be used explicitly. See :ref:`migration_2992` for background. + .. _sqlexpression_literal_column: Using More Specific Text with :func:`.table`, :func:`.literal_column`, and :func:`.column` diff --git a/doc/build/orm/tutorial.rst b/doc/build/orm/tutorial.rst index 6514ec800..f1b6a4499 100644 --- a/doc/build/orm/tutorial.rst +++ b/doc/build/orm/tutorial.rst @@ -982,6 +982,11 @@ completely "raw", using string names to identify desired columns: ('ed',) {stop}[(1, u'ed', 12)] +.. versionchanged:: 1.0.0 + The :class:`.Query` construct emits warnings when string SQL + fragments are coerced to :func:`.text`, and :func:`.text` should + be used explicitly. See :ref:`migration_2992` for background. + .. seealso:: :ref:`sqlexpression_text` - Core description of textual segments. The |
