diff options
Diffstat (limited to 'lib/sqlalchemy/sql/expression.py')
-rw-r--r-- | lib/sqlalchemy/sql/expression.py | 3305 |
1 files changed, 3305 insertions, 0 deletions
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py new file mode 100644 index 000000000..e117e3f47 --- /dev/null +++ b/lib/sqlalchemy/sql/expression.py @@ -0,0 +1,3305 @@ +# sql.py +# Copyright (C) 2005, 2006, 2007 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""Defines the base components of SQL expression trees. + + +All components are derived from a common base class +[sqlalchemy.sql#ClauseElement]. Common behaviors are organized based +on class hierarchies, in some cases via mixins. + +All object construction from this package occurs via functions which +in some cases will construct composite ``ClauseElement`` structures +together, and in other cases simply return a single ``ClauseElement`` +constructed directly. The function interface affords a more "DSL-ish" +feel to constructing SQL expressions and also allows future class +reorganizations. + +Even though classes are not constructed directly from the outside, +most classes which have additional public methods are considered to be +public (i.e. have no leading underscore). Other classes which are +"semi-public" are marked with a single leading underscore; these +classes usually have few or no public methods and are less guaranteed +to stay the same in future releases. +""" + +from sqlalchemy import util, exceptions +from sqlalchemy.sql import operators, visitors +from sqlalchemy import types as sqltypes +import re + +__all__ = [ + 'Alias', 'ClauseElement', + 'ColumnCollection', 'ColumnElement', + 'CompoundSelect', 'Delete', 'FromClause', 'Insert', 'Join', + 'Select', 'Selectable', 'TableClause', 'Update', 'alias', 'and_', 'asc', + 'between', 'bindparam', 'case', 'cast', 'column', 'delete', + 'desc', 'distinct', 'except_', 'except_all', 'exists', 'extract', 'func', + 'modifier', + 'insert', 'intersect', 'intersect_all', 'join', 'literal', + 'literal_column', 'not_', 'null', 'or_', 'outparam', 'outerjoin', 'select', + 'subquery', 'table', 'text', 'union', 'union_all', 'update', ] + +BIND_PARAMS = re.compile(r'(?<![:\w\x5c]):(\w+)(?!:)', re.UNICODE) + +def desc(column): + """Return a descending ``ORDER BY`` clause element. + + e.g.:: + + order_by = [desc(table1.mycol)] + """ + return _UnaryExpression(column, modifier=operators.desc_op) + +def asc(column): + """Return an ascending ``ORDER BY`` clause element. + + e.g.:: + + order_by = [asc(table1.mycol)] + """ + return _UnaryExpression(column, modifier=operators.asc_op) + +def outerjoin(left, right, onclause=None, **kwargs): + """Return an ``OUTER JOIN`` clause element. + + The returned object is an instance of [sqlalchemy.sql#Join]. + + Similar functionality is also available via the ``outerjoin()`` + method on any [sqlalchemy.sql#FromClause]. + + left + The left side of the join. + + right + The right side of the join. + + onclause + Optional criterion for the ``ON`` clause, is derived from + foreign key relationships established between left and right + otherwise. + + To chain joins together, use the ``join()`` or ``outerjoin()`` + methods on the resulting ``Join`` object. + """ + + return Join(left, right, onclause, isouter = True, **kwargs) + +def join(left, right, onclause=None, **kwargs): + """Return a ``JOIN`` clause element (regular inner join). + + The returned object is an instance of [sqlalchemy.sql#Join]. + + Similar functionality is also available via the ``join()`` method + on any [sqlalchemy.sql#FromClause]. + + left + The left side of the join. + + right + The right side of the join. + + onclause + Optional criterion for the ``ON`` clause, is derived from + foreign key relationships established between left and right + otherwise. + + To chain joins together, use the ``join()`` or ``outerjoin()`` + methods on the resulting ``Join`` object. + """ + + return Join(left, right, onclause, **kwargs) + +def select(columns=None, whereclause=None, from_obj=[], **kwargs): + """Returns a ``SELECT`` clause element. + + Similar functionality is also available via the ``select()`` + method on any [sqlalchemy.sql#FromClause]. + + The returned object is an instance of [sqlalchemy.sql#Select]. + + All arguments which accept ``ClauseElement`` arguments also accept + string arguments, which will be converted as appropriate into + either ``text()`` or ``literal_column()`` constructs. + + columns + A list of ``ClauseElement`` objects, typically ``ColumnElement`` + objects or subclasses, which will form the columns clause of the + resulting statement. For all members which are instances of + ``Selectable``, the individual ``ColumnElement`` members of the + ``Selectable`` will be added individually to the columns clause. + For example, specifying a ``Table`` instance will result in all + the contained ``Column`` objects within to be added to the + columns clause. + + This argument is not present on the form of ``select()`` + available on ``Table``. + + whereclause + A ``ClauseElement`` expression which will be used to form the + ``WHERE`` clause. + + from_obj + A list of ``ClauseElement`` objects which will be added to the + ``FROM`` clause of the resulting statement. Note that "from" + objects are automatically located within the columns and + whereclause ClauseElements. Use this parameter to explicitly + specify "from" objects which are not automatically locatable. + This could include ``Table`` objects that aren't otherwise + present, or ``Join`` objects whose presence will supercede that + of the ``Table`` objects already located in the other clauses. + + \**kwargs + Additional parameters include: + + prefixes + a list of strings or ``ClauseElement`` objects to include + directly after the SELECT keyword in the generated statement, + for dialect-specific query features. + + distinct=False + when ``True``, applies a ``DISTINCT`` qualifier to the columns + clause of the resulting statement. + + use_labels=False + when ``True``, the statement will be generated using labels + for each column in the columns clause, which qualify each + column with its parent table's (or aliases) name so that name + conflicts between columns in different tables don't occur. + The format of the label is <tablename>_<column>. The "c" + collection of the resulting ``Select`` object will use these + names as well for targeting column members. + + for_update=False + when ``True``, applies ``FOR UPDATE`` to the end of the + resulting statement. Certain database dialects also support + alternate values for this parameter, for example mysql + supports "read" which translates to ``LOCK IN SHARE MODE``, + and oracle supports "nowait" which translates to ``FOR UPDATE + NOWAIT``. + + correlate=True + indicates that this ``Select`` object should have its + contained ``FromClause`` elements "correlated" to an enclosing + ``Select`` object. This means that any ``ClauseElement`` + instance within the "froms" collection of this ``Select`` + which is also present in the "froms" collection of an + enclosing select will not be rendered in the ``FROM`` clause + of this select statement. + + group_by + a list of ``ClauseElement`` objects which will comprise the + ``GROUP BY`` clause of the resulting select. + + having + a ``ClauseElement`` that will comprise the ``HAVING`` clause + of the resulting select when ``GROUP BY`` is used. + + order_by + a scalar or list of ``ClauseElement`` objects which will + comprise the ``ORDER BY`` clause of the resulting select. + + limit=None + a numerical value which usually compiles to a ``LIMIT`` + expression in the resulting select. Databases that don't + support ``LIMIT`` will attempt to provide similar + functionality. + + offset=None + a numeric value which usually compiles to an ``OFFSET`` + expression in the resulting select. Databases that don't + support ``OFFSET`` will attempt to provide similar + functionality. + + bind=None + an ``Engine`` or ``Connection`` instance to which the + resulting ``Select ` object will be bound. The ``Select`` + object will otherwise automatically bind to whatever + ``Connectable`` instances can be located within its contained + ``ClauseElement`` members. + + scalar=False + deprecated. Use select(...).as_scalar() to create a "scalar + column" proxy for an existing Select object. + """ + + if 'scalar' in kwargs: + util.warn_deprecated('scalar option is deprecated; see docs for details') + scalar = kwargs.pop('scalar', False) + s = Select(columns, whereclause=whereclause, from_obj=from_obj, **kwargs) + if scalar: + return s.as_scalar() + else: + return s + +def subquery(alias, *args, **kwargs): + """Return an [sqlalchemy.sql#Alias] object derived from a [sqlalchemy.sql#Select]. + + name + alias name + + \*args, \**kwargs + + all other arguments are delivered to the [sqlalchemy.sql#select()] + function. + """ + + return Select(*args, **kwargs).alias(alias) + +def insert(table, values = None, **kwargs): + """Return an [sqlalchemy.sql#Insert] clause element. + + Similar functionality is available via the ``insert()`` method on + [sqlalchemy.schema#Table]. + + table + The table to be inserted into. + + values + A dictionary which specifies the column specifications of the + ``INSERT``, and is optional. If left as None, the column + specifications are determined from the bind parameters used + during the compile phase of the ``INSERT`` statement. If the + bind parameters also are None during the compile phase, then the + column specifications will be generated from the full list of + table columns. + + If both `values` and compile-time bind parameters are present, the + compile-time bind parameters override the information specified + within `values` on a per-key basis. + + The keys within `values` can be either ``Column`` objects or their + string identifiers. Each key may reference one of: + + * a literal data value (i.e. string, number, etc.); + * a Column object; + * a SELECT statement. + + If a ``SELECT`` statement is specified which references this + ``INSERT`` statement's table, the statement will be correlated + against the ``INSERT`` statement. + """ + + return Insert(table, values, **kwargs) + +def update(table, whereclause = None, values = None, **kwargs): + """Return an [sqlalchemy.sql#Update] clause element. + + Similar functionality is available via the ``update()`` method on + [sqlalchemy.schema#Table]. + + table + The table to be updated. + + whereclause + A ``ClauseElement`` describing the ``WHERE`` condition of the + ``UPDATE`` statement. + + values + A dictionary which specifies the ``SET`` conditions of the + ``UPDATE``, and is optional. If left as None, the ``SET`` + conditions are determined from the bind parameters used during + the compile phase of the ``UPDATE`` statement. If the bind + parameters also are None during the compile phase, then the + ``SET`` conditions will be generated from the full list of table + columns. + + If both `values` and compile-time bind parameters are present, the + compile-time bind parameters override the information specified + within `values` on a per-key basis. + + The keys within `values` can be either ``Column`` objects or their + string identifiers. Each key may reference one of: + + * a literal data value (i.e. string, number, etc.); + * a Column object; + * a SELECT statement. + + If a ``SELECT`` statement is specified which references this + ``UPDATE`` statement's table, the statement will be correlated + against the ``UPDATE`` statement. + """ + + return Update(table, whereclause, values, **kwargs) + +def delete(table, whereclause = None, **kwargs): + """Return a [sqlalchemy.sql#Delete] clause element. + + Similar functionality is available via the ``delete()`` method on + [sqlalchemy.schema#Table]. + + table + The table to be updated. + + whereclause + A ``ClauseElement`` describing the ``WHERE`` condition of the + ``UPDATE`` statement. + """ + + return Delete(table, whereclause, **kwargs) + +def and_(*clauses): + """Join a list of clauses together using the ``AND`` operator. + + The ``&`` operator is also overloaded on all + [sqlalchemy.sql#_CompareMixin] subclasses to produce the same + result. + """ + if len(clauses) == 1: + return clauses[0] + return ClauseList(operator=operators.and_, *clauses) + +def or_(*clauses): + """Join a list of clauses together using the ``OR`` operator. + + The ``|`` operator is also overloaded on all + [sqlalchemy.sql#_CompareMixin] subclasses to produce the same + result. + """ + + if len(clauses) == 1: + return clauses[0] + return ClauseList(operator=operators.or_, *clauses) + +def not_(clause): + """Return a negation of the given clause, i.e. ``NOT(clause)``. + + The ``~`` operator is also overloaded on all + [sqlalchemy.sql#_CompareMixin] subclasses to produce the same + result. + """ + + return operators.inv(clause) + +def distinct(expr): + """Return a ``DISTINCT`` clause.""" + + return _UnaryExpression(expr, operator=operators.distinct_op) + +def between(ctest, cleft, cright): + """Return a ``BETWEEN`` predicate clause. + + Equivalent of SQL ``clausetest BETWEEN clauseleft AND clauseright``. + + The ``between()`` method on all [sqlalchemy.sql#_CompareMixin] subclasses + provides similar functionality. + """ + + ctest = _literal_as_binds(ctest) + return _BinaryExpression(ctest, ClauseList(_literal_as_binds(cleft, type_=ctest.type), _literal_as_binds(cright, type_=ctest.type), operator=operators.and_, group=False), operators.between_op) + + +def case(whens, value=None, else_=None): + """Produce a ``CASE`` statement. + + whens + A sequence of pairs to be translated into "when / then" clauses. + + value + Optional for simple case statements. + + else\_ + Optional as well, for case defaults. + """ + + whenlist = [ClauseList('WHEN', c, 'THEN', r, operator=None) + for (c,r) in whens] + if not else_ is None: + whenlist.append(ClauseList('ELSE', else_, operator=None)) + if whenlist: + type = list(whenlist[-1])[-1].type + else: + type = None + cc = _CalculatedClause(None, 'CASE', value, type_=type, operator=None, group_contents=False, *whenlist + ['END']) + return cc + +def cast(clause, totype, **kwargs): + """Return a ``CAST`` function. + + Equivalent of SQL ``CAST(clause AS totype)``. + + Use with a [sqlalchemy.types#TypeEngine] subclass, i.e:: + + cast(table.c.unit_price * table.c.qty, Numeric(10,4)) + + or:: + + cast(table.c.timestamp, DATE) + """ + + return _Cast(clause, totype, **kwargs) + +def extract(field, expr): + """Return the clause ``extract(field FROM expr)``.""" + + expr = _BinaryExpression(text(field), expr, operators.from_) + return func.extract(expr) + +def exists(*args, **kwargs): + """Return an ``EXISTS`` clause as applied to a [sqlalchemy.sql#Select] object. + + The resulting [sqlalchemy.sql#_Exists] object can be executed by + itself or used as a subquery within an enclosing select. + + \*args, \**kwargs + all arguments are sent directly to the [sqlalchemy.sql#select()] + function to produce a ``SELECT`` statement. + """ + + return _Exists(*args, **kwargs) + +def union(*selects, **kwargs): + """Return a ``UNION`` of multiple selectables. + + The returned object is an instance of [sqlalchemy.sql#CompoundSelect]. + + A similar ``union()`` method is available on all + [sqlalchemy.sql#FromClause] subclasses. + + \*selects + a list of [sqlalchemy.sql#Select] instances. + + \**kwargs + available keyword arguments are the same as those of + [sqlalchemy.sql#select()]. + """ + + return _compound_select('UNION', *selects, **kwargs) + +def union_all(*selects, **kwargs): + """Return a ``UNION ALL`` of multiple selectables. + + The returned object is an instance of [sqlalchemy.sql#CompoundSelect]. + + A similar ``union_all()`` method is available on all + [sqlalchemy.sql#FromClause] subclasses. + + \*selects + a list of [sqlalchemy.sql#Select] instances. + + \**kwargs + available keyword arguments are the same as those of + [sqlalchemy.sql#select()]. + """ + + return _compound_select('UNION ALL', *selects, **kwargs) + +def except_(*selects, **kwargs): + """Return an ``EXCEPT`` of multiple selectables. + + The returned object is an instance of [sqlalchemy.sql#CompoundSelect]. + + \*selects + a list of [sqlalchemy.sql#Select] instances. + + \**kwargs + available keyword arguments are the same as those of + [sqlalchemy.sql#select()]. + """ + return _compound_select('EXCEPT', *selects, **kwargs) + +def except_all(*selects, **kwargs): + """Return an ``EXCEPT ALL`` of multiple selectables. + + The returned object is an instance of [sqlalchemy.sql#CompoundSelect]. + + \*selects + a list of [sqlalchemy.sql#Select] instances. + + \**kwargs + available keyword arguments are the same as those of + [sqlalchemy.sql#select()]. + """ + return _compound_select('EXCEPT ALL', *selects, **kwargs) + +def intersect(*selects, **kwargs): + """Return an ``INTERSECT`` of multiple selectables. + + The returned object is an instance of [sqlalchemy.sql#CompoundSelect]. + + \*selects + a list of [sqlalchemy.sql#Select] instances. + + \**kwargs + available keyword arguments are the same as those of + [sqlalchemy.sql#select()]. + """ + return _compound_select('INTERSECT', *selects, **kwargs) + +def intersect_all(*selects, **kwargs): + """Return an ``INTERSECT ALL`` of multiple selectables. + + The returned object is an instance of [sqlalchemy.sql#CompoundSelect]. + + \*selects + a list of [sqlalchemy.sql#Select] instances. + + \**kwargs + available keyword arguments are the same as those of + [sqlalchemy.sql#select()]. + """ + return _compound_select('INTERSECT ALL', *selects, **kwargs) + +def alias(selectable, alias=None): + """Return an [sqlalchemy.sql#Alias] object. + + An ``Alias`` represents any [sqlalchemy.sql#FromClause] with + an alternate name assigned within SQL, typically using the ``AS`` + clause when generated, e.g. ``SELECT * FROM table AS aliasname``. + + Similar functionality is available via the ``alias()`` method + available on all ``FromClause`` subclasses. + + selectable + any ``FromClause`` subclass, such as a table, select + statement, etc.. + + alias + string name to be assigned as the alias. If ``None``, a + random name will be generated. + """ + + return Alias(selectable, alias=alias) + + +def literal(value, type_=None): + """Return a literal clause, bound to a bind parameter. + + Literal clauses are created automatically when non- + ``ClauseElement`` objects (such as strings, ints, dates, etc.) are + used in a comparison operation with a + [sqlalchemy.sql#_CompareMixin] subclass, such as a ``Column`` + object. Use this function to force the generation of a literal + clause, which will be created as a + [sqlalchemy.sql#_BindParamClause] with a bound value. + + value + the value to be bound. Can be any Python object supported by + the underlying DBAPI, or is translatable via the given type + argument. + + type\_ + an optional [sqlalchemy.types#TypeEngine] which will provide + bind-parameter translation for this literal. + """ + + return _BindParamClause('literal', value, type_=type_, unique=True) + +def label(name, obj): + """Return a [sqlalchemy.sql#_Label] object for the given [sqlalchemy.sql#ColumnElement]. + + A label changes the name of an element in the columns clause of a + ``SELECT`` statement, typically via the ``AS`` SQL keyword. + + This functionality is more conveniently available via the + ``label()`` method on ``ColumnElement``. + + name + label name + + obj + a ``ColumnElement``. + """ + + return _Label(name, obj) + +def column(text, type_=None): + """Return a textual column clause, as would be in the columns clause of a ``SELECT`` statement. + + The object returned is an instance of [sqlalchemy.sql#_ColumnClause], + which represents the "syntactical" portion of the schema-level + [sqlalchemy.schema#Column] object. + + text + the name of the column. Quoting rules will be applied to the + clause like any other column name. For textual column + constructs that are not to be quoted, use the + [sqlalchemy.sql#literal_column()] function. + + type\_ + an optional [sqlalchemy.types#TypeEngine] object which will + provide result-set translation for this column. + + """ + + return _ColumnClause(text, type_=type_) + +def literal_column(text, type_=None): + """Return a textual column clause, as would be in the columns clause of a ``SELECT`` statement. + + The object returned is an instance of [sqlalchemy.sql#_ColumnClause], + which represents the "syntactical" portion of the schema-level + [sqlalchemy.schema#Column] object. + + text + the name of the column. Quoting rules will not be applied to + the column. For textual column constructs that should be quoted + like any other column construct, use the + [sqlalchemy.sql#column()] function. + + type + an optional [sqlalchemy.types#TypeEngine] object which will + provide result-set translation for this column. + """ + + return _ColumnClause(text, type_=type_, is_literal=True) + +def table(name, *columns): + """Return a [sqlalchemy.sql#Table] object. + + This is a primitive version of the [sqlalchemy.schema#Table] object, + which is a subclass of this object. + """ + + return TableClause(name, *columns) + +def bindparam(key, value=None, type_=None, shortname=None, unique=False): + """Create a bind parameter clause with the given key. + + value + a default value for this bind parameter. a bindparam with a + value is called a ``value-based bindparam``. + + shortname + an ``alias`` for this bind parameter. usually used to alias the + ``key`` nd ``label`` of a column, i.e. ``somecolname`` and + ``sometable_somecolname`` + + type + a sqlalchemy.types.TypeEngine object indicating the type of this + bind param, will invoke type-specific bind parameter processing + + unique + if True, bind params sharing the same name will have their + underlying ``key`` modified to a uniquely generated name. + mostly useful with value-based bind params. + """ + + if isinstance(key, _ColumnClause): + return _BindParamClause(key.name, value, type_=key.type, shortname=shortname, unique=unique) + else: + return _BindParamClause(key, value, type_=type_, shortname=shortname, unique=unique) + +def outparam(key, type_=None): + """Create an 'OUT' parameter for usage in functions (stored procedures), for databases which support them. + + The ``outparam`` can be used like a regular function parameter. + The "output" value will be available from the + [sqlalchemy.engine#ResultProxy] object via its ``out_parameters`` + attribute, which returns a dictionary containing the values. + """ + + return _BindParamClause(key, None, type_=type_, unique=False, isoutparam=True) + +def text(text, bind=None, *args, **kwargs): + """Create literal text to be inserted into a query. + + When constructing a query from a ``select()``, ``update()``, + ``insert()`` or ``delete()``, using plain strings for argument + values will usually result in text objects being created + automatically. Use this function when creating textual clauses + outside of other ``ClauseElement`` objects, or optionally wherever + plain text is to be used. + + text + the text of the SQL statement to be created. use ``:<param>`` + to specify bind parameters; they will be compiled to their + engine-specific format. + + bind + an optional connection or engine to be used for this text query. + + bindparams + a list of ``bindparam()`` instances which can be used to define + the types and/or initial values for the bind parameters within + the textual statement; the keynames of the bindparams must match + those within the text of the statement. The types will be used + for pre-processing on bind values. + + typemap + a dictionary mapping the names of columns represented in the + ``SELECT`` clause of the textual statement to type objects, + which will be used to perform post-processing on columns within + the result set (for textual statements that produce result + sets). + """ + + return _TextClause(text, bind=bind, *args, **kwargs) + +def null(): + """Return a ``_Null`` object, which compiles to ``NULL`` in a sql statement.""" + + return _Null() + +class _FunctionGenerator(object): + """Generate ``_Function`` objects based on getattr calls.""" + + def __init__(self, **opts): + self.__names = [] + self.opts = opts + + def __getattr__(self, name): + # passthru __ attributes; fixes pydoc + if name.startswith('__'): + try: + return self.__dict__[name] + except KeyError: + raise AttributeError(name) + + elif name.startswith('_'): + name = name[0:-1] + f = _FunctionGenerator(**self.opts) + f.__names = list(self.__names) + [name] + return f + + def __call__(self, *c, **kwargs): + o = self.opts.copy() + o.update(kwargs) + return _Function(self.__names[-1], packagenames=self.__names[0:-1], *c, **o) + +func = _FunctionGenerator() + +# TODO: use UnaryExpression for this instead ? +modifier = _FunctionGenerator(group=False) + + +def _compound_select(keyword, *selects, **kwargs): + return CompoundSelect(keyword, *selects, **kwargs) + +def _is_literal(element): + return not isinstance(element, ClauseElement) + +def _literal_as_text(element): + if isinstance(element, Operators): + return element.expression_element() + elif _is_literal(element): + return _TextClause(unicode(element)) + else: + return element + +def _literal_as_column(element): + if isinstance(element, Operators): + return element.clause_element() + elif _is_literal(element): + return literal_column(str(element)) + else: + return element + +def _literal_as_binds(element, name='literal', type_=None): + if isinstance(element, Operators): + return element.expression_element() + elif _is_literal(element): + if element is None: + return null() + else: + return _BindParamClause(name, element, shortname=name, type_=type_, unique=True) + else: + return element + +def _selectable(element): + if hasattr(element, '__selectable__'): + return element.__selectable__() + elif isinstance(element, Selectable): + return element + else: + raise exceptions.ArgumentError("Object '%s' is not a Selectable and does not implement `__selectable__()`" % repr(element)) + +def is_column(col): + """True if ``col`` is an instance of ``ColumnElement``.""" + return isinstance(col, ColumnElement) + + +class _FigureVisitName(type): + def __init__(cls, clsname, bases, dict): + if not '__visit_name__' in cls.__dict__: + m = re.match(r'_?(\w+?)(?:Expression|Clause|Element|$)', clsname) + x = m.group(1) + x = re.sub(r'(?!^)[A-Z]', lambda m:'_'+m.group(0).lower(), x) + cls.__visit_name__ = x.lower() + super(_FigureVisitName, cls).__init__(clsname, bases, dict) + +class ClauseElement(object): + """Base class for elements of a programmatically constructed SQL expression.""" + __metaclass__ = _FigureVisitName + + def _clone(self): + """Create a shallow copy of this ClauseElement. + + This method may be used by a generative API. Its also used as + part of the "deep" copy afforded by a traversal that combines + the _copy_internals() method. + """ + + c = self.__class__.__new__(self.__class__) + c.__dict__ = self.__dict__.copy() + return c + + def _get_from_objects(self, **modifiers): + """Return objects represented in this ``ClauseElement`` that + should be added to the ``FROM`` list of a query, when this + ``ClauseElement`` is placed in the column clause of a + ``Select`` statement. + """ + + raise NotImplementedError(repr(self)) + + def _hide_froms(self, **modifiers): + """Return a list of ``FROM`` clause elements which this ``ClauseElement`` replaces.""" + + return [] + + def unique_params(self, *optionaldict, **kwargs): + """Return a copy with ``bindparam()`` elments replaced. + + Same functionality as ``params()``, except adds `unique=True` + to affected bind parameters so that multiple statements can be + used. + """ + + return self._params(True, optionaldict, kwargs) + def params(self, *optionaldict, **kwargs): + """Return a copy with ``bindparam()`` elments replaced. + + Returns a copy of this ClauseElement with ``bindparam()`` + elements replaced with values taken from the given dictionary:: + + >>> clause = column('x') + bindparam('foo') + >>> print clause.compile().params + {'foo':None} + >>> print clause.params({'foo':7}).compile().params + {'foo':7} + """ + + return self._params(False, optionaldict, kwargs) + + def _params(self, unique, optionaldict, kwargs): + if len(optionaldict) == 1: + kwargs.update(optionaldict[0]) + elif len(optionaldict) > 1: + raise exceptions.ArgumentError("params() takes zero or one positional dictionary argument") + + class Vis(visitors.ClauseVisitor): + def visit_bindparam(self, bind): + if bind.key in kwargs: + bind.value = kwargs[bind.key] + if unique: + bind.unique=True + return Vis().traverse(self, clone=True) + + def compare(self, other): + """Compare this ClauseElement to the given ClauseElement. + + Subclasses should override the default behavior, which is a + straight identity comparison. + """ + + return self is other + + def _copy_internals(self): + """Reassign internal elements to be clones of themselves. + + Called during a copy-and-traverse operation on newly + shallow-copied elements to create a deep copy. + """ + + pass + + def get_children(self, **kwargs): + """Return immediate child elements of this ``ClauseElement``. + + This is used for visit traversal. + + \**kwargs may contain flags that change the collection that is + returned, for example to return a subset of items in order to + cut down on larger traversals, or to return child items from a + different context (such as schema-level collections instead of + clause-level). + """ + return [] + + def self_group(self, against=None): + return self + + def supports_execution(self): + """Return True if this clause element represents a complete executable statement.""" + + return False + + def _find_engine(self): + """Default strategy for locating an engine within the clause element. + + Relies upon a local engine property, or looks in the *from* + objects which ultimately have to contain Tables or + TableClauses. + """ + + try: + if self._bind is not None: + return self._bind + except AttributeError: + pass + for f in self._get_from_objects(): + if f is self: + continue + engine = f.bind + if engine is not None: + return engine + else: + return None + + bind = property(lambda s:s._find_engine(), doc="""Returns the Engine or Connection to which this ClauseElement is bound, or None if none found.""") + + def execute(self, *multiparams, **params): + """Compile and execute this ``ClauseElement``.""" + + if multiparams: + compile_params = multiparams[0] + else: + compile_params = params + return self.compile(bind=self.bind, parameters=compile_params).execute(*multiparams, **params) + + def scalar(self, *multiparams, **params): + """Compile and execute this ``ClauseElement``, returning the result's scalar representation.""" + + return self.execute(*multiparams, **params).scalar() + + def compile(self, bind=None, parameters=None, compiler=None, dialect=None): + """Compile this SQL expression. + + Uses the given ``Compiler``, or the given ``AbstractDialect`` + or ``Engine`` to create a ``Compiler``. If no `compiler` + arguments are given, tries to use the underlying ``Engine`` + this ``ClauseElement`` is bound to to create a ``Compiler``, + if any. + + Finally, if there is no bound ``Engine``, uses an + ``DefaultDialect`` to create a default ``Compiler``. + + `parameters` is a dictionary representing the default bind + parameters to be used with the statement. If `parameters` is + a list, it is assumed to be a list of dictionaries and the + first dictionary in the list is used with which to compile + against. + + The bind parameters can in some cases determine the output of + the compilation, such as for ``UPDATE`` and ``INSERT`` + statements the bind parameters that are present determine the + ``SET`` and ``VALUES`` clause of those statements. + """ + + if isinstance(parameters, (list, tuple)): + parameters = parameters[0] + + if compiler is None: + if dialect is not None: + compiler = dialect.statement_compiler(dialect, self, parameters) + elif bind is not None: + compiler = bind.statement_compiler(self, parameters) + elif self.bind is not None: + compiler = self.bind.statement_compiler(self, parameters) + + if compiler is None: + from sqlalchemy.engine.default import DefaultDialect + dialect = DefaultDialect() + compiler = dialect.statement_compiler(dialect, self, parameters=parameters) + compiler.compile() + return compiler + + def __str__(self): + return unicode(self.compile()).encode('ascii', 'backslashreplace') + + def __and__(self, other): + return and_(self, other) + + def __or__(self, other): + return or_(self, other) + + def __invert__(self): + return self._negate() + + def _negate(self): + if hasattr(self, 'negation_clause'): + return self.negation_clause + else: + return _UnaryExpression(self.self_group(against=operators.inv), operator=operators.inv, negate=None) + + +class Operators(object): + def __and__(self, other): + return self.operate(operators.and_, other) + + def __or__(self, other): + return self.operate(operators.or_, other) + + def __invert__(self): + return self.operate(operators.inv) + + def clause_element(self): + raise NotImplementedError() + + def operate(self, op, *other, **kwargs): + raise NotImplementedError() + + def reverse_operate(self, op, other, **kwargs): + raise NotImplementedError() + +class ColumnOperators(Operators): + """Defines comparison and math operations.""" + + def __lt__(self, other): + return self.operate(operators.lt, other) + + def __le__(self, other): + return self.operate(operators.le, other) + + def __eq__(self, other): + return self.operate(operators.eq, other) + + def __ne__(self, other): + return self.operate(operators.ne, other) + + def __gt__(self, other): + return self.operate(operators.gt, other) + + def __ge__(self, other): + return self.operate(operators.ge, other) + + def concat(self, other): + return self.operate(operators.concat_op, other) + + def like(self, other): + return self.operate(operators.like_op, other) + + def in_(self, *other): + return self.operate(operators.in_op, other) + + def startswith(self, other): + return self.operate(operators.startswith_op, other) + + def endswith(self, other): + return self.operate(operators.endswith_op, other) + + def desc(self): + return self.operate(operators.desc_op) + + def asc(self): + return self.operate(operators.asc_op) + + def __radd__(self, other): + return self.reverse_operate(operators.add, other) + + def __rsub__(self, other): + return self.reverse_operate(operators.sub, other) + + def __rmul__(self, other): + return self.reverse_operate(operators.mul, other) + + def __rdiv__(self, other): + return self.reverse_operate(operators.div, other) + + def between(self, cleft, cright): + return self.operate(operators.between_op, cleft, cright) + + def distinct(self): + return self.operate(operators.distinct_op) + + def __add__(self, other): + return self.operate(operators.add, other) + + def __sub__(self, other): + return self.operate(operators.sub, other) + + def __mul__(self, other): + return self.operate(operators.mul, other) + + def __div__(self, other): + return self.operate(operators.div, other) + + def __mod__(self, other): + return self.operate(operators.mod, other) + + def __truediv__(self, other): + return self.operate(operators.truediv, other) + +# precedence ordering for common operators. if an operator is not +# present in this list, it will be parenthesized when grouped against +# other operators +_smallest = object() +_largest = object() + +PRECEDENCE = { + operators.from_:15, + operators.mul:7, + operators.div:7, + operators.mod:7, + operators.add:6, + operators.sub:6, + operators.concat_op:6, + operators.ilike_op:5, + operators.notilike_op:5, + operators.like_op:5, + operators.notlike_op:5, + operators.in_op:5, + operators.notin_op:5, + operators.is_:5, + operators.isnot:5, + operators.eq:5, + operators.ne:5, + operators.gt:5, + operators.lt:5, + operators.ge:5, + operators.le:5, + operators.between_op:5, + operators.distinct_op:5, + operators.inv:4, + operators.and_:3, + operators.or_:2, + operators.comma_op:-1, + operators.as_:-1, + operators.exists:0, + _smallest: -1000, + _largest: 1000 +} + +class _CompareMixin(ColumnOperators): + """Defines comparison and math operations for ``ClauseElement`` instances.""" + + def __compare(self, op, obj, negate=None): + if obj is None or isinstance(obj, _Null): + if op == operators.eq: + return _BinaryExpression(self.expression_element(), null(), operators.is_, negate=operators.isnot) + elif op == operators.ne: + return _BinaryExpression(self.expression_element(), null(), operators.isnot, negate=operators.is_) + else: + raise exceptions.ArgumentError("Only '='/'!=' operators can be used with NULL") + else: + obj = self._check_literal(obj) + return _BinaryExpression(self.expression_element(), obj, op, type_=sqltypes.Boolean, negate=negate) + + def __operate(self, op, obj): + obj = self._check_literal(obj) + + type_ = self._compare_type(obj) + + # TODO: generalize operator overloading like this out into the + # types module + if op == operators.add and isinstance(type_, (sqltypes.Concatenable)): + op = operators.concat_op + return _BinaryExpression(self.expression_element(), obj, op, type_=type_) + operators = { + operators.add : (__operate,), + operators.mul : (__operate,), + operators.sub : (__operate,), + operators.div : (__operate,), + operators.mod : (__operate,), + operators.truediv : (__operate,), + operators.lt : (__compare, operators.ge), + operators.le : (__compare, operators.gt), + operators.ne : (__compare, operators.eq), + operators.gt : (__compare, operators.le), + operators.ge : (__compare, operators.lt), + operators.eq : (__compare, operators.ne), + operators.like_op : (__compare, operators.notlike_op), + } + + def operate(self, op, *other): + o = _CompareMixin.operators[op] + return o[0](self, op, other[0], *o[1:]) + + def reverse_operate(self, op, other): + return self._bind_param(other).operate(op, self) + + def in_(self, *other): + return self._in_impl(operators.in_op, operators.notin_op, *other) + + def _in_impl(self, op, negate_op, *other): + if len(other) == 0: + return _Grouping(case([(self.__eq__(None), text('NULL'))], else_=text('0')).__eq__(text('1'))) + elif len(other) == 1: + o = other[0] + if _is_literal(o) or isinstance( o, _CompareMixin): + return self.__eq__( o) #single item -> == + else: + assert isinstance(o, Selectable) + return self.__compare( op, o, negate=negate_op) #single selectable + + args = [] + for o in other: + if not _is_literal(o): + if not isinstance( o, _CompareMixin): + raise exceptions.InvalidRequestError( "in() function accepts either non-selectable values, or a single selectable: "+repr(o) ) + else: + o = self._bind_param(o) + args.append(o) + return self.__compare(op, ClauseList(*args).self_group(against=op), negate=negate_op) + + def startswith(self, other): + """Produce the clause ``LIKE '<other>%'``""" + + perc = isinstance(other,(str,unicode)) and '%' or literal('%',type_= sqltypes.String) + return self.__compare(operators.like_op, other + perc) + + def endswith(self, other): + """Produce the clause ``LIKE '%<other>'``""" + + if isinstance(other,(str,unicode)): po = '%' + other + else: + po = literal('%', type_=sqltypes.String) + other + po.type = sqltypes.to_instance(sqltypes.String) #force! + return self.__compare(operators.like_op, po) + + def label(self, name): + """Produce a column label, i.e. ``<columnname> AS <name>``""" + return _Label(name, self, self.type) + + def desc(self): + """Produce a DESC clause, i.e. ``<columnname> DESC``""" + + return desc(self) + + def asc(self): + """Produce a ASC clause, i.e. ``<columnname> ASC``""" + + return asc(self) + + def distinct(self): + """Produce a DISTINCT clause, i.e. ``DISTINCT <columnname>``""" + return _UnaryExpression(self, operator=operators.distinct_op) + + def between(self, cleft, cright): + """Produce a BETWEEN clause, i.e. ``<column> BETWEEN <cleft> AND <cright>``""" + + return _BinaryExpression(self, ClauseList(self._check_literal(cleft), self._check_literal(cright), operator=operators.and_, group=False), operators.between_op) + + def op(self, operator): + """produce a generic operator function. + + e.g.:: + + somecolumn.op("*")(5) + + produces:: + + somecolumn * 5 + + operator + a string which will be output as the infix operator between + this ``ClauseElement`` and the expression passed to the + generated function. + """ + return lambda other: self.__operate(operator, other) + + def _bind_param(self, obj): + return _BindParamClause('literal', obj, shortname=None, type_=self.type, unique=True) + + def _check_literal(self, other): + if isinstance(other, Operators): + return other.expression_element() + elif _is_literal(other): + return self._bind_param(other) + else: + return other + + def clause_element(self): + """Allow ``_CompareMixins`` to return the underlying ``ClauseElement``, for non-``ClauseElement`` ``_CompareMixins``.""" + return self + + def expression_element(self): + """Allow ``_CompareMixins`` to return the appropriate object to be used in expressions.""" + + return self + + def _compare_type(self, obj): + """Allow subclasses to override the type used in constructing + ``_BinaryExpression`` objects. + + Default return value is the type of the given object. + """ + + return obj.type + +class Selectable(ClauseElement): + """Represent a column list-holding object. + + This is the common base class of [sqlalchemy.sql#ColumnElement] + and [sqlalchemy.sql#FromClause]. The reason ``ColumnElement`` is + marked as a "list-holding" object is so that it can be treated + similarly to ``FromClause`` in column-selection scenarios; it + contains a list of columns consisting of itself. + """ + + columns = util.NotImplProperty("""a [sqlalchemy.sql#ColumnCollection] containing ``ColumnElement`` instances.""") + + def select(self, whereclauses = None, **params): + return select([self], whereclauses, **params) + +class ColumnElement(ClauseElement, _CompareMixin): + """Represent an element that is usable within the "column clause" portion of a ``SELECT`` statement. + + This includes columns associated with tables, aliases, and + subqueries, expressions, function calls, SQL keywords such as + ``NULL``, literals, etc. ``ColumnElement`` is the ultimate base + class for all such elements. + + ``ColumnElement`` supports the ability to be a *proxy* element, + which indicates that the ``ColumnElement`` may be associated with + a ``Selectable`` which was derived from another ``Selectable``. + An example of a "derived" ``Selectable`` is an ``Alias`` of a + ``Table``. + + A ``ColumnElement``, by subclassing the ``_CompareMixin`` mixin + class, provides the ability to generate new ``ClauseElement`` + objects using Python expressions. See the ``_CompareMixin`` + docstring for more details. + """ + + primary_key = property(lambda self:getattr(self, '_primary_key', False), + doc=\ + """Primary key flag. Indicates if this ``Column`` represents part or + whole of a primary key for its parent table. + """) + foreign_keys = property(lambda self:getattr(self, '_foreign_keys', []), + doc=\ + """Foreign key accessor. References a list of ``ForeignKey`` objects + which each represent a foreign key placed on this column's ultimate + ancestor. + """) + + def _one_fkey(self): + if self._foreign_keys: + return list(self._foreign_keys)[0] + else: + return None + + foreign_key = property(_one_fkey) + + def _get_orig_set(self): + try: + return self.__orig_set + except AttributeError: + self.__orig_set = util.Set([self]) + return self.__orig_set + + def _set_orig_set(self, s): + if len(s) == 0: + s.add(self) + self.__orig_set = s + + orig_set = property(_get_orig_set, _set_orig_set, + doc=\ + """A Set containing TableClause-bound, non-proxied ColumnElements + for which this ColumnElement is a proxy. In all cases except + for a column proxied from a Union (i.e. CompoundSelect), this + set will be just one element. + """) + + def shares_lineage(self, othercolumn): + """Return True if the given ``ColumnElement`` has a common ancestor to this ``ColumnElement``. + """ + + for c in self.orig_set: + if c in othercolumn.orig_set: + return True + else: + return False + + def _make_proxy(self, selectable, name=None): + """Create a new ``ColumnElement`` representing this + ``ColumnElement`` as it appears in the select list of a + descending selectable. + + The default implementation returns a ``_ColumnClause`` if a + name is given, else just returns self. + """ + + if name is not None: + co = _ColumnClause(name, selectable) + co.orig_set = self.orig_set + selectable.columns[name]= co + return co + else: + return self + +class ColumnCollection(util.OrderedProperties): + """An ordered dictionary that stores a list of ColumnElement + instances. + + Overrides the ``__eq__()`` method to produce SQL clauses between + sets of correlated columns. + """ + + def __init__(self, *cols): + super(ColumnCollection, self).__init__() + [self.add(c) for c in cols] + + def __str__(self): + return repr([str(c) for c in self]) + + def add(self, column): + """Add a column to this collection. + + The key attribute of the column will be used as the hash key + for this dictionary. + """ + + # Allow an aliased column to replace an unaliased column of the + # same name. + if column.name in self: + other = self[column.name] + if other.name == other.key: + del self[other.name] + self[column.key] = column + + def remove(self, column): + del self[column.key] + + def extend(self, iter): + for c in iter: + self.add(c) + + def __eq__(self, other): + l = [] + for c in other: + for local in self: + if c.shares_lineage(local): + l.append(c==local) + return and_(*l) + + def __contains__(self, other): + if not isinstance(other, basestring): + raise exceptions.ArgumentError("__contains__ requires a string argument") + return util.OrderedProperties.__contains__(self, other) + + def contains_column(self, col): + # have to use a Set here, because it will compare the identity + # of the column, not just using "==" for comparison which will always return a + # "True" value (i.e. a BinaryClause...) + return col in util.Set(self) + +class ColumnSet(util.OrderedSet): + def contains_column(self, col): + return col in self + + def extend(self, cols): + for col in cols: + self.add(col) + + def __add__(self, other): + return list(self) + list(other) + + def __eq__(self, other): + l = [] + for c in other: + for local in self: + if c.shares_lineage(local): + l.append(c==local) + return and_(*l) + +class FromClause(Selectable): + """Represent an element that can be used within the ``FROM`` clause of a ``SELECT`` statement.""" + + __visit_name__ = 'fromclause' + + def __init__(self, name=None): + self.name = name + + def _get_from_objects(self, **modifiers): + # this could also be [self], at the moment it doesnt matter to the Select object + return [] + + def default_order_by(self): + return [self.oid_column] + + def count(self, whereclause=None, **params): + if self.primary_key: + col = list(self.primary_key)[0] + else: + col = list(self.columns)[0] + return select([func.count(col).label('tbl_row_count')], whereclause, from_obj=[self], **params) + + def join(self, right, *args, **kwargs): + return Join(self, right, *args, **kwargs) + + def outerjoin(self, right, *args, **kwargs): + return Join(self, right, isouter=True, *args, **kwargs) + + def alias(self, name=None): + return Alias(self, name) + + def named_with_column(self): + """True if the name of this FromClause may be prepended to a + column in a generated SQL statement. + """ + + return False + + def _locate_oid_column(self): + """Subclasses should override this to return an appropriate OID column.""" + + return None + + def _get_oid_column(self): + if not hasattr(self, '_oid_column'): + self._oid_column = self._locate_oid_column() + return self._oid_column + + def _get_all_embedded_columns(self): + ret = [] + class FindCols(visitors.ClauseVisitor): + def visit_column(self, col): + ret.append(col) + FindCols().traverse(self) + return ret + + def is_derived_from(self, fromclause): + """Return True if this FromClause is 'derived' from the given FromClause. + + An example would be an Alias of a Table is derived from that Table. + """ + + return False + + def replace_selectable(self, old, alias): + """replace all occurences of FromClause 'old' with the given Alias object, returning a copy of this ``FromClause``.""" + + from sqlalchemy.sql import util + return util.ClauseAdapter(alias).traverse(self, clone=True) + + def corresponding_column(self, column, raiseerr=True, keys_ok=False, require_embedded=False): + """Given a ``ColumnElement``, return the exported ``ColumnElement`` + object from this ``Selectable`` which corresponds to that + original ``Column`` via a common anscestor column. + + column + the target ``ColumnElement`` to be matched + + raiseerr + if True, raise an error if the given ``ColumnElement`` could + not be matched. if False, non-matches will return None. + + keys_ok + if the ``ColumnElement`` cannot be matched, attempt to match + based on the string "key" property of the column alone. This + makes the search much more liberal. + + require_embedded + only return corresponding columns for the given + ``ColumnElement``, if the given ``ColumnElement`` is + actually present within a sub-element of this + ``FromClause``. Normally the column will match if it merely + shares a common anscestor with one of the exported columns + of this ``FromClause``. + """ + + if self.c.contains_column(column): + return column + + if require_embedded and column not in util.Set(self._get_all_embedded_columns()): + if not raiseerr: + return None + else: + raise exceptions.InvalidRequestError("Column instance '%s' is not directly present within selectable '%s'" % (str(column), column.table)) + for c in column.orig_set: + try: + return self.original_columns[c] + except KeyError: + pass + else: + if keys_ok: + try: + return self.c[column.name] + except KeyError: + pass + if not raiseerr: + return None + else: + raise exceptions.InvalidRequestError("Given column '%s', attached to table '%s', failed to locate a corresponding column from table '%s'" % (str(column), str(getattr(column, 'table', None)), self.name)) + + def _get_exported_attribute(self, name): + try: + return getattr(self, name) + except AttributeError: + self._export_columns() + return getattr(self, name) + + def _clone_from_clause(self): + # delete all the "generated" collections of columns for a + # newly cloned FromClause, so that they will be re-derived + # from the item. this is because FromClause subclasses, when + # cloned, need to reestablish new "proxied" columns that are + # linked to the new item + for attr in ('_columns', '_primary_key' '_foreign_keys', '_orig_cols', '_oid_column'): + if hasattr(self, attr): + delattr(self, attr) + + columns = property(lambda s:s._get_exported_attribute('_columns')) + c = property(lambda s:s._get_exported_attribute('_columns')) + primary_key = property(lambda s:s._get_exported_attribute('_primary_key')) + foreign_keys = property(lambda s:s._get_exported_attribute('_foreign_keys')) + original_columns = property(lambda s:s._get_exported_attribute('_orig_cols'), doc=\ + """A dictionary mapping an original Table-bound + column to a proxied column in this FromClause. + """) + oid_column = property(_get_oid_column) + + def _export_columns(self, columns=None): + """Initialize column collections. + + The collections include the primary key, foreign keys, list of + all columns, as well as the *_orig_cols* collection which is a + dictionary used to match Table-bound columns to proxied + columns in this ``FromClause``. The columns in each + collection are *proxied* from the columns returned by the + _exportable_columns method, where a *proxied* column maintains + most or all of the properties of its original column, except + its parent ``Selectable`` is this ``FromClause``. + """ + + if hasattr(self, '_columns') and columns is None: + # TODO: put a mutex here ? this is a key place for threading probs + return + self._columns = ColumnCollection() + self._primary_key = ColumnSet() + self._foreign_keys = util.Set() + self._orig_cols = {} + + if columns is None: + columns = self._flatten_exportable_columns() + for co in columns: + cp = self._proxy_column(co) + for ci in cp.orig_set: + cx = self._orig_cols.get(ci) + # TODO: the '=' thing here relates to the order of + # columns as they are placed in the "columns" + # collection of a CompositeSelect, illustrated in + # test/sql/selectable.SelectableTest.testunion make + # this relationship less brittle + if cx is None or cp._distance <= cx._distance: + self._orig_cols[ci] = cp + if self.oid_column is not None: + for ci in self.oid_column.orig_set: + self._orig_cols[ci] = self.oid_column + + def _flatten_exportable_columns(self): + """Return the list of ColumnElements represented within this FromClause's _exportable_columns""" + export = self._exportable_columns() + for column in export: + if isinstance(column, Selectable): + for co in column.columns: + yield co + elif isinstance(column, ColumnElement): + yield column + else: + continue + + def _exportable_columns(self): + return [] + + def _proxy_column(self, column): + return column._make_proxy(self) + +class _BindParamClause(ClauseElement, _CompareMixin): + """Represent a bind parameter. + + Public constructor is the ``bindparam()`` function. + """ + + __visit_name__ = 'bindparam' + + def __init__(self, key, value, shortname=None, type_=None, unique=False, isoutparam=False): + """Construct a _BindParamClause. + + key + the key for this bind param. Will be used in the generated + SQL statement for dialects that use named parameters. This + value may be modified when part of a compilation operation, + if other ``_BindParamClause`` objects exist with the same + key, or if its length is too long and truncation is + required. + + value + Initial value for this bind param. This value may be + overridden by the dictionary of parameters sent to statement + compilation/execution. + + shortname + Defaults to the key, a *short name* that will also identify + this bind parameter, similar to an alias. the bind + parameter keys sent to a statement compilation or compiled + execution may match either the key or the shortname of the + corresponding ``_BindParamClause`` objects. + + type\_ + A ``TypeEngine`` object that will be used to pre-process the + value corresponding to this ``_BindParamClause`` at + execution time. + + unique + if True, the key name of this BindParamClause will be + modified if another ``_BindParamClause`` of the same name + already has been located within the containing + ``ClauseElement``. + + isoutparam + if True, the parameter should be treated like a stored procedure "OUT" + parameter. + """ + + self.key = key or "{ANON %d param}" % id(self) + self.value = value + self.shortname = shortname or key + self.unique = unique + self.isoutparam = isoutparam + type_ = sqltypes.to_instance(type_) + if isinstance(type_, sqltypes.NullType) and type(value) in _BindParamClause.type_map: + self.type = sqltypes.to_instance(_BindParamClause.type_map[type(value)]) + else: + self.type = type_ + + # TODO: move to types module, obviously + type_map = { + str : sqltypes.String, + unicode : sqltypes.Unicode, + int : sqltypes.Integer, + float : sqltypes.Numeric + } + + def _get_from_objects(self, **modifiers): + return [] + + def bind_processor(self, dialect): + return self.type.dialect_impl(dialect).bind_processor(dialect) + + def _compare_type(self, obj): + if not isinstance(self.type, sqltypes.NullType): + return self.type + else: + return obj.type + + def compare(self, other): + """Compare this ``_BindParamClause`` to the given clause. + + Since ``compare()`` is meant to compare statement syntax, this + method returns True if the two ``_BindParamClauses`` have just + the same type. + """ + + return isinstance(other, _BindParamClause) and other.type.__class__ == self.type.__class__ + + def __repr__(self): + return "_BindParamClause(%s, %s, type_=%s)" % (repr(self.key), repr(self.value), repr(self.type)) + +class _TypeClause(ClauseElement): + """Handle a type keyword in a SQL statement. + + Used by the ``Case`` statement. + """ + + __visit_name__ = 'typeclause' + + def __init__(self, type): + self.type = type + + def _get_from_objects(self, **modifiers): + return [] + +class _TextClause(ClauseElement): + """Represent a literal SQL text fragment. + + Public constructor is the ``text()`` function. + """ + + __visit_name__ = 'textclause' + + def __init__(self, text = "", bind=None, bindparams=None, typemap=None): + self._bind = bind + self.bindparams = {} + self.typemap = typemap + if typemap is not None: + for key in typemap.keys(): + typemap[key] = sqltypes.to_instance(typemap[key]) + + def repl(m): + self.bindparams[m.group(1)] = bindparam(m.group(1)) + return ":%s" % m.group(1) + + # scan the string and search for bind parameter names, add them + # to the list of bindparams + self.text = BIND_PARAMS.sub(repl, text) + if bindparams is not None: + for b in bindparams: + self.bindparams[b.key] = b + + def _get_type(self): + if self.typemap is not None and len(self.typemap) == 1: + return list(self.typemap)[0] + else: + return None + type = property(_get_type) + + columns = property(lambda s:[]) + + def _copy_internals(self): + self.bindparams = [b._clone() for b in self.bindparams] + + def get_children(self, **kwargs): + return self.bindparams.values() + + def _get_from_objects(self, **modifiers): + return [] + + def supports_execution(self): + return True + + def _table_iterator(self): + return iter([]) + +class _Null(ColumnElement): + """Represent the NULL keyword in a SQL statement. + + Public constructor is the ``null()`` function. + """ + + def __init__(self): + self.type = sqltypes.NULLTYPE + + def _get_from_objects(self, **modifiers): + return [] + +class ClauseList(ClauseElement): + """Describe a list of clauses, separated by an operator. + + By default, is comma-separated, such as a column listing. + """ + __visit_name__ = 'clauselist' + + def __init__(self, *clauses, **kwargs): + self.clauses = [] + self.operator = kwargs.pop('operator', operators.comma_op) + self.group = kwargs.pop('group', True) + self.group_contents = kwargs.pop('group_contents', True) + for c in clauses: + if c is None: + continue + self.append(c) + + def __iter__(self): + return iter(self.clauses) + def __len__(self): + return len(self.clauses) + + def append(self, clause): + # TODO: not sure if i like the 'group_contents' flag. need to + # define the difference between a ClauseList of ClauseLists, + # and a "flattened" ClauseList of ClauseLists. flatten() + # method ? + if self.group_contents: + self.clauses.append(_literal_as_text(clause).self_group(against=self.operator)) + else: + self.clauses.append(_literal_as_text(clause)) + + def _copy_internals(self): + self.clauses = [clause._clone() for clause in self.clauses] + + def get_children(self, **kwargs): + return self.clauses + + def _get_from_objects(self, **modifiers): + f = [] + for c in self.clauses: + f += c._get_from_objects(**modifiers) + return f + + def self_group(self, against=None): + if self.group and self.operator != against and PRECEDENCE.get(self.operator, PRECEDENCE[_smallest]) <= PRECEDENCE.get(against, PRECEDENCE[_largest]): + return _Grouping(self) + else: + return self + + def compare(self, other): + """Compare this ``ClauseList`` to the given ``ClauseList``, + including a comparison of all the clause items. + """ + + if not isinstance(other, ClauseList) and len(self.clauses) == 1: + return self.clauses[0].compare(other) + elif isinstance(other, ClauseList) and len(self.clauses) == len(other.clauses): + for i in range(0, len(self.clauses)): + if not self.clauses[i].compare(other.clauses[i]): + return False + else: + return self.operator == other.operator + else: + return False + +class _CalculatedClause(ColumnElement): + """Describe a calculated SQL expression that has a type, like ``CASE``. + + Extends ``ColumnElement`` to provide column-level comparison + operators. + """ + + __visit_name__ = 'calculatedclause' + + def __init__(self, name, *clauses, **kwargs): + self.name = name + self.type = sqltypes.to_instance(kwargs.get('type_', None)) + self._bind = kwargs.get('bind', None) + self.group = kwargs.pop('group', True) + clauses = ClauseList(operator=kwargs.get('operator', None), group_contents=kwargs.get('group_contents', True), *clauses) + if self.group: + self.clause_expr = clauses.self_group() + else: + self.clause_expr = clauses + + key = property(lambda self:self.name or "_calc_") + + def _copy_internals(self): + self.clause_expr = self.clause_expr._clone() + + def clauses(self): + if isinstance(self.clause_expr, _Grouping): + return self.clause_expr.elem + else: + return self.clause_expr + clauses = property(clauses) + + def get_children(self, **kwargs): + return self.clause_expr, + + def _get_from_objects(self, **modifiers): + return self.clauses._get_from_objects(**modifiers) + + def _bind_param(self, obj): + return _BindParamClause(self.name, obj, type_=self.type, unique=True) + + def select(self): + return select([self]) + + def scalar(self): + return select([self]).execute().scalar() + + def execute(self): + return select([self]).execute() + + def _compare_type(self, obj): + return self.type + +class _Function(_CalculatedClause, FromClause): + """Describe a SQL function. + + Extends ``_CalculatedClause``, turn the *clauselist* into function + arguments, also adds a `packagenames` argument. + """ + + def __init__(self, name, *clauses, **kwargs): + self.packagenames = kwargs.get('packagenames', None) or [] + kwargs['operator'] = operators.comma_op + _CalculatedClause.__init__(self, name, **kwargs) + for c in clauses: + self.append(c) + + key = property(lambda self:self.name) + columns = property(lambda self:[self]) + + def _copy_internals(self): + _CalculatedClause._copy_internals(self) + self._clone_from_clause() + + def get_children(self, **kwargs): + return _CalculatedClause.get_children(self, **kwargs) + + def append(self, clause): + self.clauses.append(_literal_as_binds(clause, self.name)) + + +class _Cast(ColumnElement): + + def __init__(self, clause, totype, **kwargs): + if not hasattr(clause, 'label'): + clause = literal(clause) + self.type = sqltypes.to_instance(totype) + self.clause = clause + self.typeclause = _TypeClause(self.type) + self._distance = 0 + + def _copy_internals(self): + self.clause = self.clause._clone() + self.typeclause = self.typeclause._clone() + + def get_children(self, **kwargs): + return self.clause, self.typeclause + + def _get_from_objects(self, **modifiers): + return self.clause._get_from_objects(**modifiers) + + def _make_proxy(self, selectable, name=None): + if name is not None: + co = _ColumnClause(name, selectable, type_=self.type) + co._distance = self._distance + 1 + co.orig_set = self.orig_set + selectable.columns[name]= co + return co + else: + return self + + +class _UnaryExpression(ColumnElement): + def __init__(self, element, operator=None, modifier=None, type_=None, negate=None): + self.operator = operator + self.modifier = modifier + + self.element = _literal_as_text(element).self_group(against=self.operator or self.modifier) + self.type = sqltypes.to_instance(type_) + self.negate = negate + + def _get_from_objects(self, **modifiers): + return self.element._get_from_objects(**modifiers) + + def _copy_internals(self): + self.element = self.element._clone() + + def get_children(self, **kwargs): + return self.element, + + def compare(self, other): + """Compare this ``_UnaryExpression`` against the given ``ClauseElement``.""" + + return ( + isinstance(other, _UnaryExpression) and + self.operator == other.operator and + self.modifier == other.modifier and + self.element.compare(other.element) + ) + + def _negate(self): + if self.negate is not None: + return _UnaryExpression(self.element, operator=self.negate, negate=self.operator, modifier=self.modifier, type_=self.type) + else: + return super(_UnaryExpression, self)._negate() + + def self_group(self, against): + if self.operator and PRECEDENCE.get(self.operator, PRECEDENCE[_smallest]) <= PRECEDENCE.get(against, PRECEDENCE[_largest]): + return _Grouping(self) + else: + return self + + +class _BinaryExpression(ColumnElement): + """Represent an expression that is ``LEFT <operator> RIGHT``.""" + + def __init__(self, left, right, operator, type_=None, negate=None): + self.left = _literal_as_text(left).self_group(against=operator) + self.right = _literal_as_text(right).self_group(against=operator) + self.operator = operator + self.type = sqltypes.to_instance(type_) + self.negate = negate + + def _get_from_objects(self, **modifiers): + return self.left._get_from_objects(**modifiers) + self.right._get_from_objects(**modifiers) + + def _copy_internals(self): + self.left = self.left._clone() + self.right = self.right._clone() + + def get_children(self, **kwargs): + return self.left, self.right + + def compare(self, other): + """Compare this ``_BinaryExpression`` against the given ``_BinaryExpression``.""" + + return ( + isinstance(other, _BinaryExpression) and + self.operator == other.operator and + ( + self.left.compare(other.left) and + self.right.compare(other.right) or + ( + self.operator in [operators.eq, operators.ne, + operators.add, operators.mul] and + self.left.compare(other.right) and + self.right.compare(other.left) + ) + ) + ) + + def self_group(self, against=None): + # use small/large defaults for comparison so that unknown + # operators are always parenthesized + if self.operator != against and (PRECEDENCE.get(self.operator, PRECEDENCE[_smallest]) <= PRECEDENCE.get(against, PRECEDENCE[_largest])): + return _Grouping(self) + else: + return self + + def _negate(self): + if self.negate is not None: + return _BinaryExpression(self.left, self.right, self.negate, negate=self.operator, type_=self.type) + else: + return super(_BinaryExpression, self)._negate() + +class _Exists(_UnaryExpression): + __visit_name__ = _UnaryExpression.__visit_name__ + + def __init__(self, *args, **kwargs): + kwargs['correlate'] = True + s = select(*args, **kwargs).as_scalar().self_group() + _UnaryExpression.__init__(self, s, operator=operators.exists) + + def select(self, whereclauses = None, **params): + return select([self], whereclauses, **params) + + def correlate(self, fromclause): + e = self._clone() + e.element = self.element.correlate(fromclause).self_group() + return e + + def where(self, clause): + e = self._clone() + e.element = self.element.where(clause).self_group() + return e + + def _hide_froms(self, **modifiers): + return self._get_from_objects(**modifiers) + +class Join(FromClause): + """represent a ``JOIN`` construct between two ``FromClause`` elements. + + The public constructor function for ``Join`` is the module-level + ``join()`` function, as well as the ``join()`` method available + off all ``FromClause`` subclasses. + """ + + def __init__(self, left, right, onclause=None, isouter = False): + self.left = _selectable(left) + self.right = _selectable(right).self_group() + if onclause is None: + self.onclause = self._match_primaries(self.left, self.right) + else: + self.onclause = onclause + self.isouter = isouter + self.__folded_equivalents = None + self._init_primary_key() + + name = property(lambda s: "Join object on " + s.left.name + " " + s.right.name) + encodedname = property(lambda s: s.name.encode('ascii', 'backslashreplace')) + + def _init_primary_key(self): + pkcol = util.Set([c for c in self._flatten_exportable_columns() if c.primary_key]) + + equivs = {} + def add_equiv(a, b): + for x, y in ((a, b), (b, a)): + if x in equivs: + equivs[x].add(y) + else: + equivs[x] = util.Set([y]) + + class BinaryVisitor(visitors.ClauseVisitor): + def visit_binary(self, binary): + if binary.operator == operators.eq: + add_equiv(binary.left, binary.right) + BinaryVisitor().traverse(self.onclause) + + for col in pkcol: + for fk in col.foreign_keys: + if fk.column in pkcol: + add_equiv(col, fk.column) + + omit = util.Set() + for col in pkcol: + p = col + for c in equivs.get(col, util.Set()): + if p.references(c) or (c.primary_key and not p.primary_key): + omit.add(p) + p = c + + self.__primary_key = ColumnSet([c for c in self._flatten_exportable_columns() if c.primary_key and c not in omit]) + + primary_key = property(lambda s:s.__primary_key) + + def self_group(self, against=None): + return _FromGrouping(self) + + def _locate_oid_column(self): + return self.left.oid_column + + def _exportable_columns(self): + return [c for c in self.left.columns] + [c for c in self.right.columns] + + def _proxy_column(self, column): + self._columns[column._label] = column + for f in column.foreign_keys: + self._foreign_keys.add(f) + return column + + def _copy_internals(self): + self._clone_from_clause() + self.left = self.left._clone() + self.right = self.right._clone() + self.onclause = self.onclause._clone() + self.__folded_equivalents = None + self._init_primary_key() + + def get_children(self, **kwargs): + return self.left, self.right, self.onclause + + def _match_primaries(self, primary, secondary): + crit = [] + constraints = util.Set() + for fk in secondary.foreign_keys: + if fk.references(primary): + crit.append(primary.corresponding_column(fk.column) == fk.parent) + constraints.add(fk.constraint) + self.foreignkey = fk.parent + if primary is not secondary: + for fk in primary.foreign_keys: + if fk.references(secondary): + crit.append(secondary.corresponding_column(fk.column) == fk.parent) + constraints.add(fk.constraint) + self.foreignkey = fk.parent + if len(crit) == 0: + raise exceptions.ArgumentError( + "Can't find any foreign key relationships " + "between '%s' and '%s'" % (primary.name, secondary.name)) + elif len(constraints) > 1: + raise exceptions.ArgumentError( + "Can't determine join between '%s' and '%s'; " + "tables have more than one foreign key " + "constraint relationship between them. " + "Please specify the 'onclause' of this " + "join explicitly." % (primary.name, secondary.name)) + elif len(crit) == 1: + return (crit[0]) + else: + return and_(*crit) + + def _get_folded_equivalents(self, equivs=None): + if self.__folded_equivalents is not None: + return self.__folded_equivalents + if equivs is None: + equivs = util.Set() + class LocateEquivs(visitors.NoColumnVisitor): + def visit_binary(self, binary): + if binary.operator == operators.eq and binary.left.name == binary.right.name: + equivs.add(binary.right) + equivs.add(binary.left) + LocateEquivs().traverse(self.onclause) + collist = [] + if isinstance(self.left, Join): + left = self.left._get_folded_equivalents(equivs) + else: + left = list(self.left.columns) + if isinstance(self.right, Join): + right = self.right._get_folded_equivalents(equivs) + else: + right = list(self.right.columns) + used = util.Set() + for c in left + right: + if c in equivs: + if c.name not in used: + collist.append(c) + used.add(c.name) + else: + collist.append(c) + self.__folded_equivalents = collist + return self.__folded_equivalents + + folded_equivalents = property(_get_folded_equivalents, doc="Returns the column list of this Join with all equivalently-named, " + "equated columns folded into one column, where 'equated' means they are " + "equated to each other in the ON clause of this join.") + + def select(self, whereclause = None, fold_equivalents=False, **kwargs): + """Create a ``Select`` from this ``Join``. + + whereclause + the WHERE criterion that will be sent to the ``select()`` + function + + fold_equivalents + based on the join criterion of this ``Join``, do not include + repeat column names in the column list of the resulting + select, for columns that are calculated to be "equivalent" + based on the join criterion of this ``Join``. This will + recursively apply to any joins directly nested by this one + as well. + + \**kwargs + all other kwargs are sent to the underlying ``select()`` function. + See the ``select()`` module level function for details. + """ + + if fold_equivalents: + collist = self.folded_equivalents + else: + collist = [self.left, self.right] + + return select(collist, whereclause, from_obj=[self], **kwargs) + + bind = property(lambda s:s.left.bind or s.right.bind) + + def alias(self, name=None): + """Create a ``Select`` out of this ``Join`` clause and return an ``Alias`` of it. + + The ``Select`` is not correlating. + """ + + return self.select(use_labels=True, correlate=False).alias(name) + + def _hide_froms(self, **modifiers): + return self.left._get_from_objects(**modifiers) + self.right._get_from_objects(**modifiers) + + def _get_from_objects(self, **modifiers): + return [self] + self.onclause._get_from_objects(**modifiers) + self.left._get_from_objects(**modifiers) + self.right._get_from_objects(**modifiers) + +class Alias(FromClause): + """Represents an table or selectable alias (AS). + + Represents an alias, as typically applied to any table or + sub-select within a SQL statement using the ``AS`` keyword (or + without the keyword on certain databases such as Oracle). + + This object is constructed from the ``alias()`` module level + function as well as the ``alias()`` method available on all + ``FromClause`` subclasses. + """ + + def __init__(self, selectable, alias=None): + baseselectable = selectable + while isinstance(baseselectable, Alias): + baseselectable = baseselectable.selectable + self.original = baseselectable + self.selectable = selectable + if alias is None: + if self.original.named_with_column(): + alias = getattr(self.original, 'name', None) + alias = '{ANON %d %s}' % (id(self), alias or 'anon') + self.name = alias + self.encodedname = alias.encode('ascii', 'backslashreplace') + + def is_derived_from(self, fromclause): + x = self.selectable + while True: + if x is fromclause: + return True + if isinstance(x, Alias): + x = x.selectable + else: + break + return False + + def supports_execution(self): + return self.original.supports_execution() + + def _table_iterator(self): + return self.original._table_iterator() + + def _locate_oid_column(self): + if self.selectable.oid_column is not None: + return self.selectable.oid_column._make_proxy(self) + else: + return None + + def named_with_column(self): + return True + + def _exportable_columns(self): + #return self.selectable._exportable_columns() + return self.selectable.columns + + def _copy_internals(self): + self._clone_from_clause() + self.selectable = self.selectable._clone() + baseselectable = self.selectable + while isinstance(baseselectable, Alias): + baseselectable = baseselectable.selectable + self.original = baseselectable + + def get_children(self, **kwargs): + for c in self.c: + yield c + yield self.selectable + + def _get_from_objects(self): + return [self] + + bind = property(lambda s: s.selectable.bind) + +class _ColumnElementAdapter(ColumnElement): + """Adapts a ClauseElement which may or may not be a + ColumnElement subclass itself into an object which + acts like a ColumnElement. + """ + + def __init__(self, elem): + self.elem = elem + self.type = getattr(elem, 'type', None) + self.orig_set = getattr(elem, 'orig_set', util.Set()) + + key = property(lambda s: s.elem.key) + _label = property(lambda s: s.elem._label) + + def _copy_internals(self): + self.elem = self.elem._clone() + + def get_children(self, **kwargs): + return self.elem, + + def _hide_froms(self, **modifiers): + return self.elem._hide_froms(**modifiers) + + def _get_from_objects(self, **modifiers): + return self.elem._get_from_objects(**modifiers) + + def __getattr__(self, attr): + return getattr(self.elem, attr) + +class _Grouping(_ColumnElementAdapter): + """Represent a grouping within a column expression""" + pass + +class _FromGrouping(FromClause): + """Represent a grouping of a FROM clause""" + __visit_name__ = 'grouping' + + def __init__(self, elem): + self.elem = elem + + columns = c = property(lambda s:s.elem.columns) + + def get_children(self, **kwargs): + return self.elem, + + def _hide_froms(self, **modifiers): + return self.elem._hide_froms(**modifiers) + + def _copy_internals(self): + self.elem = self.elem._clone() + + def _get_from_objects(self, **modifiers): + return self.elem._get_from_objects(**modifiers) + + def __getattr__(self, attr): + return getattr(self.elem, attr) + +class _Label(ColumnElement): + """Represents a column label (AS). + + Represent a label, as typically applied to any column-level + element using the ``AS`` sql keyword. + + This object is constructed from the ``label()`` module level + function as well as the ``label()`` method available on all + ``ColumnElement`` subclasses. + """ + + def __init__(self, name, obj, type_=None): + while isinstance(obj, _Label): + obj = obj.obj + self.name = name or "{ANON %d %s}" % (id(self), getattr(obj, 'name', 'anon')) + + self.obj = obj.self_group(against=operators.as_) + self.type = sqltypes.to_instance(type_ or getattr(obj, 'type', None)) + + key = property(lambda s: s.name) + _label = property(lambda s: s.name) + orig_set = property(lambda s:s.obj.orig_set) + + def expression_element(self): + return self.obj + + def _copy_internals(self): + self.obj = self.obj._clone() + + def get_children(self, **kwargs): + return self.obj, + + def _get_from_objects(self, **modifiers): + return self.obj._get_from_objects(**modifiers) + + def _hide_froms(self, **modifiers): + return self.obj._hide_froms(**modifiers) + + def _make_proxy(self, selectable, name = None): + if isinstance(self.obj, (Selectable, ColumnElement)): + return self.obj._make_proxy(selectable, name=self.name) + else: + return column(self.name)._make_proxy(selectable=selectable) + +class _ColumnClause(ColumnElement): + """Represents a generic column expression from any textual string. + + This includes columns associated with tables, aliases and select + statements, but also any arbitrary text. May or may not be bound + to an underlying ``Selectable``. ``_ColumnClause`` is usually + created publically via the ``column()`` function or the + ``literal_column()`` function. + + text + the text of the element. + + selectable + parent selectable. + + type + ``TypeEngine`` object which can associate this ``_ColumnClause`` + with a type. + + is_literal + if True, the ``_ColumnClause`` is assumed to be an exact + expression that will be delivered to the output with no quoting + rules applied regardless of case sensitive settings. the + ``literal_column()`` function is usually used to create such a + ``_ColumnClause``. + """ + + def __init__(self, text, selectable=None, type_=None, _is_oid=False, is_literal=False): + self.key = self.name = text + self.encodedname = isinstance(self.name, unicode) and self.name.encode('ascii', 'backslashreplace') or self.name + self.table = selectable + self.type = sqltypes.to_instance(type_) + self._is_oid = _is_oid + self._distance = 0 + self.__label = None + self.is_literal = is_literal + + def _clone(self): + # ColumnClause is immutable + return self + + def _get_label(self): + """Generate a 'label' for this column. + + The label is a product of the parent table name and column + name, and is treated as a unique identifier of this ``Column`` + across all ``Tables`` and derived selectables for a particular + metadata collection. + """ + + # for a "literal" column, we've no idea what the text is + # therefore no 'label' can be automatically generated + if self.is_literal: + return None + if self.__label is None: + if self.table is not None and self.table.named_with_column(): + self.__label = self.table.name + "_" + self.name + counter = 1 + while self.__label in self.table.c: + self.__label = self.__label + "_%d" % counter + counter += 1 + else: + self.__label = self.name + return self.__label + + is_labeled = property(lambda self:self.name != list(self.orig_set)[0].name) + + _label = property(_get_label) + + def label(self, name): + # if going off the "__label" property and its None, we have + # no label; return self + if name is None: + return self + else: + return super(_ColumnClause, self).label(name) + + def _get_from_objects(self, **modifiers): + if self.table is not None: + return [self.table] + else: + return [] + + def _bind_param(self, obj): + return _BindParamClause(self._label, obj, shortname=self.name, type_=self.type, unique=True) + + def _make_proxy(self, selectable, name = None): + # propigate the "is_literal" flag only if we are keeping our name, + # otherwise its considered to be a label + is_literal = self.is_literal and (name is None or name == self.name) + c = _ColumnClause(name or self.name, selectable=selectable, _is_oid=self._is_oid, type_=self.type, is_literal=is_literal) + c.orig_set = self.orig_set + c._distance = self._distance + 1 + if not self._is_oid: + selectable.columns[c.name] = c + return c + + def _compare_type(self, obj): + return self.type + +class TableClause(FromClause): + """Represents a "table" construct. + + Note that this represents tables only as another syntactical + construct within SQL expressions; it does not provide schema-level + functionality. + """ + + def __init__(self, name, *columns): + super(TableClause, self).__init__(name) + self.name = self.fullname = name + self.encodedname = self.name.encode('ascii', 'backslashreplace') + self._oid_column = _ColumnClause('oid', self, _is_oid=True) + self._export_columns(columns) + + def _clone(self): + # TableClause is immutable + return self + + def named_with_column(self): + return True + + def append_column(self, c): + self._columns[c.name] = c + c.table = self + + def _locate_oid_column(self): + return self._oid_column + + def _proxy_column(self, c): + self.append_column(c) + return c + + def _orig_columns(self): + try: + return self._orig_cols + except AttributeError: + self._orig_cols= {} + for c in self.columns: + for ci in c.orig_set: + self._orig_cols[ci] = c + return self._orig_cols + + original_columns = property(_orig_columns) + + def get_children(self, column_collections=True, **kwargs): + if column_collections: + return [c for c in self.c] + else: + return [] + + def _exportable_columns(self): + raise NotImplementedError() + + def count(self, whereclause=None, **params): + if self.primary_key: + col = list(self.primary_key)[0] + else: + col = list(self.columns)[0] + return select([func.count(col).label('tbl_row_count')], whereclause, from_obj=[self], **params) + + def join(self, right, *args, **kwargs): + return Join(self, right, *args, **kwargs) + + def outerjoin(self, right, *args, **kwargs): + return Join(self, right, isouter = True, *args, **kwargs) + + def alias(self, name=None): + return Alias(self, name) + + def select(self, whereclause = None, **params): + return select([self], whereclause, **params) + + def insert(self, values = None): + return insert(self, values=values) + + def update(self, whereclause = None, values = None): + return update(self, whereclause, values) + + def delete(self, whereclause = None): + return delete(self, whereclause) + + def _get_from_objects(self, **modifiers): + return [self] + + +class _SelectBaseMixin(object): + """Base class for ``Select`` and ``CompoundSelects``.""" + + def __init__(self, use_labels=False, for_update=False, limit=None, offset=None, order_by=None, group_by=None, bind=None): + self.use_labels = use_labels + self.for_update = for_update + self._limit = limit + self._offset = offset + self._bind = bind + + self.append_order_by(*util.to_list(order_by, [])) + self.append_group_by(*util.to_list(group_by, [])) + + def as_scalar(self): + return _ScalarSelect(self) + + def apply_labels(self): + s = self._generate() + s.use_labels = True + return s + + def label(self, name): + return self.as_scalar().label(name) + + def supports_execution(self): + return True + + def _generate(self): + s = self._clone() + s._clone_from_clause() + return s + + def limit(self, limit): + s = self._generate() + s._limit = limit + return s + + def offset(self, offset): + s = self._generate() + s._offset = offset + return s + + def order_by(self, *clauses): + s = self._generate() + s.append_order_by(*clauses) + return s + + def group_by(self, *clauses): + s = self._generate() + s.append_group_by(*clauses) + return s + + def append_order_by(self, *clauses): + if clauses == [None]: + self._order_by_clause = ClauseList() + else: + if getattr(self, '_order_by_clause', None): + clauses = list(self._order_by_clause) + list(clauses) + self._order_by_clause = ClauseList(*clauses) + + def append_group_by(self, *clauses): + if clauses == [None]: + self._group_by_clause = ClauseList() + else: + if getattr(self, '_group_by_clause', None): + clauses = list(self._group_by_clause) + list(clauses) + self._group_by_clause = ClauseList(*clauses) + + def select(self, whereclauses = None, **params): + return select([self], whereclauses, **params) + + def _get_from_objects(self, is_where=False, **modifiers): + if is_where: + return [] + else: + return [self] + +class _ScalarSelect(_Grouping): + __visit_name__ = 'grouping' + + def __init__(self, elem): + super(_ScalarSelect, self).__init__(elem) + self.type = list(elem.inner_columns)[0].type + + def _no_cols(self): + raise exceptions.InvalidRequestError("Scalar Select expression has no columns; use this object directly within a column-level expression.") + c = property(_no_cols) + columns = c + + def self_group(self, **kwargs): + return self + + def _make_proxy(self, selectable, name): + return list(self.inner_columns)[0]._make_proxy(selectable, name) + + def _get_from_objects(self, **modifiers): + return [] + +class CompoundSelect(_SelectBaseMixin, FromClause): + def __init__(self, keyword, *selects, **kwargs): + self._should_correlate = kwargs.pop('correlate', False) + self.keyword = keyword + self.selects = [] + + # some DBs do not like ORDER BY in the inner queries of a UNION, etc. + for n, s in enumerate(selects): + if s._order_by_clause: + s = s.order_by(None) + # unions group from left to right, so don't group first select + if n: + self.selects.append(s.self_group(self)) + else: + self.selects.append(s) + + self._col_map = {} + + _SelectBaseMixin.__init__(self, **kwargs) + + name = property(lambda s:s.keyword + " statement") + + def self_group(self, against=None): + return _FromGrouping(self) + + def _locate_oid_column(self): + return self.selects[0].oid_column + + def _exportable_columns(self): + for s in self.selects: + for c in s.c: + yield c + + def _proxy_column(self, column): + if self.use_labels: + col = column._make_proxy(self, name=column._label) + else: + col = column._make_proxy(self) + try: + colset = self._col_map[col.name] + except KeyError: + colset = util.Set() + self._col_map[col.name] = colset + [colset.add(c) for c in col.orig_set] + col.orig_set = colset + return col + + def _copy_internals(self): + self._clone_from_clause() + self._col_map = {} + self.selects = [s._clone() for s in self.selects] + for attr in ('_order_by_clause', '_group_by_clause'): + if getattr(self, attr) is not None: + setattr(self, attr, getattr(self, attr)._clone()) + + def get_children(self, column_collections=True, **kwargs): + return (column_collections and list(self.c) or []) + \ + [self._order_by_clause, self._group_by_clause] + list(self.selects) + + def _table_iterator(self): + for s in self.selects: + for t in s._table_iterator(): + yield t + + def _find_engine(self): + for s in self.selects: + e = s._find_engine() + if e: + return e + else: + return None + +class Select(_SelectBaseMixin, FromClause): + """Represents a ``SELECT`` statement. + + Select statements support appendable clauses, as well as the + ability to execute themselves and return a result set. + """ + + def __init__(self, columns, whereclause=None, from_obj=None, distinct=False, having=None, correlate=True, prefixes=None, **kwargs): + """Construct a Select object. + + The public constructor for Select is the + [sqlalchemy.sql#select()] function; see that function for + argument descriptions. + """ + + self._should_correlate = correlate + self._distinct = distinct + + self._raw_columns = [] + self.__correlate = util.Set() + self._froms = util.OrderedSet() + self._whereclause = None + self._having = None + self._prefixes = [] + + if columns is not None: + for c in columns: + self.append_column(c) + + if from_obj is not None: + for f in from_obj: + self.append_from(f) + + if whereclause is not None: + self.append_whereclause(whereclause) + + if having is not None: + self.append_having(having) + + if prefixes is not None: + for p in prefixes: + self.append_prefix(p) + + _SelectBaseMixin.__init__(self, **kwargs) + + def _get_display_froms(self, existing_froms=None): + """Return the full list of 'from' clauses to be displayed. + + Takes into account a set of existing froms which may be + rendered in the FROM clause of enclosing selects; this Select + may want to leave those absent if it is automatically + correlating. + """ + + froms = util.OrderedSet() + hide_froms = util.Set() + + for col in self._raw_columns: + for f in col._hide_froms(): + hide_froms.add(f) + for f in col._get_from_objects(): + froms.add(f) + + if self._whereclause is not None: + for f in self._whereclause._get_from_objects(is_where=True): + froms.add(f) + + for elem in self._froms: + froms.add(elem) + for f in elem._get_from_objects(): + froms.add(f) + + for elem in froms: + for f in elem._hide_froms(): + hide_froms.add(f) + + froms = froms.difference(hide_froms) + + if len(froms) > 1: + corr = self.__correlate + if self._should_correlate and existing_froms is not None: + corr = existing_froms.union(corr) + f = froms.difference(corr) + if len(f) == 0: + raise exceptions.InvalidRequestError("Select statement '%s' is overcorrelated; returned no 'from' clauses" % str(self.__dont_correlate())) + return f + else: + return froms + + froms = property(_get_display_froms, doc="""Return a list of all FromClause elements which will be applied to the FROM clause of the resulting statement.""") + + name = property(lambda self:"Select statement") + + def locate_all_froms(self): + froms = util.Set() + for col in self._raw_columns: + for f in col._get_from_objects(): + froms.add(f) + + if self._whereclause is not None: + for f in self._whereclause._get_from_objects(is_where=True): + froms.add(f) + + for elem in self._froms: + froms.add(elem) + for f in elem._get_from_objects(): + froms.add(f) + return froms + + def _get_inner_columns(self): + for c in self._raw_columns: + if isinstance(c, Selectable): + for co in c.columns: + yield co + else: + yield c + + inner_columns = property(_get_inner_columns) + + def _copy_internals(self): + self._clone_from_clause() + self._raw_columns = [c._clone() for c in self._raw_columns] + self._recorrelate_froms([(f, f._clone()) for f in self._froms]) + for attr in ('_whereclause', '_having', '_order_by_clause', '_group_by_clause'): + if getattr(self, attr) is not None: + setattr(self, attr, getattr(self, attr)._clone()) + + def get_children(self, column_collections=True, **kwargs): + return (column_collections and list(self.columns) or []) + \ + list(self.locate_all_froms()) + \ + [x for x in (self._whereclause, self._having, self._order_by_clause, self._group_by_clause) if x is not None] + + def _recorrelate_froms(self, froms): + newcorrelate = util.Set() + newfroms = util.Set() + oldfroms = util.Set(self._froms) + for old, new in froms: + if old in self.__correlate: + newcorrelate.add(new) + self.__correlate.remove(old) + if old in oldfroms: + newfroms.add(new) + oldfroms.remove(old) + self.__correlate = self.__correlate.union(newcorrelate) + self._froms = [f for f in oldfroms.union(newfroms)] + + def column(self, column): + s = self._generate() + s.append_column(column) + return s + + def where(self, whereclause): + s = self._generate() + s.append_whereclause(whereclause) + return s + + def having(self, having): + s = self._generate() + s.append_having(having) + return s + + def distinct(self): + s = self._generate() + s._distinct = True + return s + + def prefix_with(self, clause): + s = self._generate() + s.append_prefix(clause) + return s + + def select_from(self, fromclause): + s = self._generate() + s.append_from(fromclause) + return s + + def __dont_correlate(self): + s = self._generate() + s._should_correlate = False + return s + + def correlate(self, fromclause): + s = self._generate() + s._should_correlate=False + if fromclause is None: + s.__correlate = util.Set() + else: + s.append_correlation(fromclause) + return s + + def append_correlation(self, fromclause): + self.__correlate.add(fromclause) + + def append_column(self, column): + column = _literal_as_column(column) + + if isinstance(column, _ScalarSelect): + column = column.self_group(against=operators.comma_op) + + self._raw_columns.append(column) + + def append_prefix(self, clause): + clause = _literal_as_text(clause) + self._prefixes.append(clause) + + def append_whereclause(self, whereclause): + if self._whereclause is not None: + self._whereclause = and_(self._whereclause, _literal_as_text(whereclause)) + else: + self._whereclause = _literal_as_text(whereclause) + + def append_having(self, having): + if self._having is not None: + self._having = and_(self._having, _literal_as_text(having)) + else: + self._having = _literal_as_text(having) + + def append_from(self, fromclause): + if _is_literal(fromclause): + fromclause = FromClause(fromclause) + self._froms.add(fromclause) + + def _exportable_columns(self): + return [c for c in self._raw_columns if isinstance(c, (Selectable, ColumnElement))] + + def _proxy_column(self, column): + if self.use_labels: + return column._make_proxy(self, name=column._label) + else: + return column._make_proxy(self) + + def self_group(self, against=None): + if isinstance(against, CompoundSelect): + return self + return _FromGrouping(self) + + def _locate_oid_column(self): + for f in self.locate_all_froms(): + 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. + continue + oid = f.oid_column + if oid is not None: + return oid + else: + return None + + def union(self, other, **kwargs): + return union(self, other, **kwargs) + + def union_all(self, other, **kwargs): + return union_all(self, other, **kwargs) + + def except_(self, other, **kwargs): + return except_(self, other, **kwargs) + + def except_all(self, other, **kwargs): + return except_all(self, other, **kwargs) + + def intersect(self, other, **kwargs): + return intersect(self, other, **kwargs) + + def intersect_all(self, other, **kwargs): + return intersect_all(self, other, **kwargs) + + def _table_iterator(self): + for t in visitors.NoColumnVisitor().iterate(self): + if isinstance(t, TableClause): + yield t + + def _find_engine(self): + """Try to return a Engine, either explicitly set in this + object, or searched within the from clauses for one. + """ + + if self._bind is not None: + return self._bind + for f in self._froms: + if f is self: + continue + e = f.bind + if e is not None: + self._bind = e + return e + # look through the columns (largely synomous with looking + # through the FROMs except in the case of _CalculatedClause/_Function) + for c in self._exportable_columns(): + if getattr(c, 'table', None) is self: + continue + e = c.bind + if e is not None: + self._bind = e + return e + return None + +class _UpdateBase(ClauseElement): + """Form the base for ``INSERT``, ``UPDATE``, and ``DELETE`` statements.""" + + def supports_execution(self): + return True + + def _table_iterator(self): + return iter([self.table]) + + def _process_colparams(self, parameters): + """Receive the *values* of an ``INSERT`` or ``UPDATE`` statement and construct appropriate bind parameters.""" + + if parameters is None: + return None + + if isinstance(parameters, (list, tuple)): + pp = {} + i = 0 + for c in self.table.c: + pp[c.key] = parameters[i] + i +=1 + parameters = pp + + for key in parameters.keys(): + value = parameters[key] + if isinstance(value, ClauseElement): + parameters[key] = value.self_group() + elif _is_literal(value): + if _is_literal(key): + col = self.table.c[key] + else: + col = key + try: + parameters[key] = bindparam(col, value, unique=True) + except KeyError: + del parameters[key] + return parameters + + def _find_engine(self): + return self.table.bind + +class Insert(_UpdateBase): + def __init__(self, table, values=None): + self.table = table + self.select = None + self.parameters = self._process_colparams(values) + + def get_children(self, **kwargs): + if self.select is not None: + return self.select, + else: + return () + + def _copy_internals(self): + self.parameters = self.parameters.copy() + + def values(self, v): + if len(v) == 0: + return self + u = self._clone() + if u.parameters is None: + u.parameters = u._process_colparams(v) + else: + u.parameters = self.parameters.copy() + u.parameters.update(u._process_colparams(v)) + return u + +class Update(_UpdateBase): + def __init__(self, table, whereclause, values=None): + self.table = table + self._whereclause = whereclause + self.parameters = self._process_colparams(values) + + def get_children(self, **kwargs): + if self._whereclause is not None: + return self._whereclause, + else: + return () + + def _copy_internals(self): + self._whereclause = self._whereclause._clone() + self.parameters = self.parameters.copy() + + def values(self, v): + if len(v) == 0: + return self + u = self._clone() + if u.parameters is None: + u.parameters = u._process_colparams(v) + else: + u.parameters = self.parameters.copy() + u.parameters.update(u._process_colparams(v)) + return u + +class Delete(_UpdateBase): + def __init__(self, table, whereclause): + self.table = table + self._whereclause = whereclause + + def get_children(self, **kwargs): + if self._whereclause is not None: + return self._whereclause, + else: + return () + + def _copy_internals(self): + self._whereclause = self._whereclause._clone() + +class _IdentifiedClause(ClauseElement): + def __init__(self, ident): + self.ident = ident + def supports_execution(self): + return True + +class SavepointClause(_IdentifiedClause): + pass + +class RollbackToSavepointClause(_IdentifiedClause): + pass + +class ReleaseSavepointClause(_IdentifiedClause): + pass |