summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2020-09-25 22:31:16 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2020-10-31 13:44:53 -0400
commit654b462d668a2ced4e87077b9babb2590acbf983 (patch)
tree8b6023480423e990c9bbca7c280cb1cb58e012fc /lib/sqlalchemy
parent841eb216644202567ebddfc0badc51a3a35e98c3 (diff)
downloadsqlalchemy-review/mike_bayer/tutorial20.tar.gz
Add SelectBase.exists() method as it seems strange this is not available already. The Exists construct itself does not provide full SELECT-building capabilities so it makes sense this should be used more like a scalar_subquery. Make sure stream_results is getting set up when yield_per is used, for 2.0 style statements as well. this was hardcoded inside of Query.yield_per() and is now moved to take place within QueryContext. Change-Id: Icafcd4fd9b708772343d56edf40995c9e8f835d6
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/engine/interfaces.py3
-rw-r--r--lib/sqlalchemy/engine/result.py4
-rw-r--r--lib/sqlalchemy/engine/row.py12
-rw-r--r--lib/sqlalchemy/orm/context.py10
-rw-r--r--lib/sqlalchemy/orm/interfaces.py2
-rw-r--r--lib/sqlalchemy/orm/query.py87
-rw-r--r--lib/sqlalchemy/orm/session.py21
-rw-r--r--lib/sqlalchemy/sql/dml.py81
-rw-r--r--lib/sqlalchemy/sql/elements.py1
-rw-r--r--lib/sqlalchemy/sql/schema.py55
-rw-r--r--lib/sqlalchemy/sql/selectable.py251
-rw-r--r--lib/sqlalchemy/sql/type_api.py22
-rw-r--r--lib/sqlalchemy/testing/suite/test_unicode_ddl.py4
-rw-r--r--lib/sqlalchemy/util/deprecations.py24
14 files changed, 395 insertions, 182 deletions
diff --git a/lib/sqlalchemy/engine/interfaces.py b/lib/sqlalchemy/engine/interfaces.py
index a7f71f5e5..fddbc501a 100644
--- a/lib/sqlalchemy/engine/interfaces.py
+++ b/lib/sqlalchemy/engine/interfaces.py
@@ -1139,7 +1139,8 @@ class CreateEnginePlugin(object):
:class:`_engine.URL` object should impliement the
:meth:`_engine.CreateEnginePlugin.update_url` method.
- :param kwargs: The keyword arguments passed to :func:`.create_engine`.
+ :param kwargs: The keyword arguments passed to
+ :func:`_sa.create_engine`.
"""
self.url = url
diff --git a/lib/sqlalchemy/engine/result.py b/lib/sqlalchemy/engine/result.py
index cb452ac73..73b07e540 100644
--- a/lib/sqlalchemy/engine/result.py
+++ b/lib/sqlalchemy/engine/result.py
@@ -710,6 +710,10 @@ class Result(_WithKeys, ResultInternal):
:class:`.ResultProxy` interface. When using the ORM, a higher level
object called :class:`.ChunkedIteratorResult` is normally used.
+ .. seealso::
+
+ :ref:`tutorial_fetching_rows` - in the :doc:`/tutorial/index`
+
"""
_process_row = Row
diff --git a/lib/sqlalchemy/engine/row.py b/lib/sqlalchemy/engine/row.py
index 288f08e29..60954fcec 100644
--- a/lib/sqlalchemy/engine/row.py
+++ b/lib/sqlalchemy/engine/row.py
@@ -500,10 +500,14 @@ class RowMapping(BaseRow, collections_abc.Mapping):
"""A ``Mapping`` that maps column names and objects to :class:`.Row` values.
The :class:`.RowMapping` is available from a :class:`.Row` via the
- :attr:`.Row._mapping` attribute and supplies Python mapping (i.e.
- dictionary) access to the contents of the row. This includes support
- for testing of containment of specific keys (string column names or
- objects), as well as iteration of keys, values, and items::
+ :attr:`.Row._mapping` attribute, as well as from the iterable interface
+ provided by the :class:`.MappingResult` object returned by the
+ :meth:`_engine.Result.mappings` method.
+
+ :class:`.RowMapping` supplies Python mapping (i.e. dictionary) access to
+ the contents of the row. This includes support for testing of
+ containment of specific keys (string column names or objects), as well
+ as iteration of keys, values, and items::
for row in result:
if 'a' in row._mapping:
diff --git a/lib/sqlalchemy/orm/context.py b/lib/sqlalchemy/orm/context.py
index 5e9cf9cce..12759f018 100644
--- a/lib/sqlalchemy/orm/context.py
+++ b/lib/sqlalchemy/orm/context.py
@@ -216,6 +216,16 @@ class ORMCompileState(CompileState):
statement._execution_options,
)
+ if "yield_per" in execution_options or load_options._yield_per:
+ execution_options = execution_options.union(
+ {
+ "stream_results": True,
+ "max_row_buffer": execution_options.get(
+ "yield_per", load_options._yield_per
+ ),
+ }
+ )
+
bind_arguments["clause"] = statement
# new in 1.4 - the coercions system is leveraged to allow the
diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py
index b1ff1a049..64f561cbd 100644
--- a/lib/sqlalchemy/orm/interfaces.py
+++ b/lib/sqlalchemy/orm/interfaces.py
@@ -499,6 +499,8 @@ class PropComparator(operators.ColumnOperators):
.. seealso::
+ :ref:`orm_queryguide_join_on_augmented`
+
:ref:`loader_option_criteria`
:func:`.with_loader_criteria`
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index 277dda6fb..f79c19849 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -770,66 +770,18 @@ class Query(
(e.g. approximately 1000) is used, even with DBAPIs that buffer
rows (which are most).
- The :meth:`_query.Query.yield_per` method **is not compatible
- subqueryload eager loading or joinedload eager loading when
- using collections**. It is potentially compatible with "select in"
- eager loading, **provided the database driver supports multiple,
- independent cursors** (pysqlite and psycopg2 are known to work,
- MySQL and SQL Server ODBC drivers do not).
-
- Therefore in some cases, it may be helpful to disable
- eager loads, either unconditionally with
- :meth:`_query.Query.enable_eagerloads`::
-
- q = sess.query(Object).yield_per(100).enable_eagerloads(False)
-
- Or more selectively using :func:`.lazyload`; such as with
- an asterisk to specify the default loader scheme::
-
- q = sess.query(Object).yield_per(100).\
- options(lazyload('*'), joinedload(Object.some_related))
-
- .. warning::
-
- Use this method with caution; if the same instance is
- present in more than one batch of rows, end-user changes
- to attributes will be overwritten.
-
- In particular, it's usually impossible to use this setting
- with eagerly loaded collections (i.e. any lazy='joined' or
- 'subquery') since those collections will be cleared for a
- new load when encountered in a subsequent result batch.
- In the case of 'subquery' loading, the full result for all
- rows is fetched which generally defeats the purpose of
- :meth:`~sqlalchemy.orm.query.Query.yield_per`.
-
- Also note that while
- :meth:`~sqlalchemy.orm.query.Query.yield_per` will set the
- ``stream_results`` execution option to True, currently
- this is only understood by
- :mod:`~sqlalchemy.dialects.postgresql.psycopg2`,
- :mod:`~sqlalchemy.dialects.mysql.mysqldb` and
- :mod:`~sqlalchemy.dialects.mysql.pymysql` dialects
- which will stream results using server side cursors
- instead of pre-buffer all rows for this query. Other
- DBAPIs **pre-buffer all rows** before making them
- available. The memory use of raw database rows is much less
- than that of an ORM-mapped object, but should still be taken into
- consideration when benchmarking.
-
- .. seealso::
-
- :ref:`engine_stream_results`
+ As of SQLAlchemy 1.4, the :meth:`_orm.Query.yield_per` method is
+ equvalent to using the ``yield_per`` execution option at the ORM level.
+ See the section :ref:`orm_queryguide_yield_per` for further background
+ on this option.
"""
self.load_options += {"_yield_per": count}
- self._execution_options = self._execution_options.union(
- {"stream_results": True, "max_row_buffer": count}
- )
@util.deprecated_20(
":meth:`_orm.Query.get`",
alternative="The method is now available as :meth:`_orm.Session.get`",
+ becomes_legacy=True,
)
def get(self, ident):
"""Return an instance based on the given primary key identifier,
@@ -983,10 +935,10 @@ class Query(
def autoflush(self, setting):
"""Return a Query with a specific 'autoflush' setting.
- Note that a Session with autoflush=False will
- not autoflush, even if this flag is set to True at the
- Query level. Therefore this flag is usually used only
- to disable autoflush for a specific Query.
+ As of SQLAlchemy 1.4, the :meth:`_orm.Query.autoflush` method
+ is equvalent to using the ``autoflush`` execution option at the
+ ORM level. See the section :ref:`orm_queryguide_autoflush` for
+ further background on this option.
"""
self.load_options += {"_autoflush": setting}
@@ -997,22 +949,10 @@ class Query(
that will expire and refresh all instances
as they are loaded, or reused from the current :class:`.Session`.
- :meth:`.populate_existing` does not improve behavior when
- the ORM is used normally - the :class:`.Session` object's usual
- behavior of maintaining a transaction and expiring all attributes
- after rollback or commit handles object state automatically.
- This method is not intended for general use.
-
- .. versionadded:: 1.4
-
- The :meth:`.populate_existing` method is equivalent to passing the
- ``populate_existing=True`` option to the
- :meth:`_orm.Query.execution_options` method.
-
- .. seealso::
-
- :ref:`session_expire` - in the ORM :class:`_orm.Session`
- documentation
+ As of SQLAlchemy 1.4, the :meth:`_orm.Query.populate_existing` method
+ is equvalent to using the ``populate_existing`` execution option at the
+ ORM level. See the section :ref:`orm_queryguide_populate_existing` for
+ further background on this option.
"""
self.load_options += {"_populate_existing": True}
@@ -1031,6 +971,7 @@ class Query(
@util.deprecated_20(
":meth:`_orm.Query.with_parent`",
alternative="Use the :func:`_orm.with_parent` standalone construct.",
+ becomes_legacy=True,
)
@util.preload_module("sqlalchemy.orm.relationships")
def with_parent(self, instance, property=None, from_entity=None): # noqa
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index af70de101..2fc2ad68c 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -957,18 +957,8 @@ class Session(_SessionClassMethods):
:ref:`session_committing`
-
- :param future: if True, use 2.0 style behavior for the
- :meth:`_orm.Session.execute` method. Future mode includes the
- following behaviors:
-
- * The :class:`_engine.Result` object returned by the
- :meth:`_orm.Session.execute` method will return new-style tuple
- :class:`_engine.Row` objects
-
- * The :meth:`_orm.Session.execute` method will invoke ORM style
- queries given objects like :class:`_sql.Select`,
- :class:`_sql.Update` and :class:`_sql.Delete` against ORM entities
+ :param future: if True, use 2.0 style transactional and engine
+ behavior. Future mode includes the following behaviors:
* The :class:`_orm.Session` will not use "bound" metadata in order
to locate an :class:`_engine.Engine`; the engine or engines in use
@@ -984,9 +974,6 @@ class Session(_SessionClassMethods):
flag on a :func:`_orm.relationship` will always assume
"False" behavior.
- The "future" flag is also available on a per-execution basis
- using the :paramref:`_orm.Session.execute.future` flag.
-
.. versionadded:: 1.4
.. seealso::
@@ -1929,7 +1916,9 @@ class Session(_SessionClassMethods):
def query(self, *entities, **kwargs):
"""Return a new :class:`_query.Query` object corresponding to this
- :class:`.Session`."""
+ :class:`_orm.Session`.
+
+ """
return self._query_cls(entities, self, **kwargs)
diff --git a/lib/sqlalchemy/sql/dml.py b/lib/sqlalchemy/sql/dml.py
index c923bf651..dc2aacbea 100644
--- a/lib/sqlalchemy/sql/dml.py
+++ b/lib/sqlalchemy/sql/dml.py
@@ -582,14 +582,6 @@ class ValuesBase(UpdateBase):
:meth:`_expression.Update.ordered_values`
- .. seealso::
-
- :ref:`inserts_and_updates` - SQL Expression
- Language Tutorial
-
- :func:`_expression.insert` - produce an ``INSERT`` statement
-
- :func:`_expression.update` - produce an ``UPDATE`` statement
"""
if self._select_names:
@@ -777,9 +769,7 @@ class Insert(ValuesBase):
The :class:`_expression.Insert` object is created using the
:func:`_expression.insert()` function.
- .. seealso::
-
- :ref:`coretutorial_insert_expressions`
+ .. note - the __init__() method delivers the docstring for this object
"""
@@ -834,10 +824,26 @@ class Insert(ValuesBase):
):
"""Construct an :class:`_expression.Insert` object.
+ E.g.::
+
+ from sqlalchemy import insert
+
+ stmt = (
+ insert(user_table).
+ values(name='username', fullname='Full Username')
+ )
+
Similar functionality is available via the
:meth:`_expression.TableClause.insert` method on
:class:`_schema.Table`.
+ .. seealso::
+
+ :ref:`coretutorial_insert_expressions` - in the 1.x tutorial
+
+ :ref:`tutorial_core_insert` - in the 2.0 tutorial
+
+
:param table: :class:`_expression.TableClause`
which is the subject of the
insert.
@@ -976,15 +982,15 @@ class DMLWhereBase(object):
_where_criteria = ()
@_generative
- def where(self, whereclause):
- """Return a new construct with the given expression added to
+ def where(self, *whereclause):
+ """Return a new construct with the given expression(s) added to
its WHERE clause, joined to the existing clause via AND, if any.
"""
- self._where_criteria += (
- coercions.expect(roles.WhereHavingRole, whereclause),
- )
+ for criterion in list(whereclause):
+ where_criteria = coercions.expect(roles.WhereHavingRole, criterion)
+ self._where_criteria += (where_criteria,)
def filter(self, *criteria):
"""A synonym for the :meth:`_dml.DMLWhereBase.where` method.
@@ -1032,9 +1038,7 @@ class DMLWhereBase(object):
class Update(DMLWhereBase, ValuesBase):
"""Represent an Update construct.
- The :class:`_expression.Update`
- object is created using the :func:`update()`
- function.
+ .. note - the __init__() method delivers the docstring for this object
"""
@@ -1090,16 +1094,23 @@ class Update(DMLWhereBase, ValuesBase):
from sqlalchemy import update
- stmt = update(users).where(users.c.id==5).\
- values(name='user #5')
+ stmt = (
+ update(user_table).
+ where(user_table.c.id == 5).
+ values(name='user #5')
+ )
Similar functionality is available via the
:meth:`_expression.TableClause.update` method on
- :class:`_schema.Table`::
+ :class:`_schema.Table`.
+
+ .. seealso::
+
+ :ref:`inserts_and_updates` - in the 1.x tutorial
+
+ :ref:`tutorial_core_update_delete` - in the 2.0 tutorial
+
- stmt = users.update().\
- where(users.c.id==5).\
- values(name='user #5')
:param table: A :class:`_schema.Table`
object representing the database
@@ -1279,9 +1290,7 @@ class Update(DMLWhereBase, ValuesBase):
class Delete(DMLWhereBase, UpdateBase):
"""Represent a DELETE construct.
- The :class:`_expression.Delete`
- object is created using the :func:`delete()`
- function.
+ .. note - the __init__() method delivers the docstring for this object
"""
@@ -1317,10 +1326,26 @@ class Delete(DMLWhereBase, UpdateBase):
):
r"""Construct :class:`_expression.Delete` object.
+ E.g.::
+
+ from sqlalchemy import delete
+
+ stmt = (
+ delete(user_table).
+ where(user_table.c.id == 5)
+ )
+
Similar functionality is available via the
:meth:`_expression.TableClause.delete` method on
:class:`_schema.Table`.
+ .. seealso::
+
+ :ref:`inserts_and_updates` - in the 1.x tutorial
+
+ :ref:`tutorial_core_update_delete` - in the 2.0 tutorial
+
+
:param table: The table to delete rows from.
:param whereclause: A :class:`_expression.ClauseElement`
diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py
index e268abc8a..550cbea24 100644
--- a/lib/sqlalchemy/sql/elements.py
+++ b/lib/sqlalchemy/sql/elements.py
@@ -1655,7 +1655,6 @@ class TextClause(
:ref:`sqlexpression_text` - in the Core tutorial
- :ref:`orm_tutorial_literal_sql` - in the ORM tutorial
"""
return TextClause(text, bind=bind)
diff --git a/lib/sqlalchemy/sql/schema.py b/lib/sqlalchemy/sql/schema.py
index d764002a6..ccb1dd7e9 100644
--- a/lib/sqlalchemy/sql/schema.py
+++ b/lib/sqlalchemy/sql/schema.py
@@ -638,13 +638,14 @@ class Table(DialectKWArgs, SchemaItem, TableClause):
)
insp = inspection.inspect(autoload_with)
- insp.reflect_table(
- self,
- include_columns,
- exclude_columns,
- resolve_fks,
- _extend_on=_extend_on,
- )
+ with insp._inspection_context() as conn_insp:
+ conn_insp.reflect_table(
+ self,
+ include_columns,
+ exclude_columns,
+ resolve_fks,
+ _extend_on=_extend_on,
+ )
@property
def _sorted_constraints(self):
@@ -2824,7 +2825,16 @@ class DefaultClause(FetchedValue):
class Constraint(DialectKWArgs, SchemaItem):
- """A table-level SQL constraint."""
+ """A table-level SQL constraint.
+
+ :class:`_schema.Constraint` serves as the base class for the series of
+ constraint objects that can be associated with :class:`_schema.Table`
+ objects, including :class:`_schema.PrimaryKeyConstraint`,
+ :class:`_schema.ForeignKeyConstraint`
+ :class:`_schema.UniqueConstraint`, and
+ :class:`_schema.CheckConstraint`.
+
+ """
__visit_name__ = "constraint"
@@ -2856,28 +2866,18 @@ class Constraint(DialectKWArgs, SchemaItem):
.. versionadded:: 1.0.0
- :param _create_rule:
- a callable which is passed the DDLCompiler object during
- compilation. Returns True or False to signal inline generation of
- this Constraint.
-
- The AddConstraint and DropConstraint DDL constructs provide
- DDLElement's more comprehensive "conditional DDL" approach that is
- passed a database connection when DDL is being issued. _create_rule
- is instead called during any CREATE TABLE compilation, where there
- may not be any transaction/connection in progress. However, it
- allows conditional compilation of the constraint even for backends
- which do not support addition of constraints through ALTER TABLE,
- which currently includes SQLite.
-
- _create_rule is used by some types to create constraints.
- Currently, its call signature is subject to change at any time.
-
:param \**dialect_kw: Additional keyword arguments are dialect
specific, and passed in the form ``<dialectname>_<argname>``. See
the documentation regarding an individual dialect at
:ref:`dialect_toplevel` for detail on documented arguments.
+ :param _create_rule:
+ used internally by some datatypes that also create constraints.
+
+ :param _type_bound:
+ used internally to indicate that this constraint is associated with
+ a specific datatype.
+
"""
self.name = name
@@ -4158,7 +4158,10 @@ class MetaData(SchemaItem):
"""
def __repr__(self):
- return "MetaData(bind=%r)" % self.bind
+ if self.bind:
+ return "MetaData(bind=%r)" % self.bind
+ else:
+ return "MetaData()"
def __contains__(self, table_or_key):
if not isinstance(table_or_key, util.string_types):
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py
index fd8832400..895a4532b 100644
--- a/lib/sqlalchemy/sql/selectable.py
+++ b/lib/sqlalchemy/sql/selectable.py
@@ -700,8 +700,7 @@ class FromClause(roles.AnonymizedFromClauseRole, Selectable):
@util.memoized_property
def columns(self):
"""A named-based collection of :class:`_expression.ColumnElement`
- objects
- maintained by this :class:`_expression.FromClause`.
+ objects maintained by this :class:`_expression.FromClause`.
The :attr:`.columns`, or :attr:`.c` collection, is the gateway
to the construction of SQL expressions using table-bound or
@@ -709,6 +708,8 @@ class FromClause(roles.AnonymizedFromClauseRole, Selectable):
select(mytable).where(mytable.c.somecolumn == 5)
+ :return: a :class:`.ColumnCollection` object.
+
"""
if "_columns" not in self.__dict__:
@@ -734,8 +735,12 @@ class FromClause(roles.AnonymizedFromClauseRole, Selectable):
@util.memoized_property
def primary_key(self):
- """Return the collection of :class:`_schema.Column` objects
- which comprise the primary key of this FromClause.
+ """Return the iterable collection of :class:`_schema.Column` objects
+ which comprise the primary key of this :class:`_selectable.FromClause`.
+
+ For a :class:`_schema.Table` object, this collection is represented
+ by the :class:`_schema.PrimaryKeyConstraint` which itself is an
+ iterable collection of :class:`_schema.Column` objects.
"""
self._init_collections()
@@ -771,7 +776,16 @@ class FromClause(roles.AnonymizedFromClauseRole, Selectable):
c = property(
attrgetter("columns"),
- doc="An alias for the :attr:`.columns` attribute.",
+ doc="""
+ A named-based collection of :class:`_expression.ColumnElement`
+ objects maintained by this :class:`_expression.FromClause`.
+
+ The :attr:`_sql.FromClause.c` attribute is an alias for the
+ :attr:`_sql.FromClause.columns` atttribute.
+
+ :return: a :class:`.ColumnCollection`
+
+ """,
)
_select_iterable = property(attrgetter("columns"))
@@ -1227,7 +1241,9 @@ class Join(roles.DMLTableRole, FromClause):
)
def bind(self):
"""Return the bound engine associated with either the left or right
- side of this :class:`_sql.Join`."""
+ side of this :class:`_sql.Join`.
+
+ """
return self.left.bind or self.right.bind
@@ -1441,10 +1457,14 @@ class AliasedReturnsRows(NoInit, FromClause):
@property
def description(self):
+ name = self.name
+ if isinstance(name, _anonymous_label):
+ name = "anon_1"
+
if util.py3k:
- return self.name
+ return name
else:
- return self.name.encode("ascii", "backslashreplace")
+ return name.encode("ascii", "backslashreplace")
@property
def original(self):
@@ -1693,8 +1713,18 @@ class CTE(Generative, HasPrefixes, HasSuffixes, AliasedReturnsRows):
"""Represent a Common Table Expression.
The :class:`_expression.CTE` object is obtained using the
- :meth:`_expression.SelectBase.cte` method from any selectable.
- See that method for complete examples.
+ :meth:`_sql.SelectBase.cte` method from any SELECT statement. A less often
+ available syntax also allows use of the :meth:`_sql.HasCTE.cte` method
+ present on :term:`DML` constructs such as :class:`_sql.Insert`,
+ :class:`_sql.Update` and
+ :class:`_sql.Delete`. See the :meth:`_sql.HasCTE.cte` method for
+ usage details on CTEs.
+
+ .. seealso::
+
+ :ref:`tutorial_subqueries_ctes` - in the 2.0 tutorial
+
+ :meth:`_sql.HasCTE.cte` - examples of calling styles
"""
@@ -1955,7 +1985,7 @@ class HasCTE(roles.HasCTERole):
.. seealso::
- :meth:`.orm.query.Query.cte` - ORM version of
+ :meth:`_orm.Query.cte` - ORM version of
:meth:`_expression.HasCTE.cte`.
"""
@@ -2546,10 +2576,29 @@ class SelectBase(
def as_scalar(self):
return self.scalar_subquery()
+ def exists(self):
+ """Return an :class:`_sql.Exists` representation of this selectable,
+ which can be used as a column expression.
+
+ The returned object is an instance of :class:`_sql.Exists`.
+
+ .. seealso::
+
+ :func:`_sql.exists`
+
+ :ref:`tutorial_exists` - in the :term:`2.0 style` tutorial.
+
+ .. versionadded:: 1.4
+
+ """
+ return Exists(self)
+
def scalar_subquery(self):
"""Return a 'scalar' representation of this selectable, which can be
used as a column expression.
+ The returned object is an instance of :class:`_sql.ScalarSelect`.
+
Typically, a select statement which has only one column in its columns
clause is eligible to be used as a scalar expression. The scalar
subquery can then be used in the WHERE clause or columns clause of
@@ -2563,6 +2612,12 @@ class SelectBase(
.. versionchanged: 1.4 - the ``.as_scalar()`` method was renamed to
:meth:`_expression.SelectBase.scalar_subquery`.
+ .. seealso::
+
+ :ref:`tutorial_scalar_subquery` - in the 2.0 tutorial
+
+ :ref:`scalar_selects` - in the 1.x tutorial
+
"""
if self._label_style is not LABEL_STYLE_NONE:
self = self._set_label_style(LABEL_STYLE_NONE)
@@ -3777,12 +3832,12 @@ class SelectState(util.MemoizedSlots, CompileState):
if not len(froms):
raise exc.InvalidRequestError(
- "Select statement '%s"
+ "Select statement '%r"
"' returned no FROM clauses "
"due to auto-correlation; "
"specify correlate(<tables>) "
"to control correlation "
- "manually." % self
+ "manually." % self.statement
)
return froms
@@ -3994,7 +4049,9 @@ class Select(
:func:`_sql.select`
- :ref:`coretutorial_selecting` - in the Core tutorial
+ :ref:`coretutorial_selecting` - in the 1.x tutorial
+
+ :ref:`tutorial_selecting_data` - in the 2.0 tutorial
"""
@@ -4491,8 +4548,8 @@ class Select(
.. seealso::
- :ref:`orm_tutorial_literal_sql` - usage examples in the
- ORM tutorial
+ :ref:`orm_queryguide_selecting_text` - usage examples in the
+ ORM Querying Guide
"""
meth = SelectState.get_plugin_class(self).from_statement
@@ -4548,6 +4605,10 @@ class Select(
.. seealso::
+ :ref:`tutorial_select_join` - in the :doc:`/tutorial/index`
+
+ :ref:`orm_queryguide_joins` - in the :ref:`queryguide_toplevel`
+
:meth:`_expression.Select.join_from`
:meth:`_expression.Select.outerjoin`
@@ -4599,6 +4660,10 @@ class Select(
.. seealso::
+ :ref:`tutorial_select_join` - in the :doc:`/tutorial/index`
+
+ :ref:`orm_queryguide_joins` - in the :ref:`queryguide_toplevel`
+
:meth:`_expression.Select.join`
""" # noqa: E501
@@ -4648,6 +4713,10 @@ class Select(
.. seealso::
+ :ref:`tutorial_select_join` - in the :doc:`/tutorial/index`
+
+ :ref:`orm_queryguide_joins` - in the :ref:`queryguide_toplevel`
+
:meth:`_expression.Select.join`
"""
@@ -4903,7 +4972,7 @@ class Select(
_whereclause = whereclause
@_generative
- def where(self, whereclause):
+ def where(self, *whereclause):
"""Return a new :func:`_expression.select` construct with
the given expression added to
its WHERE clause, joined to the existing clause via AND, if any.
@@ -4911,9 +4980,10 @@ class Select(
"""
assert isinstance(self._where_criteria, tuple)
- self._where_criteria += (
- coercions.expect(roles.WhereHavingRole, whereclause),
- )
+
+ for criterion in list(whereclause):
+ where_criteria = coercions.expect(roles.WhereHavingRole, criterion)
+ self._where_criteria += (where_criteria,)
@_generative
def having(self, having):
@@ -5400,6 +5470,24 @@ class Select(
class ScalarSelect(roles.InElementRole, Generative, Grouping):
+ """Represent a scalar subquery.
+
+
+ A :class:`_sql.ScalarSubquery` is created by invoking the
+ :meth:`_sql.SelectBase.scalar_subquery` method. The object
+ then participates in other SQL expressions as a SQL column expression
+ within the :class:`_sql.ColumnElement` hierarchy.
+
+ .. seealso::
+
+ :meth:`_sql.SelectBase.scalar_subquery`
+
+ :ref:`tutorial_scalar_subquery` - in the 2.0 tutorial
+
+ :ref:`scalar_selects` - in the 1.x tutorial
+
+ """
+
_from_objects = []
_is_from_container = True
_is_implicitly_boolean = False
@@ -5430,9 +5518,79 @@ class ScalarSelect(roles.InElementRole, Generative, Grouping):
def self_group(self, **kwargs):
return self
+ @_generative
+ def correlate(self, *fromclauses):
+ r"""Return a new :class:`_expression.ScalarSelect`
+ which will correlate the given FROM
+ clauses to that of an enclosing :class:`_expression.Select`.
+
+ This method is mirrored from the :meth:`_sql.Select.correlate` method
+ of the underlying :class:`_sql.Select`. The method applies the
+ :meth:_sql.Select.correlate` method, then returns a new
+ :class:`_sql.ScalarSelect` against that statement.
+
+ .. versionadded:: 1.4 Previously, the
+ :meth:`_sql.ScalarSelect.correlate`
+ method was only available from :class:`_sql.Select`.
+
+ :param \*fromclauses: a list of one or more
+ :class:`_expression.FromClause`
+ constructs, or other compatible constructs (i.e. ORM-mapped
+ classes) to become part of the correlate collection.
+
+ .. seealso::
+
+ :meth:`_expression.ScalarSelect.correlate_except`
+
+ :ref:`tutorial_scalar_subquery` - in the 2.0 tutorial
+
+ :ref:`correlated_subqueries` - in the 1.x tutorial
+
+
+ """
+ self.element = self.element.correlate(*fromclauses)
+
+ @_generative
+ def correlate_except(self, *fromclauses):
+ r"""Return a new :class:`_expression.ScalarSelect`
+ which will omit the given FROM
+ clauses from the auto-correlation process.
+
+ This method is mirrored from the
+ :meth:`_sql.Select.correlate_except` method of the underlying
+ :class:`_sql.Select`. The method applies the
+ :meth:_sql.Select.correlate_except` method, then returns a new
+ :class:`_sql.ScalarSelect` against that statement.
+
+ .. versionadded:: 1.4 Previously, the
+ :meth:`_sql.ScalarSelect.correlate_except`
+ method was only available from :class:`_sql.Select`.
+
+ :param \*fromclauses: a list of one or more
+ :class:`_expression.FromClause`
+ constructs, or other compatible constructs (i.e. ORM-mapped
+ classes) to become part of the correlate-exception collection.
+
+ .. seealso::
+
+ :meth:`_expression.ScalarSelect.correlate`
+
+ :ref:`tutorial_scalar_subquery` - in the 2.0 tutorial
+
+ :ref:`correlated_subqueries` - in the 1.x tutorial
+
+
+ """
+
+ self.element = self.element.correlate_except(*fromclauses)
+
class Exists(UnaryExpression):
- """Represent an ``EXISTS`` clause."""
+ """Represent an ``EXISTS`` clause.
+
+ See :func:`_sql.exists` for a description of usage.
+
+ """
_from_objects = []
inherit_cache = True
@@ -5440,12 +5598,23 @@ class Exists(UnaryExpression):
def __init__(self, *args, **kwargs):
"""Construct a new :class:`_expression.Exists` construct.
- The modern form of :func:`.exists` is to invoke with no arguments,
- which will produce an ``"EXISTS *"`` construct. A WHERE clause
- is then added using the :meth:`.Exists.where` method::
+ The :func:`_sql.exists` can be invoked by itself to produce an
+ :class:`_sql.Exists` construct, which will accept simple WHERE
+ criteria::
exists_criteria = exists().where(table1.c.col1 == table2.c.col2)
+ However, for greater flexibility in constructing the SELECT, an
+ existing :class:`_sql.Select` construct may be converted to an
+ :class:`_sql.Exists`, most conveniently by making use of the
+ :meth:`_sql.SelectBase.exists` method::
+
+ exists_criteria = (
+ select(table2.c.col2).
+ where(table1.c.col1 == table2.c.col2).
+ exists()
+ )
+
The EXISTS criteria is then used inside of an enclosing SELECT::
stmt = select(table1.c.col1).where(exists_criteria)
@@ -5453,9 +5622,13 @@ class Exists(UnaryExpression):
The above statement will then be of the form::
SELECT col1 FROM table1 WHERE EXISTS
- (SELECT * FROM table2 WHERE table2.col2 = table1.col1)
+ (SELECT table2.col2 FROM table2 WHERE table2.col2 = table1.col1)
- """
+ .. seealso::
+
+ :ref:`tutorial_exists` - in the :term:`2.0 style` tutorial.
+
+ """ # noqa E501
if args and isinstance(args[0], (SelectBase, ScalarSelect)):
s = args[0]
else:
@@ -5524,6 +5697,13 @@ class Exists(UnaryExpression):
return Select._create_select_from_fromclause(self, [self], **kwargs)
def correlate(self, *fromclause):
+ """Apply correlation to the subquery noted by this :class:`_sql.Exists`.
+
+ .. seealso::
+
+ :meth:`_sql.ScalarSelect.correlate`
+
+ """
e = self._clone()
e.element = self._regroup(
lambda element: element.correlate(*fromclause)
@@ -5531,6 +5711,14 @@ class Exists(UnaryExpression):
return e
def correlate_except(self, *fromclause):
+ """Apply correlation to the subquery noted by this :class:`_sql.Exists`.
+
+ .. seealso::
+
+ :meth:`_sql.ScalarSelect.correlate_except`
+
+ """
+
e = self._clone()
e.element = self._regroup(
lambda element: element.correlate_except(*fromclause)
@@ -5544,6 +5732,11 @@ class Exists(UnaryExpression):
method of the select
statement contained.
+ .. note:: it is typically preferable to build a :class:`_sql.Select`
+ statement first, including the desired WHERE clause, then use the
+ :meth:`_sql.SelectBase.exists` method to produce an
+ :class:`_sql.Exists` object at once.
+
"""
e = self._clone()
e.element = self._regroup(lambda element: element.select_from(*froms))
@@ -5554,6 +5747,12 @@ class Exists(UnaryExpression):
given expression added to
its WHERE clause, joined to the existing clause via AND, if any.
+
+ .. note:: it is typically preferable to build a :class:`_sql.Select`
+ statement first, including the desired WHERE clause, then use the
+ :meth:`_sql.SelectBase.exists` method to produce an
+ :class:`_sql.Exists` object at once.
+
"""
e = self._clone()
e.element = self._regroup(lambda element: element.where(clause))
diff --git a/lib/sqlalchemy/sql/type_api.py b/lib/sqlalchemy/sql/type_api.py
index 614b70a41..bca6e9020 100644
--- a/lib/sqlalchemy/sql/type_api.py
+++ b/lib/sqlalchemy/sql/type_api.py
@@ -456,6 +456,28 @@ class TypeEngine(Traversible):
else:
return self.__class__
+ @classmethod
+ def _is_generic_type(cls):
+ n = cls.__name__
+ return n.upper() != n
+
+ def _generic_type_affinity(self):
+
+ for t in self.__class__.__mro__:
+ if (
+ t.__module__
+ in (
+ "sqlalchemy.sql.sqltypes",
+ "sqlalchemy.sql.type_api",
+ )
+ and t._is_generic_type()
+ ):
+ if t in (TypeEngine, UserDefinedType):
+ return NULLTYPE.__class__
+ return t
+ else:
+ return self.__class__
+
def dialect_impl(self, dialect):
"""Return a dialect-specific implementation for this
:class:`.TypeEngine`.
diff --git a/lib/sqlalchemy/testing/suite/test_unicode_ddl.py b/lib/sqlalchemy/testing/suite/test_unicode_ddl.py
index 6c6518011..af6b382ae 100644
--- a/lib/sqlalchemy/testing/suite/test_unicode_ddl.py
+++ b/lib/sqlalchemy/testing/suite/test_unicode_ddl.py
@@ -188,7 +188,7 @@ class UnicodeSchemaTest(fixtures.TablesTest):
eq_(
repr(t),
(
- "Table('\\u6e2c\\u8a66', MetaData(bind=None), "
+ "Table('\\u6e2c\\u8a66', MetaData(), "
"Column('\\u6e2c\\u8a66_id', Integer(), "
"table=<\u6e2c\u8a66>), "
"schema=None)"
@@ -198,7 +198,7 @@ class UnicodeSchemaTest(fixtures.TablesTest):
eq_(
repr(t),
(
- "Table('測試', MetaData(bind=None), "
+ "Table('測試', MetaData(), "
"Column('測試_id', Integer(), "
"table=<測試>), "
"schema=None)"
diff --git a/lib/sqlalchemy/util/deprecations.py b/lib/sqlalchemy/util/deprecations.py
index 9f0ca0b1a..f46374601 100644
--- a/lib/sqlalchemy/util/deprecations.py
+++ b/lib/sqlalchemy/util/deprecations.py
@@ -81,10 +81,18 @@ def deprecated_cls(version, message, constructor="__init__"):
return decorate
-def deprecated_20_cls(clsname, alternative=None, constructor="__init__"):
+def deprecated_20_cls(
+ clsname, alternative=None, constructor="__init__", becomes_legacy=False
+):
message = (
".. deprecated:: 1.4 The %s class is considered legacy as of the "
- "1.x series of SQLAlchemy and will be removed in 2.0." % clsname
+ "1.x series of SQLAlchemy and %s in 2.0."
+ % (
+ clsname,
+ "will be removed"
+ if not becomes_legacy
+ else "becomes a legacy construct",
+ )
)
if alternative:
@@ -161,7 +169,7 @@ def moved_20(message, **kw):
)
-def deprecated_20(api_name, alternative=None, **kw):
+def deprecated_20(api_name, alternative=None, becomes_legacy=False, **kw):
type_reg = re.match("^:(attr|func|meth):", api_name)
if type_reg:
type_ = {"attr": "attribute", "func": "function", "meth": "method"}[
@@ -171,8 +179,14 @@ def deprecated_20(api_name, alternative=None, **kw):
type_ = "construct"
message = (
"The %s %s is considered legacy as of the "
- "1.x series of SQLAlchemy and will be removed in 2.0."
- % (api_name, type_)
+ "1.x series of SQLAlchemy and %s in 2.0."
+ % (
+ api_name,
+ type_,
+ "will be removed"
+ if not becomes_legacy
+ else "becomes a legacy construct",
+ )
)
if alternative: