summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql/expression.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-01-25 21:04:50 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2010-01-25 21:04:50 +0000
commit73bfc876692afad7c9f3fcb8bc42bbe732738a5c (patch)
tree2afc36e3d6780c728eb847e83502848be63f9734 /lib/sqlalchemy/sql/expression.py
parentba53c6e844a81d984b70c46d2c1d41405e76595c (diff)
downloadsqlalchemy-73bfc876692afad7c9f3fcb8bc42bbe732738a5c.tar.gz
- Added a tuple_() construct, allows sets of expressions
to be compared to another set, typically with IN against composite primary keys or similar. Also accepts an IN with multiple columns. The "scalar select can have only one column" error message is removed - will rely upon the database to report problems with col mismatch.
Diffstat (limited to 'lib/sqlalchemy/sql/expression.py')
-rw-r--r--lib/sqlalchemy/sql/expression.py43
1 files changed, 38 insertions, 5 deletions
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py
index cf5d98d8f..6d74fec16 100644
--- a/lib/sqlalchemy/sql/expression.py
+++ b/lib/sqlalchemy/sql/expression.py
@@ -47,7 +47,7 @@ __all__ = [
'modifier', 'collate',
'insert', 'intersect', 'intersect_all', 'join', 'label', 'literal',
'literal_column', 'not_', 'null', 'or_', 'outparam', 'outerjoin', 'select',
- 'subquery', 'table', 'text', 'union', 'union_all', 'update', ]
+ 'subquery', 'table', 'text', 'tuple_', 'union', 'union_all', 'update', ]
PARSE_AUTOCOMMIT = util._symbol('PARSE_AUTOCOMMIT')
@@ -662,6 +662,18 @@ def literal(value, type_=None):
"""
return _BindParamClause(None, value, type_=type_, unique=True)
+def tuple_(*expr):
+ """Return a SQL tuple.
+
+ Main usage is to produce a composite IN construct::
+
+ tuple_(table.c.col1, table.c.col2).in_(
+ [(1, 2), (5, 12), (10, 19)]
+ )
+
+ """
+ return _Tuple(*expr)
+
def label(name, obj):
"""Return a :class:`_Label` object for the
given :class:`ColumnElement`.
@@ -955,6 +967,13 @@ def _literal_as_binds(element, name=None, type_=None):
else:
return element
+def _type_from_args(args):
+ for a in args:
+ if not isinstance(a.type, sqltypes.NullType):
+ return a.type
+ else:
+ return sqltypes.NullType
+
def _no_literals(element):
if hasattr(element, '__clause_element__'):
return element.__clause_element__()
@@ -1500,7 +1519,8 @@ class _CompareMixin(ColumnOperators):
if not _is_literal(o):
if not isinstance( o, _CompareMixin):
raise exc.InvalidRequestError(
- "in() function accepts either a list of non-selectable values, or a selectable: %r" % o)
+ "in() function accepts either a list of non-selectable values, "
+ "or a selectable: %r" % o)
else:
o = self._bind_param(o)
args.append(o)
@@ -2360,6 +2380,22 @@ class BooleanClauseList(ClauseList, ColumnElement):
def _select_iterable(self):
return (self, )
+class _Tuple(ClauseList, ColumnElement):
+
+ def __init__(self, *clauses, **kw):
+ super(_Tuple, self).__init__(*clauses, **kw)
+ self.type = _type_from_args(clauses)
+
+ @property
+ def _select_iterable(self):
+ return (self, )
+
+ def _bind_param(self, obj):
+ return _Tuple(*[
+ _BindParamClause(None, o, type_=self.type, unique=True)
+ for o in obj
+ ]).self_group()
+
class _Case(ColumnElement):
__visit_name__ = 'case'
@@ -3318,9 +3354,6 @@ class _ScalarSelect(_Grouping):
def __init__(self, element):
self.element = element
cols = list(element.c)
- if len(cols) != 1:
- raise exc.InvalidRequestError("Scalar select can only be created "
- "from a Select object that has exactly one column expression.")
self.type = cols[0].type
@property