summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/sql')
-rw-r--r--lib/sqlalchemy/sql/base.py74
-rw-r--r--lib/sqlalchemy/sql/coercions.py12
-rw-r--r--lib/sqlalchemy/sql/elements.py62
-rw-r--r--lib/sqlalchemy/sql/operators.py32
-rw-r--r--lib/sqlalchemy/sql/selectable.py166
5 files changed, 222 insertions, 124 deletions
diff --git a/lib/sqlalchemy/sql/base.py b/lib/sqlalchemy/sql/base.py
index 4bc6d8280..36a8151d3 100644
--- a/lib/sqlalchemy/sql/base.py
+++ b/lib/sqlalchemy/sql/base.py
@@ -571,6 +571,26 @@ class Options(util.with_metaclass(_MetaOptions)):
o1.__dict__.update(other)
return o1
+ def __eq__(self, other):
+ # TODO: very inefficient. This is used only in test suites
+ # right now.
+ for a, b in util.zip_longest(self._cache_attrs, other._cache_attrs):
+ if getattr(self, a) != getattr(other, b):
+ return False
+ return True
+
+ def __repr__(self):
+ # TODO: fairly inefficient, used only in debugging right now.
+
+ return "%s(%s)" % (
+ self.__class__.__name__,
+ ", ".join(
+ "%s=%r" % (k, self.__dict__[k])
+ for k in self._cache_attrs
+ if k in self.__dict__
+ ),
+ )
+
@hybridmethod
def add_to_element(self, name, value):
return self + {name: getattr(self, name) + value}
@@ -610,6 +630,60 @@ class Options(util.with_metaclass(_MetaOptions)):
)
return cls + d
+ @classmethod
+ def from_execution_options(
+ cls, key, attrs, exec_options, statement_exec_options
+ ):
+ """"process Options argument in terms of execution options.
+
+
+ e.g.::
+
+ (
+ load_options,
+ execution_options,
+ ) = QueryContext.default_load_options.from_execution_options(
+ "_sa_orm_load_options",
+ {
+ "populate_existing",
+ "autoflush",
+ "yield_per"
+ },
+ execution_options,
+ statement._execution_options,
+ )
+
+ get back the Options and refresh "_sa_orm_load_options" in the
+ exec options dict w/ the Options as well
+
+ """
+
+ # common case is that no options we are looking for are
+ # in either dictionary, so cancel for that first
+ check_argnames = attrs.intersection(
+ set(exec_options).union(statement_exec_options)
+ )
+
+ existing_options = exec_options.get(key, cls)
+
+ if check_argnames:
+ result = {}
+ for argname in check_argnames:
+ local = "_" + argname
+ if argname in exec_options:
+ result[local] = exec_options[argname]
+ elif argname in statement_exec_options:
+ result[local] = statement_exec_options[argname]
+
+ new_options = existing_options + result
+ exec_options = util.immutabledict().merge_with(
+ exec_options, {key: new_options}
+ )
+ return new_options, exec_options
+
+ else:
+ return existing_options, exec_options
+
class CacheableOptions(Options, HasCacheKey):
@hybridmethod
diff --git a/lib/sqlalchemy/sql/coercions.py b/lib/sqlalchemy/sql/coercions.py
index be412c770..588c485ae 100644
--- a/lib/sqlalchemy/sql/coercions.py
+++ b/lib/sqlalchemy/sql/coercions.py
@@ -52,6 +52,18 @@ def _document_text_coercion(paramname, meth_rst, param_rst):
)
+def _expression_collection_was_a_list(attrname, fnname, args):
+ if args and isinstance(args[0], (list, set)) and len(args) == 1:
+ util.warn_deprecated_20(
+ 'The "%s" argument to %s() is now passed as a series of '
+ "positional "
+ "elements, rather than as a list. " % (attrname, fnname)
+ )
+ return args[0]
+ else:
+ return args
+
+
def expect(role, element, apply_propagate_attrs=None, argname=None, **kw):
if (
role.allows_lambda
diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py
index 6ce505412..c7e5aabcc 100644
--- a/lib/sqlalchemy/sql/elements.py
+++ b/lib/sqlalchemy/sql/elements.py
@@ -2573,10 +2573,8 @@ class Case(ColumnElement):
stmt = select([users_table]).\
where(
case(
- [
- (users_table.c.name == 'wendy', 'W'),
- (users_table.c.name == 'jack', 'J')
- ],
+ (users_table.c.name == 'wendy', 'W'),
+ (users_table.c.name == 'jack', 'J'),
else_='E'
)
)
@@ -2597,7 +2595,10 @@ class Case(ColumnElement):
("else_", InternalTraversal.dp_clauseelement),
]
- def __init__(self, whens, value=None, else_=None):
+ # TODO: for Py2k removal, this will be:
+ # def __init__(self, *whens, value=None, else_=None):
+
+ def __init__(self, *whens, **kw):
r"""Produce a ``CASE`` expression.
The ``CASE`` construct in SQL is a conditional object that
@@ -2612,10 +2613,8 @@ class Case(ColumnElement):
stmt = select([users_table]).\
where(
case(
- [
- (users_table.c.name == 'wendy', 'W'),
- (users_table.c.name == 'jack', 'J')
- ],
+ (users_table.c.name == 'wendy', 'W'),
+ (users_table.c.name == 'jack', 'J'),
else_='E'
)
)
@@ -2660,16 +2659,14 @@ class Case(ColumnElement):
from sqlalchemy import case, literal_column
case(
- [
- (
- orderline.c.qty > 100,
- literal_column("'greaterthan100'")
- ),
- (
- orderline.c.qty > 10,
- literal_column("'greaterthan10'")
- )
- ],
+ (
+ orderline.c.qty > 100,
+ literal_column("'greaterthan100'")
+ ),
+ (
+ orderline.c.qty > 10,
+ literal_column("'greaterthan10'")
+ ),
else_=literal_column("'lessthan10'")
)
@@ -2683,19 +2680,23 @@ class Case(ColumnElement):
ELSE 'lessthan10'
END
- :param whens: The criteria to be compared against,
+ :param \*whens: The criteria to be compared against,
:paramref:`.case.whens` accepts two different forms, based on
whether or not :paramref:`.case.value` is used.
+ .. versionchanged:: 1.4 the :func:`_sql.case`
+ function now accepts the series of WHEN conditions positionally;
+ passing the expressions within a list is deprecated.
+
In the first form, it accepts a list of 2-tuples; each 2-tuple
consists of ``(<sql expression>, <value>)``, where the SQL
expression is a boolean expression and "value" is a resulting value,
e.g.::
- case([
+ case(
(users_table.c.name == 'wendy', 'W'),
(users_table.c.name == 'jack', 'J')
- ])
+ )
In the second form, it accepts a Python dictionary of comparison
values mapped to a resulting value; this form requires
@@ -2720,11 +2721,23 @@ class Case(ColumnElement):
"""
+ if "whens" in kw:
+ util.warn_deprecated_20(
+ 'The "whens" argument to case() is now passed as a series of '
+ "positional "
+ "elements, rather than as a list. "
+ )
+ whens = kw.pop("whens")
+ else:
+ whens = coercions._expression_collection_was_a_list(
+ "whens", "case", whens
+ )
try:
whens = util.dictlike_iteritems(whens)
except TypeError:
pass
+ value = kw.pop("value", None)
if value is not None:
whenlist = [
(
@@ -2760,11 +2773,16 @@ class Case(ColumnElement):
self.type = type_
self.whens = whenlist
+
+ else_ = kw.pop("else_", None)
if else_ is not None:
self.else_ = coercions.expect(roles.ExpressionElementRole, else_)
else:
self.else_ = None
+ if kw:
+ raise TypeError("unknown arguments: %s" % (", ".join(sorted(kw))))
+
@property
def _from_objects(self):
return list(
diff --git a/lib/sqlalchemy/sql/operators.py b/lib/sqlalchemy/sql/operators.py
index 85db88345..2d369cdf8 100644
--- a/lib/sqlalchemy/sql/operators.py
+++ b/lib/sqlalchemy/sql/operators.py
@@ -169,9 +169,6 @@ class Operators(object):
:class:`.Boolean`, and those that do not will be of the same
type as the left-hand operand.
- .. versionadded:: 1.2.0b3 - added the
- :paramref:`.Operators.op.return_type` argument.
-
.. seealso::
:ref:`types_operators`
@@ -194,8 +191,6 @@ class Operators(object):
:paramref:`.Operators.op.is_comparison`
flag with True.
- .. versionadded:: 1.2.0b3
-
.. seealso::
:meth:`.Operators.op`
@@ -723,15 +718,6 @@ class ColumnOperators(Operators):
With the value of ``:param`` as ``"foo/%bar"``.
- .. versionadded:: 1.2
-
- .. versionchanged:: 1.2.0 The
- :paramref:`.ColumnOperators.startswith.autoescape` parameter is
- now a simple boolean rather than a character; the escape
- character itself is also escaped, and defaults to a forwards
- slash, which itself can be customized using the
- :paramref:`.ColumnOperators.startswith.escape` parameter.
-
:param escape: a character which when given will render with the
``ESCAPE`` keyword to establish that character as the escape
character. This character can then be placed preceding occurrences
@@ -811,15 +797,6 @@ class ColumnOperators(Operators):
With the value of ``:param`` as ``"foo/%bar"``.
- .. versionadded:: 1.2
-
- .. versionchanged:: 1.2.0 The
- :paramref:`.ColumnOperators.endswith.autoescape` parameter is
- now a simple boolean rather than a character; the escape
- character itself is also escaped, and defaults to a forwards
- slash, which itself can be customized using the
- :paramref:`.ColumnOperators.endswith.escape` parameter.
-
:param escape: a character which when given will render with the
``ESCAPE`` keyword to establish that character as the escape
character. This character can then be placed preceding occurrences
@@ -899,15 +876,6 @@ class ColumnOperators(Operators):
With the value of ``:param`` as ``"foo/%bar"``.
- .. versionadded:: 1.2
-
- .. versionchanged:: 1.2.0 The
- :paramref:`.ColumnOperators.contains.autoescape` parameter is
- now a simple boolean rather than a character; the escape
- character itself is also escaped, and defaults to a forwards
- slash, which itself can be customized using the
- :paramref:`.ColumnOperators.contains.escape` parameter.
-
:param escape: a character which when given will render with the
``ESCAPE`` keyword to establish that character as the escape
character. This character can then be placed preceding occurrences
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py
index 12fcc00c3..1155c273b 100644
--- a/lib/sqlalchemy/sql/selectable.py
+++ b/lib/sqlalchemy/sql/selectable.py
@@ -2225,6 +2225,17 @@ class ForUpdateArg(ClauseElement):
("skip_locked", InternalTraversal.dp_boolean),
]
+ @classmethod
+ def _from_argument(cls, with_for_update):
+ if isinstance(with_for_update, ForUpdateArg):
+ return with_for_update
+ elif with_for_update in (None, False):
+ return None
+ elif with_for_update is True:
+ return ForUpdateArg()
+ else:
+ return ForUpdateArg(**with_for_update)
+
def __eq__(self, other):
return (
isinstance(other, ForUpdateArg)
@@ -2699,6 +2710,12 @@ class SelectStatementGrouping(GroupedElement, SelectBase):
class DeprecatedSelectBaseGenerations(object):
+ """A collection of methods available on :class:`_sql.Select` and
+ :class:`_sql.CompoundSelect`, these are all **deprecated** methods as they
+ modify the object in-place.
+
+ """
+
@util.deprecated(
"1.4",
"The :meth:`_expression.GenerativeSelect.append_order_by` "
@@ -2740,9 +2757,6 @@ class DeprecatedSelectBaseGenerations(object):
as it
provides standard :term:`method chaining`.
- .. seealso::
-
- :meth:`_expression.GenerativeSelect.group_by`
"""
self.group_by.non_generative(self, *clauses)
@@ -3353,6 +3367,12 @@ class CompoundSelect(HasCompileState, GenerativeSelect):
class DeprecatedSelectGenerations(object):
+ """A collection of methods available on :class:`_sql.Select`, these
+ are all **deprecated** methods as they modify the :class:`_sql.Select`
+ object in -place.
+
+ """
+
@util.deprecated(
"1.4",
"The :meth:`_expression.Select.append_correlation` "
@@ -3377,7 +3397,7 @@ class DeprecatedSelectGenerations(object):
"1.4",
"The :meth:`_expression.Select.append_column` method is deprecated "
"and will be removed in a future release. Use the generative "
- "method :meth:`_expression.Select.column`.",
+ "method :meth:`_expression.Select.add_columns`.",
)
def append_column(self, column):
"""Append the given column expression to the columns clause of this
@@ -3388,14 +3408,10 @@ class DeprecatedSelectGenerations(object):
my_select.append_column(some_table.c.new_column)
This is an **in-place** mutation method; the
- :meth:`_expression.Select.column` method is preferred,
+ :meth:`_expression.Select.add_columns` method is preferred,
as it provides standard
:term:`method chaining`.
- See the documentation for :meth:`_expression.Select.with_only_columns`
- for guidelines on adding /replacing the columns of a
- :class:`_expression.Select` object.
-
"""
self.add_columns.non_generative(self, column)
@@ -3501,6 +3517,21 @@ class SelectState(util.MemoizedSlots, CompileState):
self.columns_plus_names = statement._generate_columns_plus_names(True)
+ @classmethod
+ def _plugin_not_implemented(cls):
+ raise NotImplementedError(
+ "The default SELECT construct without plugins does not "
+ "implement this method."
+ )
+
+ @classmethod
+ def get_column_descriptions(cls, statement):
+ cls._plugin_not_implemented()
+
+ @classmethod
+ def from_statement(cls, statement, from_statement):
+ cls._plugin_not_implemented()
+
def _get_froms(self, statement):
seen = set()
froms = []
@@ -3805,6 +3836,15 @@ class Select(
):
"""Represents a ``SELECT`` statement.
+ The :class:`_sql.Select` object is normally constructed using the
+ :func:`_sql.select` function. See that function for details.
+
+ .. seealso::
+
+ :func:`_sql.select`
+
+ :ref:`coretutorial_selecting` - in the Core tutorial
+
"""
__visit_name__ = "select"
@@ -3821,7 +3861,7 @@ class Select(
_from_obj = ()
_auto_correlate = True
- compile_options = SelectState.default_select_compile_options
+ _compile_options = SelectState.default_select_compile_options
_traverse_internals = (
[
@@ -3851,7 +3891,7 @@ class Select(
)
_cache_key_traversal = _traverse_internals + [
- ("compile_options", InternalTraversal.dp_has_cache_key)
+ ("_compile_options", InternalTraversal.dp_has_cache_key)
]
@classmethod
@@ -4274,12 +4314,35 @@ class Select(
@property
def column_descriptions(self):
"""Return a 'column descriptions' structure which may be
- plugin-specific.
+ :term:`plugin-specific`.
"""
meth = SelectState.get_plugin_class(self).get_column_descriptions
return meth(self)
+ def from_statement(self, statement):
+ """Apply the columns which this :class:`.Select` would select
+ onto another statement.
+
+ This operation is :term:`plugin-specific` and will raise a not
+ supported exception if this :class:`_sql.Select` does not select from
+ plugin-enabled entities.
+
+
+ The statement is typically either a :func:`_expression.text` or
+ :func:`_expression.select` construct, and should return the set of
+ columns appropriate to the entities represented by this
+ :class:`.Select`.
+
+ .. seealso::
+
+ :ref:`orm_tutorial_literal_sql` - usage examples in the
+ ORM tutorial
+
+ """
+ meth = SelectState.get_plugin_class(self).from_statement
+ return meth(self, statement)
+
@_generative
def join(self, target, onclause=None, isouter=False, full=False):
r"""Create a SQL JOIN against this :class:`_expresson.Select`
@@ -4550,7 +4613,7 @@ class Select(
)
@_generative
- def with_only_columns(self, columns):
+ def with_only_columns(self, *columns):
r"""Return a new :func:`_expression.select` construct with its columns
clause replaced with the given columns.
@@ -4558,65 +4621,26 @@ class Select(
:func:`_expression.select` had been called with the given columns
clause. I.e. a statement::
- s = select([table1.c.a, table1.c.b])
- s = s.with_only_columns([table1.c.b])
+ s = select(table1.c.a, table1.c.b)
+ s = s.with_only_columns(table1.c.b)
should be exactly equivalent to::
- s = select([table1.c.b])
-
- This means that FROM clauses which are only derived
- from the column list will be discarded if the new column
- list no longer contains that FROM::
-
- >>> table1 = table('t1', column('a'), column('b'))
- >>> table2 = table('t2', column('a'), column('b'))
- >>> s1 = select([table1.c.a, table2.c.b])
- >>> print(s1)
- SELECT t1.a, t2.b FROM t1, t2
- >>> s2 = s1.with_only_columns([table2.c.b])
- >>> print(s2)
- SELECT t2.b FROM t1
-
- The preferred way to maintain a specific FROM clause
- in the construct, assuming it won't be represented anywhere
- else (i.e. not in the WHERE clause, etc.) is to set it using
- :meth:`_expression.Select.select_from`::
-
- >>> s1 = select([table1.c.a, table2.c.b]).\
- ... select_from(table1.join(table2,
- ... table1.c.a==table2.c.a))
- >>> s2 = s1.with_only_columns([table2.c.b])
- >>> print(s2)
- SELECT t2.b FROM t1 JOIN t2 ON t1.a=t2.a
-
- Care should also be taken to use the correct set of column objects
- passed to :meth:`_expression.Select.with_only_columns`.
- Since the method is
- essentially equivalent to calling the :func:`_expression.select`
- construct in the first place with the given columns, the columns passed
- to :meth:`_expression.Select.with_only_columns`
- should usually be a subset of
- those which were passed to the :func:`_expression.select`
- construct, not those which are available from the ``.c`` collection of
- that :func:`_expression.select`. That is::
-
- s = select([table1.c.a, table1.c.b]).select_from(table1)
- s = s.with_only_columns([table1.c.b])
-
- and **not**::
-
- # usually incorrect
- s = s.with_only_columns([s.c.b])
-
- The latter would produce the SQL::
-
- SELECT b
- FROM (SELECT t1.a AS a, t1.b AS b
- FROM t1), t1
-
- Since the :func:`_expression.select` construct is essentially
- being asked to select both from ``table1`` as well as itself.
+ s = select(table1.c.b)
+
+ Note that this will also dynamically alter the FROM clause of the
+ statement if it is not explicitly stated. To maintain the FROM
+ clause, ensure the :meth:`_sql.Select.select_from` method is
+ used appropriately::
+
+ s = select(table1.c.a, table2.c.b)
+ s = s.select_from(table2.c.b).with_only_columns(table1.c.a)
+
+ :param \*columns: column expressions to be used.
+
+ .. versionchanged:: 1.4 the :meth:`_sql.Select.with_only_columns`
+ method accepts the list of column expressions positionally;
+ passing the expressions as a list is deprecateed.
"""
@@ -4626,7 +4650,9 @@ class Select(
self._assert_no_memoizations()
rc = []
- for c in columns:
+ for c in coercions._expression_collection_was_a_list(
+ "columns", "Select.with_only_columns", columns
+ ):
c = coercions.expect(roles.ColumnsClauseRole, c,)
# TODO: why are we doing this here?
if isinstance(c, ScalarSelect):