diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-11-14 18:25:13 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-11-14 18:25:13 -0500 |
| commit | 6fb06409c622e0355e0a36817940035c33e17ce3 (patch) | |
| tree | 872218fb4c1d65b1f7476497c540bf504999d200 /lib/sqlalchemy/sql | |
| parent | 90c8d8e0c9e2d0a9eeace7fa326df26a5f28465a (diff) | |
| parent | 06bf218ed37ca780bc4de2ceb47769c84de70ba1 (diff) | |
| download | sqlalchemy-6fb06409c622e0355e0a36817940035c33e17ce3.tar.gz | |
merge tip
Diffstat (limited to 'lib/sqlalchemy/sql')
| -rw-r--r-- | lib/sqlalchemy/sql/__init__.py | 1 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/compiler.py | 15 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/expression.py | 118 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/util.py | 19 |
4 files changed, 107 insertions, 46 deletions
diff --git a/lib/sqlalchemy/sql/__init__.py b/lib/sqlalchemy/sql/__init__.py index aa18eac17..2bb5f6ab4 100644 --- a/lib/sqlalchemy/sql/__init__.py +++ b/lib/sqlalchemy/sql/__init__.py @@ -47,6 +47,7 @@ from sqlalchemy.sql.expression import ( table, text, tuple_, + type_coerce, union, union_all, update, diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 154ede1bf..6f0a45df9 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -153,6 +153,10 @@ class _CompileLabel(visitors.Visitable): def __init__(self, col, name): self.element = col self.name = name + + @property + def type(self): + return self.element.type @property def quote(self): @@ -317,7 +321,7 @@ class SQLCompiler(engine.Compiled): if result_map is not None: result_map[labelname.lower()] = \ - (label.name, (label, label.element, labelname), label.element.type) + (label.name, (label, label.element, labelname), label.type) return self.process(label.element, within_columns_clause=True, @@ -329,7 +333,7 @@ class SQLCompiler(engine.Compiled): return self.process(label.element, within_columns_clause=False, **kw) - + def visit_column(self, column, result_map=None, **kwargs): name = column.name if name is None: @@ -1302,7 +1306,7 @@ class DDLCompiler(engine.Compiled): text += "FOREIGN KEY(%s) REFERENCES %s (%s)" % ( ', '.join(preparer.quote(f.parent.name, f.parent.quote) for f in constraint._elements.values()), - preparer.format_table(remote_table), + self.define_constraint_remote_table(constraint, remote_table, preparer), ', '.join(preparer.quote(f.column.name, f.column.quote) for f in constraint._elements.values()) ) @@ -1310,6 +1314,11 @@ class DDLCompiler(engine.Compiled): text += self.define_constraint_deferrability(constraint) return text + def define_constraint_remote_table(self, constraint, table, preparer): + """Format the remote table clause of a CREATE CONSTRAINT clause.""" + + return preparer.format_table(table) + def visit_unique_constraint(self, constraint): text = "" if constraint.name is not None: diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 5df3b8794..c3dc339a5 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -29,13 +29,15 @@ to stay the same in future releases. import itertools, re from operator import attrgetter -from sqlalchemy import util, exc #, types as sqltypes +from sqlalchemy import util, exc from sqlalchemy.sql import operators from sqlalchemy.sql.visitors import Visitable, cloned_traverse import operator -functions, sql_util, sqltypes = None, None, None -DefaultDialect = None +functions = util.importlater("sqlalchemy.sql", "functions") +sqlutil = util.importlater("sqlalchemy.sql", "util") +sqltypes = util.importlater("sqlalchemy", "types") +default = util.importlater("sqlalchemy.engine", "default") __all__ = [ 'Alias', 'ClauseElement', 'ColumnCollection', 'ColumnElement', @@ -45,8 +47,8 @@ __all__ = [ 'except_', 'except_all', 'exists', 'extract', 'func', 'modifier', 'collate', 'insert', 'intersect', 'intersect_all', 'join', 'label', 'literal', 'literal_column', 'not_', 'null', 'or_', 'outparam', - 'outerjoin', 'select', 'subquery', 'table', 'text', 'tuple_', 'union', - 'union_all', 'update', ] + 'outerjoin', 'select', 'subquery', 'table', 'text', 'tuple_', 'type_coerce', + 'union', 'union_all', 'update', ] PARSE_AUTOCOMMIT = util._symbol('PARSE_AUTOCOMMIT') @@ -666,6 +668,54 @@ def tuple_(*expr): """ return _Tuple(*expr) + +def type_coerce(expr, type_): + """Coerce the given expression into the given type, on the Python side only. + + :func:`.type_coerce` is roughly similar to :func:.`cast`, except no + "CAST" expression is rendered - the given type is only applied towards + expression typing and against received result values. + + e.g.:: + + from sqlalchemy.types import TypeDecorator + import uuid + + class AsGuid(TypeDecorator): + impl = String + + def process_bind_param(self, value, dialect): + if value is not None: + return str(value) + else: + return None + + def process_result_value(self, value, dialect): + if value is not None: + return uuid.UUID(value) + else: + return None + + conn.execute( + select([type_coerce(mytable.c.ident, AsGuid)]).\\ + where( + type_coerce(mytable.c.ident, AsGuid) == + uuid.uuid3(uuid.NAMESPACE_URL, 'bar') + ) + ) + + """ + if hasattr(expr, '__clause_expr__'): + return type_coerce(expr.__clause_expr__()) + + elif not isinstance(expr, Visitable): + if expr is None: + return null() + else: + return literal(expr, type_=type_) + else: + return _Label(None, expr, type_=type_) + def label(name, obj): """Return a :class:`_Label` object for the @@ -909,9 +959,6 @@ class _FunctionGenerator(object): o = self.opts.copy() o.update(kwargs) if len(self.__names) == 1: - global functions - if functions is None: - from sqlalchemy.sql import functions func = getattr(functions, self.__names[-1].lower(), None) if func is not None and \ isinstance(func, type) and \ @@ -1157,10 +1204,7 @@ class ClauseElement(Visitable): dictionary. """ - global sql_util - if sql_util is None: - from sqlalchemy.sql import util as sql_util - return sql_util.Annotated(self, values) + return sqlutil.Annotated(self, values) def _deannotate(self): """return a copy of this ClauseElement with an empty annotations @@ -1341,10 +1385,7 @@ class ClauseElement(Visitable): dialect = self.bind.dialect bind = self.bind else: - global DefaultDialect - if DefaultDialect is None: - from sqlalchemy.engine.default import DefaultDialect - dialect = DefaultDialect() + dialect = default.DefaultDialect() compiler = self._compiler(dialect, bind=bind, **kw) compiler.compile() return compiler @@ -2106,10 +2147,7 @@ class FromClause(Selectable): """ - global sql_util - if sql_util is None: - from sqlalchemy.sql import util as sql_util - return sql_util.ClauseAdapter(alias).traverse(self) + return sqlutil.ClauseAdapter(alias).traverse(self) def correspond_on_equivalents(self, column, equivalents): """Return corresponding_column for the given column, or if None @@ -2201,7 +2239,7 @@ class FromClause(Selectable): def _reset_exported(self): """delete memoized collections when a FromClause is cloned.""" - for attr in '_columns', '_primary_key_foreign_keys', \ + for attr in '_columns', '_primary_key', '_foreign_keys', \ 'locate_all_froms': self.__dict__.pop(attr, None) @@ -3050,10 +3088,7 @@ class Join(FromClause): columns = [c for c in self.left.columns] + \ [c for c in self.right.columns] - global sql_util - if not sql_util: - from sqlalchemy.sql import util as sql_util - self._primary_key.extend(sql_util.reduce_columns( + self._primary_key.extend(sqlutil.reduce_columns( (c for c in columns if c.primary_key), self.onclause)) self._columns.update((col._label, col) for col in columns) self._foreign_keys.update(itertools.chain( @@ -3070,14 +3105,11 @@ class Join(FromClause): return self.left, self.right, self.onclause def _match_primaries(self, left, right): - global sql_util - if not sql_util: - from sqlalchemy.sql import util as sql_util if isinstance(left, Join): left_right = left.right else: left_right = None - return sql_util.join_condition(left, right, a_subset=left_right) + return sqlutil.join_condition(left, right, a_subset=left_right) def select(self, whereclause=None, fold_equivalents=False, **kwargs): """Create a :class:`Select` from this :class:`Join`. @@ -3097,11 +3129,8 @@ class Join(FromClause): underlying :func:`select()` function. """ - global sql_util - if not sql_util: - from sqlalchemy.sql import util as sql_util if fold_equivalents: - collist = sql_util.folded_equivalents(self) + collist = sqlutil.folded_equivalents(self) else: collist = [self.left, self.right] @@ -3924,16 +3953,21 @@ class Select(_SelectBaseMixin, FromClause): return self._get_display_froms() @_generative - def with_hint(self, selectable, text, dialect_name=None): + def with_hint(self, selectable, text, dialect_name='*'): """Add an indexing hint for the given selectable to this :class:`Select`. - The text of the hint is written specific to a specific backend, and - typically uses Python string substitution syntax to render the name - of the table or alias, such as for Oracle:: + The text of the hint is rendered in the appropriate + location for the database backend in use, relative + to the given :class:`.Table` or :class:`.Alias` passed as the + *selectable* argument. The dialect implementation + typically uses Python string substitution syntax + with the token ``%(name)s`` to render the name of + the table or alias. E.g. when using Oracle, the + following:: - select([mytable]).with_hint(mytable, "+ index(%(name)s - ix_mytable)") + select([mytable]).\\ + with_hint(mytable, "+ index(%(name)s ix_mytable)") Would render SQL as:: @@ -3943,13 +3977,11 @@ class Select(_SelectBaseMixin, FromClause): hint to a particular backend. Such as, to add hints for both Oracle and Sybase simultaneously:: - select([mytable]).\ - with_hint(mytable, "+ index(%(name)s ix_mytable)", 'oracle').\ + select([mytable]).\\ + with_hint(mytable, "+ index(%(name)s ix_mytable)", 'oracle').\\ with_hint(mytable, "WITH INDEX ix_mytable", 'sybase') """ - if not dialect_name: - dialect_name = '*' self._hints = self._hints.union({(selectable, dialect_name):text}) @property diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index bd4f70247..638549e12 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -92,6 +92,25 @@ def find_columns(clause): visitors.traverse(clause, {}, {'column':cols.add}) return cols +def clause_is_present(clause, search): + """Given a target clause and a second to search within, return True + if the target is plainly present in the search without any + subqueries or aliases involved. + + Basically descends through Joins. + + """ + + stack = [search] + while stack: + elem = stack.pop() + if clause is elem: + return True + elif isinstance(elem, expression.Join): + stack.extend((elem.left, elem.right)) + return False + + def bind_values(clause): """Return an ordered list of "bound" values in the given clause. |
