diff options
Diffstat (limited to 'lib/sqlalchemy/sql.py')
-rw-r--r-- | lib/sqlalchemy/sql.py | 88 |
1 files changed, 52 insertions, 36 deletions
diff --git a/lib/sqlalchemy/sql.py b/lib/sqlalchemy/sql.py index ce33810a5..b5faf37fe 100644 --- a/lib/sqlalchemy/sql.py +++ b/lib/sqlalchemy/sql.py @@ -9,7 +9,8 @@ from sqlalchemy import util, exceptions from sqlalchemy import types as sqltypes import string, re, random, sets -__all__ = ['text', 'table', 'column', 'func', 'select', 'update', 'insert', 'delete', 'join', 'and_', 'or_', 'not_', 'between_', 'case', 'cast', 'union', 'union_all', 'null', 'desc', 'asc', 'outerjoin', 'alias', 'subquery', 'literal', 'bindparam', 'exists', 'extract','AbstractDialect', 'ClauseParameters', 'ClauseVisitor', 'Executor', 'Compiled', 'ClauseElement', 'ColumnElement', 'ColumnCollection', 'FromClause', 'TableClause', 'Select', 'Alias', 'CompoundSelect','Join', 'Selectable'] + +__all__ = ['text', 'table', 'column', 'func', 'select', 'update', 'insert', 'delete', 'join', 'and_', 'or_', 'not_', 'between_', 'case', 'cast', 'union', 'union_all', 'except_', 'except_all', 'intersect', 'intersect_all', 'null', 'desc', 'asc', 'outerjoin', 'alias', 'subquery', 'literal', 'bindparam', 'exists', 'extract','AbstractDialect', 'ClauseParameters', 'ClauseVisitor', 'Executor', 'Compiled', 'ClauseElement', 'ColumnElement', 'ColumnCollection', 'FromClause', 'TableClause', 'Select', 'Alias', 'CompoundSelect','Join', 'Selectable'] def desc(column): """return a descending ORDER BY clause element, e.g.: @@ -181,6 +182,18 @@ def union(*selects, **params): def union_all(*selects, **params): return _compound_select('UNION ALL', *selects, **params) +def except_(*selects, **params): + return _compound_select('EXCEPT', *selects, **params) + +def except_all(*selects, **params): + return _compound_select('EXCEPT ALL', *selects, **params) + +def intersect(*selects, **params): + return _compound_select('INTERSECT', *selects, **params) + +def intersect_all(*selects, **params): + return _compound_select('INTERSECT ALL', *selects, **params) + def alias(*args, **params): return Alias(*args, **params) @@ -1357,7 +1370,7 @@ class _SelectBaseMixin(object): def select(self, whereclauses = None, **params): return select([self], whereclauses, **params) def _get_from_objects(self): - if self.is_where or self._scalar: + if self.is_where or self.is_scalar: return [] else: return [self] @@ -1366,19 +1379,27 @@ class CompoundSelect(_SelectBaseMixin, FromClause): def __init__(self, keyword, *selects, **kwargs): _SelectBaseMixin.__init__(self) self.keyword = keyword - self.selects = selects self.use_labels = kwargs.pop('use_labels', False) self.parens = kwargs.pop('parens', False) self.correlate = kwargs.pop('correlate', False) self.for_update = kwargs.pop('for_update', False) self.nowait = kwargs.pop('nowait', False) - self.limit = kwargs.get('limit', None) - self.offset = kwargs.get('offset', None) - for s in self.selects: + self.limit = kwargs.pop('limit', None) + self.offset = kwargs.pop('offset', None) + self.is_compound = True + self.is_where = False + self.is_scalar = False + + self.selects = selects + + for s in selects: s.group_by(None) s.order_by(None) - self.group_by(*kwargs.get('group_by', [None])) - self.order_by(*kwargs.get('order_by', [None])) + + self.group_by(*kwargs.pop('group_by', [None])) + self.order_by(*kwargs.pop('order_by', [None])) + if len(kwargs): + raise TypeError("invalid keyword argument(s) for CompoundSelect: %s" % repr(kwargs.keys())) self._col_map = {} name = property(lambda s:s.keyword + " statement") @@ -1420,9 +1441,9 @@ class CompoundSelect(_SelectBaseMixin, FromClause): class Select(_SelectBaseMixin, FromClause): """represents a SELECT statement, with appendable clauses, as well as the ability to execute itself and return a result set.""" - def __init__(self, columns=None, whereclause = None, from_obj = [], order_by = None, group_by=None, having=None, use_labels = False, distinct=False, for_update=False, nowait=False, engine=None, limit=None, offset=None, scalar=False, correlate=True): + def __init__(self, columns=None, whereclause = None, from_obj = [], order_by = None, group_by=None, having=None, use_labels = False, distinct=False, for_update=False, engine=None, limit=None, offset=None, scalar=False, correlate=True): _SelectBaseMixin.__init__(self) - self._froms = util.OrderedDict() + self.__froms = util.OrderedDict() self.use_labels = use_labels self.whereclause = None self.having = None @@ -1430,31 +1451,29 @@ class Select(_SelectBaseMixin, FromClause): self.limit = limit self.offset = offset self.for_update = for_update - self.nowait = nowait + self.is_compound = False # indicates that this select statement should not expand its columns # into the column clause of an enclosing select, and should instead # act like a single scalar column - self._scalar = scalar + self.is_scalar = scalar # indicates if this select statement, as a subquery, should correlate # its FROM clause to that of an enclosing select statement self.correlate = correlate # indicates if this select statement is a subquery inside another query - self.issubquery = False + self.is_subquery = False # indicates if this select statement is a subquery as a criterion # inside of a WHERE clause self.is_where = False self.distinct = distinct - self._text = None self._raw_columns = [] self._correlated = None - self._correlator = Select._CorrelatedVisitor(self, False) - self._wherecorrelator = Select._CorrelatedVisitor(self, True) - + self.__correlator = Select._CorrelatedVisitor(self, False) + self.__wherecorrelator = Select._CorrelatedVisitor(self, True) self.group_by(*(group_by or [None])) self.order_by(*(order_by or [None])) @@ -1471,10 +1490,6 @@ class Select(_SelectBaseMixin, FromClause): for f in from_obj: self.append_from(f) - def _foo(self): - raise "this is a temporary assertion while we refactor SQL to not call 'name' on non-table Selectables" - name = property(lambda s:s._foo()) #"SELECT statement") - class _CorrelatedVisitor(ClauseVisitor): """visits a clause, locates any Select clauses, and tells them that they should correlate their FROM list to that of their parent.""" @@ -1491,12 +1506,12 @@ class Select(_SelectBaseMixin, FromClause): if select is self.select: return select.is_where = self.is_where - select.issubquery = True + select.is_subquery = True select.parens = True if not select.correlate: return if getattr(select, '_correlated', None) is None: - select._correlated = self.select._froms + select._correlated = self.select._Select__froms def append_column(self, column): if _is_literal(column): @@ -1506,12 +1521,13 @@ class Select(_SelectBaseMixin, FromClause): # if the column is a Select statement itself, # accept visitor - column.accept_visitor(self._correlator) + column.accept_visitor(self.__correlator) # visit the FROM objects of the column looking for more Selects for f in column._get_from_objects(): - f.accept_visitor(self._correlator) - column._process_from_dict(self._froms, False) + f.accept_visitor(self.__correlator) + column._process_from_dict(self.__froms, False) + def _exportable_columns(self): return self._raw_columns def _proxy_column(self, column): @@ -1526,23 +1542,23 @@ class Select(_SelectBaseMixin, FromClause): def _append_condition(self, attribute, condition): if type(condition) == str: condition = _TextClause(condition) - condition.accept_visitor(self._wherecorrelator) - condition._process_from_dict(self._froms, False) + condition.accept_visitor(self.__wherecorrelator) + condition._process_from_dict(self.__froms, False) if getattr(self, attribute) is not None: setattr(self, attribute, and_(getattr(self, attribute), condition)) else: setattr(self, attribute, condition) def clear_from(self, from_obj): - self._froms[from_obj] = FromClause() + self.__froms[from_obj] = FromClause() def append_from(self, fromclause): if type(fromclause) == str: fromclause = _TextClause(fromclause) - fromclause.accept_visitor(self._correlator) - fromclause._process_from_dict(self._froms, True) + fromclause.accept_visitor(self.__correlator) + fromclause._process_from_dict(self.__froms, True) def _locate_oid_column(self): - for f in self._froms.values(): + for f in self.__froms.values(): if f is self: # we might be in our own _froms list if a column with us as the parent is attached, # which includes textual columns. @@ -1553,8 +1569,8 @@ class Select(_SelectBaseMixin, FromClause): else: return None def _get_froms(self): - return [f for f in self._froms.values() if f is not self and (self._correlated is None or not self._correlated.has_key(f))] - froms = property(lambda s: s._get_froms()) + return [f for f in self.__froms.values() if f is not self and (self._correlated is None or not self._correlated.has_key(f))] + froms = property(lambda s: s._get_froms(), doc="""a list containing all elements of the FROM clause""") def accept_visitor(self, visitor): # TODO: add contextual visit_ methods @@ -1581,7 +1597,7 @@ class Select(_SelectBaseMixin, FromClause): if self._engine is not None: return self._engine - for f in self._froms.values(): + for f in self.__froms.values(): if f is self: continue e = f.engine @@ -1657,7 +1673,7 @@ class _Update(_UpdateBase): visitor.visit_update(self) class _Delete(_UpdateBase): - def __init__(self, table, whereclause, **params): + def __init__(self, table, whereclause): self.table = table self.whereclause = whereclause |