summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/sql')
-rw-r--r--lib/sqlalchemy/sql/__init__.py2
-rw-r--r--lib/sqlalchemy/sql/compiler.py40
-rw-r--r--lib/sqlalchemy/sql/expression.py4
-rw-r--r--lib/sqlalchemy/sql/selectable.py122
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,