summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-11-14 18:25:13 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2010-11-14 18:25:13 -0500
commit6fb06409c622e0355e0a36817940035c33e17ce3 (patch)
tree872218fb4c1d65b1f7476497c540bf504999d200 /lib/sqlalchemy/sql
parent90c8d8e0c9e2d0a9eeace7fa326df26a5f28465a (diff)
parent06bf218ed37ca780bc4de2ceb47769c84de70ba1 (diff)
downloadsqlalchemy-6fb06409c622e0355e0a36817940035c33e17ce3.tar.gz
merge tip
Diffstat (limited to 'lib/sqlalchemy/sql')
-rw-r--r--lib/sqlalchemy/sql/__init__.py1
-rw-r--r--lib/sqlalchemy/sql/compiler.py15
-rw-r--r--lib/sqlalchemy/sql/expression.py118
-rw-r--r--lib/sqlalchemy/sql/util.py19
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.