diff options
Diffstat (limited to 'lib/sqlalchemy/sql')
-rw-r--r-- | lib/sqlalchemy/sql/__init__.py | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/compiler.py | 40 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/expression.py | 4 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/selectable.py | 122 |
4 files changed, 168 insertions, 0 deletions
diff --git a/lib/sqlalchemy/sql/__init__.py b/lib/sqlalchemy/sql/__init__.py index 281b7d0f2..78de80734 100644 --- a/lib/sqlalchemy/sql/__init__.py +++ b/lib/sqlalchemy/sql/__init__.py @@ -77,6 +77,8 @@ from .expression import union # noqa from .expression import union_all # noqa from .expression import Update # noqa from .expression import update # noqa +from .expression import Values # noqa +from .expression import values # noqa from .expression import within_group # noqa from .visitors import ClauseVisitor # noqa diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index ae9c3c73a..14f4bda8c 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -2292,6 +2292,46 @@ class SQLCompiler(Compiled): return text + def visit_values(self, element, asfrom=False, from_linter=None, **kw): + v = "VALUES %s" % ", ".join( + self.process(elem, literal_binds=element.literal_binds) + for elem in element._data + ) + + if isinstance(element.name, elements._truncated_label): + name = self._truncated_identifier("values", element.name) + else: + name = element.name + + if element._is_lateral: + lateral = "LATERAL " + else: + lateral = "" + + if asfrom: + if from_linter: + from_linter.froms[element] = ( + name if name is not None else "(unnamed VALUES element)" + ) + + if name: + v = "%s(%s)%s (%s)" % ( + lateral, + v, + self.get_render_as_alias_suffix(self.preparer.quote(name)), + ( + ", ".join( + c._compiler_dispatch( + self, include_table=False, **kw + ) + for c in element.columns + ) + ), + ) + else: + v = "%s(%s)" % (lateral, v) + return v + def get_render_as_alias_suffix(self, alias_name_text): return " AS " + alias_name_text diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 780648df0..4dc2b8bbf 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -32,6 +32,7 @@ __all__ = [ "Selectable", "TableClause", "Update", + "Values", "alias", "and_", "asc", @@ -80,6 +81,7 @@ __all__ = [ "Subquery", "TableSample", "tablesample", + "values", ] @@ -156,6 +158,7 @@ from .selectable import TableClause # noqa from .selectable import TableSample # noqa from .selectable import TextAsFrom # noqa from .selectable import TextualSelect # noqa +from .selectable import Values # noqa from .visitors import Visitable # noqa from ..util.langhelpers import public_factory # noqa @@ -184,6 +187,7 @@ label = public_factory(Label, ".sql.expression.label") case = public_factory(Case, ".sql.expression.case") cast = public_factory(Cast, ".sql.expression.cast") cte = public_factory(CTE._factory, ".sql.expression.cte") +values = public_factory(Values, ".sql.expression.values") extract = public_factory(Extract, ".sql.expression.extract") tuple_ = public_factory(Tuple, ".sql.expression.tuple_") except_ = public_factory( diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index 3c8990a84..a44b079c4 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -46,6 +46,7 @@ from .elements import ColumnClause from .elements import GroupedElement from .elements import Grouping from .elements import literal_column +from .elements import Tuple from .elements import UnaryExpression from .visitors import InternalTraversal from .. import exc @@ -2010,6 +2011,127 @@ class ForUpdateArg(ClauseElement): self.of = None +class Values(Generative, FromClause): + """represent a ``VALUES`` construct that can be used as a FROM element + in a statement. + + The :class:`.Values` object is created from the + :func:`~.sql.expression.values` function. + + .. versionadded:: 1.4 + + """ + + named_with_column = True + __visit_name__ = "values" + + _data = () + + _traverse_internals = [ + ("_column_args", InternalTraversal.dp_clauseelement_list,), + ("_data", InternalTraversal.dp_clauseelement_list), + ("name", InternalTraversal.dp_string), + ("literal_binds", InternalTraversal.dp_boolean), + ] + + def __init__(self, *columns, **kw): + r"""Construct a :class:`.Values` construct. + + The column expressions and the actual data for + :class:`.Values` are given in two separate steps. The + constructor receives the column expressions typically as + :func:`.column` constructs, and the data is then passed via the + :meth:`.Values.data` method as a list, which can be called multiple + times to add more data, e.g.:: + + from sqlalchemy import column + from sqlalchemy import values + + value_expr = values( + column('id', Integer), + column('name', Integer), + name="my_values" + ).data( + [(1, 'name1'), (2, 'name2'), (3, 'name3')] + ) + + :param \*columns: column expressions, typically composed using + :func:`.column` objects. + + :param name: the name for this VALUES construct. If omitted, the + VALUES construct will be unnamed in a SQL expression. Different + backends may have different requirements here. + + :param literal_binds: Defaults to False. Whether or not to render + the data values inline in the SQL output, rather than using bound + parameters. + + """ + + super(Values, self).__init__() + self._column_args = columns + self.name = kw.pop("name", None) + self.literal_binds = kw.pop("literal_binds", False) + self.named_with_column = self.name is not None + + @_generative + def alias(self, name, **kw): + """Return a new :class:`.Values` construct that is a copy of this + one with the given name. + + This method is a VALUES-specific specialization of the + :class:`.FromClause.alias` method. + + .. seealso:: + + :ref:`core_tutorial_aliases` + + :func:`~.expression.alias` + + """ + self.name = name + self.named_with_column = self.name is not None + + @_generative + def lateral(self, name=None): + """Return a new :class:`.Values` with the lateral flag set, so that + it renders as LATERAL. + + .. seealso:: + + :func:`~.expression.lateral` + + """ + self._is_lateral = True + if name is not None: + self.name = name + + @_generative + def data(self, values): + """Return a new :class:`.Values` construct, adding the given data + to the data list. + + E.g.:: + + my_values = my_values.data([(1, 'value 1'), (2, 'value2')]) + + :param values: a sequence (i.e. list) of tuples that map to the + column expressions given in the :class:`.Values` constructor. + + """ + + self._data += tuple(Tuple(*row).self_group() for row in values) + + def _populate_column_collection(self): + for c in self._column_args: + self._columns.add(c) + c.table = self + + @property + def _from_objects(self): + return [self] + + class SelectBase( roles.SelectStatementRole, roles.DMLSelectRole, |