summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql/expression.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2008-04-07 01:12:44 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2008-04-07 01:12:44 +0000
commite3b2305d6721a1f1ed20f9c520765f7c33876f32 (patch)
tree8fa4a5565f42dc836a22c44219762dee4b933fd7 /lib/sqlalchemy/sql/expression.py
parent5b3cddc48e5b436a0c46f0df3b016a837d823c92 (diff)
downloadsqlalchemy-e3b2305d6721a1f1ed20f9c520765f7c33876f32.tar.gz
- merged -r4458:4466 of query_columns branch
- this branch changes query.values() to immediately return an iterator, adds a new "aliased" construct which will be the primary method to get at aliased columns when using values() - tentative ORM versions of _join and _outerjoin are not yet public, would like to integrate with Query better (work continues in the branch) - lots of fixes to expressions regarding cloning and correlation. Some apparent ORM bug-workarounds removed. - to fix a recursion issue with anonymous identifiers, bind parameters generated against columns now just use the name of the column instead of the tablename_columnname label (plus the unique integer counter). this way expensive recursive schemes aren't needed for the anon identifier logic. This, as usual, impacted a ton of compiler unit tests which needed a search-n-replace for the new bind names.
Diffstat (limited to 'lib/sqlalchemy/sql/expression.py')
-rw-r--r--lib/sqlalchemy/sql/expression.py359
1 files changed, 132 insertions, 227 deletions
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py
index b45fa4035..30f22e31f 100644
--- a/lib/sqlalchemy/sql/expression.py
+++ b/lib/sqlalchemy/sql/expression.py
@@ -65,7 +65,7 @@ def asc(column):
"""
return _UnaryExpression(column, modifier=operators.asc_op)
-def outerjoin(left, right, onclause=None, **kwargs):
+def outerjoin(left, right, onclause=None):
"""Return an ``OUTER JOIN`` clause element.
The returned object is an instance of [sqlalchemy.sql.expression#Join].
@@ -88,9 +88,9 @@ def outerjoin(left, right, onclause=None, **kwargs):
methods on the resulting ``Join`` object.
"""
- return Join(left, right, onclause, isouter = True, **kwargs)
+ return Join(left, right, onclause, isouter=True)
-def join(left, right, onclause=None, **kwargs):
+def join(left, right, onclause=None, isouter=False):
"""Return a ``JOIN`` clause element (regular inner join).
The returned object is an instance of [sqlalchemy.sql.expression#Join].
@@ -113,7 +113,7 @@ def join(left, right, onclause=None, **kwargs):
methods on the resulting ``Join`` object.
"""
- return Join(left, right, onclause, **kwargs)
+ return Join(left, right, onclause, isouter)
def select(columns=None, whereclause=None, from_obj=[], **kwargs):
"""Returns a ``SELECT`` clause element.
@@ -831,14 +831,35 @@ class _FunctionGenerator(object):
return _Function(self.__names[-1], packagenames=self.__names[0:-1], *c, **o)
+# "func" global - i.e. func.count()
func = _FunctionGenerator()
+# "modifier" global - i.e. modifier.distinct
# TODO: use UnaryExpression for this instead ?
modifier = _FunctionGenerator(group=False)
def _clone(element):
return element._clone()
+def _expand_cloned(elements):
+ """expand the given set of ClauseElements to be the set of all 'cloned' predecessors."""
+
+ return itertools.chain(*[x._cloned_set for x in elements])
+
+def _cloned_intersection(a, b):
+ """return the intersection of sets a and b, counting
+ any overlap between 'cloned' predecessors.
+
+ The returned set is in terms of the enties present within 'a'.
+
+ """
+ all_overlap = util.Set(_expand_cloned(a)).intersection(_expand_cloned(b))
+ return a.intersection(
+ [
+ elem for elem in a if all_overlap.intersection(elem._cloned_set)
+ ]
+ )
+
def _compound_select(keyword, *selects, **kwargs):
return CompoundSelect(keyword, *selects, **kwargs)
@@ -894,6 +915,7 @@ def _selectable(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)
@@ -1475,7 +1497,7 @@ class ColumnElement(ClauseElement, _CompareMixin):
co = _ColumnClause(name, selectable, type_=getattr(self, 'type', None))
else:
name = str(self)
- co = _ColumnClause(self.anon_label.name, selectable, type_=getattr(self, 'type', None))
+ co = _ColumnClause(self.anon_label, selectable, type_=getattr(self, 'type', None))
co.proxies = [self]
selectable.columns[name]= co
@@ -1495,7 +1517,7 @@ class ColumnElement(ClauseElement, _CompareMixin):
"""
if not hasattr(self, '_ColumnElement__anon_label'):
- self.__anon_label = self.label(None)
+ self.__anon_label = "{ANON %d %s}" % (id(self), getattr(self, 'name', 'anon'))
return self.__anon_label
anon_label = property(anon_label)
@@ -1626,20 +1648,20 @@ class FromClause(Selectable):
col = list(self.columns)[0]
return select([func.count(col).label('tbl_row_count')], whereclause, from_obj=[self], **params)
- def select(self, whereclauses = None, **params):
+ def select(self, whereclause=None, **params):
"""return a SELECT of this ``FromClause``."""
- return select([self], whereclauses, **params)
+ return select([self], whereclause, **params)
- def join(self, right, *args, **kwargs):
+ def join(self, right, onclause=None, isouter=False):
"""return a join of this ``FromClause`` against another ``FromClause``."""
- return Join(self, right, *args, **kwargs)
+ return Join(self, right, onclause, isouter)
- def outerjoin(self, right, *args, **kwargs):
+ def outerjoin(self, right, onclause=None):
"""return an outer join of this ``FromClause`` against another ``FromClause``."""
- return Join(self, right, isouter=True, *args, **kwargs)
+ return Join(self, right, onclause, True)
def alias(self, name=None):
"""return an alias of this ``FromClause`` against another ``FromClause``."""
@@ -1709,7 +1731,7 @@ class FromClause(Selectable):
return getattr(self, 'name', self.__class__.__name__ + " object")
description = property(description)
- def _clone_from_clause(self):
+ def _reset_exported(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
@@ -2075,7 +2097,7 @@ class _Function(_CalculatedClause, FromClause):
def _copy_internals(self, clone=_clone):
_CalculatedClause._copy_internals(self, clone=clone)
- self._clone_from_clause()
+ self._reset_exported()
def get_children(self, **kwargs):
return _CalculatedClause.get_children(self, **kwargs)
@@ -2206,8 +2228,8 @@ class _Exists(_UnaryExpression):
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 select(self, whereclause=None, **params):
+ return select([self], whereclause, **params)
def correlate(self, fromclause):
e = self._clone()
@@ -2230,14 +2252,15 @@ class Join(FromClause):
off all ``FromClause`` subclasses.
"""
- def __init__(self, left, right, onclause=None, isouter = False):
+ 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)
+ self.onclause = self.__match_primaries(self.left, self.right)
else:
self.onclause = onclause
+
self.isouter = isouter
self.__folded_equivalents = None
@@ -2263,7 +2286,7 @@ class Join(FromClause):
self._oid_column = self.left.oid_column
def _copy_internals(self, clone=_clone):
- self._clone_from_clause()
+ self._reset_exported()
self.left = clone(self.left)
self.right = clone(self.right)
self.onclause = clone(self.onclause)
@@ -2272,7 +2295,7 @@ class Join(FromClause):
def get_children(self, **kwargs):
return self.left, self.right, self.onclause
- def _match_primaries(self, primary, secondary):
+ def __match_primaries(self, primary, secondary):
crit = []
constraints = util.Set()
for fk in secondary.foreign_keys:
@@ -2302,50 +2325,7 @@ class Join(FromClause):
else:
return and_(*crit)
- def _folded_equivalents(self, equivs=None):
- """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.
-
- this method is used by select(fold_equivalents=True).
-
- The primary usage for this is when generating UNIONs so that
- each selectable can have distinctly-named columns without the need
- for use_labels=True.
- """
-
- 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._folded_equivalents(equivs)
- else:
- left = list(self.left.columns)
- if isinstance(self.right, Join):
- right = self.right._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(_folded_equivalents)
-
- def select(self, whereclause = None, fold_equivalents=False, **kwargs):
+ def select(self, whereclause=None, fold_equivalents=False, **kwargs):
"""Create a ``Select`` from this ``Join``.
whereclause
@@ -2366,7 +2346,10 @@ class Join(FromClause):
"""
if fold_equivalents:
- collist = self.folded_equivalents
+ global sql_util
+ if not sql_util:
+ from sqlalchemy.sql import util as sql_util
+ collist = sql_util.folded_equivalents(self)
else:
collist = [self.left, self.right]
@@ -2439,7 +2422,7 @@ class Alias(FromClause):
self._oid_column = self.selectable.oid_column._make_proxy(self)
def _copy_internals(self, clone=_clone):
- self._clone_from_clause()
+ self._reset_exported()
self.selectable = _clone(self.selectable)
baseselectable = self.selectable
while isinstance(baseselectable, Alias):
@@ -2670,7 +2653,7 @@ class _ColumnClause(ColumnElement):
return []
def _bind_param(self, obj):
- return _BindParamClause(self._label, obj, type_=self.type, unique=True)
+ return _BindParamClause(self.name, obj, type_=self.type, unique=True)
def _make_proxy(self, selectable, name = None):
# propigate the "is_literal" flag only if we are keeping our name,
@@ -2733,18 +2716,6 @@ class TableClause(FromClause):
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, inline=False, **kwargs):
return insert(self, values=values, inline=inline, **kwargs)
@@ -2780,8 +2751,8 @@ class _SelectBaseMixin(object):
is eligible to be used as a scalar expression.
The returned object is an instance of [sqlalchemy.sql.expression#_ScalarSelect].
- """
+ """
return _ScalarSelect(self)
def apply_labels(self):
@@ -2791,8 +2762,8 @@ class _SelectBaseMixin(object):
name, such as "SELECT somecolumn AS tablename_somecolumn". This allows selectables which
contain multiple FROM clauses to produce a unique set of column names regardless of name conflicts
among the individual FROM clauses.
- """
+ """
s = self._generate()
s.use_labels = True
return s
@@ -2802,8 +2773,8 @@ class _SelectBaseMixin(object):
with a label.
See also ``as_scalar()``.
- """
+ """
return self.as_scalar().label(name)
def supports_execution(self):
@@ -2819,8 +2790,9 @@ class _SelectBaseMixin(object):
return s
def _generate(self):
- s = self._clone()
- s._clone_from_clause()
+ s = self.__class__.__new__(self.__class__)
+ s.__dict__ = self.__dict__.copy()
+ s._reset_exported()
return s
def limit(self, limit):
@@ -2841,8 +2813,8 @@ class _SelectBaseMixin(object):
"""return a new selectable with the given list of ORDER BY criterion applied.
The criterion will be appended to any pre-existing ORDER BY criterion.
- """
+ """
s = self._generate()
s.append_order_by(*clauses)
return s
@@ -2851,8 +2823,8 @@ class _SelectBaseMixin(object):
"""return a new selectable with the given list of GROUP BY criterion applied.
The criterion will be appended to any pre-existing GROUP BY criterion.
- """
+ """
s = self._generate()
s.append_group_by(*clauses)
return s
@@ -2862,12 +2834,7 @@ class _SelectBaseMixin(object):
The criterion will be appended to any pre-existing ORDER BY criterion.
- Note that this mutates the Select construct such that derived attributes,
- such as the "primary_key", "oid_column", and child "froms" collection may
- be invalid if they have already been initialized. Consider the generative
- form of this method instead to prevent this issue.
"""
-
if len(clauses) == 1 and clauses[0] is None:
self._order_by_clause = ClauseList()
else:
@@ -2880,12 +2847,7 @@ class _SelectBaseMixin(object):
The criterion will be appended to any pre-existing GROUP BY criterion.
- Note that this mutates the Select construct such that derived attributes,
- such as the "primary_key", "oid_column", and child "froms" collection may
- be invalid if they have already been initialized. Consider the generative
- form of this method instead to prevent this issue.
"""
-
if len(clauses) == 1 and clauses[0] is None:
self._group_by_clause = ClauseList()
else:
@@ -2893,14 +2855,6 @@ class _SelectBaseMixin(object):
clauses = list(self._group_by_clause) + list(clauses)
self._group_by_clause = ClauseList(*clauses)
- def select(self, whereclauses = None, **params):
- """return a SELECT of this selectable.
-
- This has the effect of embeddeding this select into a subquery that is selected
- from.
- """
- return select([self], whereclauses, **params)
-
def _get_from_objects(self, is_where=False, **modifiers):
if is_where:
return []
@@ -2974,7 +2928,7 @@ class CompoundSelect(_SelectBaseMixin, FromClause):
self._oid_column = col
def _copy_internals(self, clone=_clone):
- self._clone_from_clause()
+ self._reset_exported()
self.selects = [clone(s) for s in self.selects]
if hasattr(self, '_col_map'):
del self._col_map
@@ -3025,14 +2979,7 @@ class Select(_SelectBaseMixin, FromClause):
self._should_correlate = correlate
self._distinct = distinct
- # NOTE: the _generate()
- # operation creates a *shallow* copy of the object, so append_XXX() methods,
- # usually called via a generative method, create a copy of each collection
- # by default
-
- self.__correlate = util.Set()
- self._having = None
- self._prefixes = []
+ self._correlate = util.Set()
if columns:
self._raw_columns = [
@@ -3042,7 +2989,7 @@ class Select(_SelectBaseMixin, FromClause):
]
else:
self._raw_columns = []
-
+
if from_obj:
self._froms = util.Set([
_is_literal(f) and _TextFromClause(f) or f
@@ -3050,7 +2997,7 @@ class Select(_SelectBaseMixin, FromClause):
])
else:
self._froms = util.Set()
-
+
if whereclause:
self._whereclause = _literal_as_text(whereclause)
else:
@@ -3075,8 +3022,8 @@ class Select(_SelectBaseMixin, FromClause):
rendered in the FROM clause of enclosing selects; this Select
may want to leave those absent if it is automatically
correlating.
+
"""
-
froms = util.OrderedSet()
for col in self._raw_columns:
@@ -3091,12 +3038,16 @@ class Select(_SelectBaseMixin, FromClause):
toremove = itertools.chain(*[f._hide_froms for f in froms])
froms.difference_update(toremove)
- if len(froms) > 1 or self.__correlate:
- if self.__correlate:
- froms.difference_update(self.__correlate)
- if self._should_correlate and existing_froms is not None:
- froms.difference_update(existing_froms)
-
+ if len(froms) > 1 or self._correlate:
+ if self._correlate:
+ froms.difference_update(_cloned_intersection(froms, self._correlate))
+
+ if self._should_correlate and existing_froms:
+ froms.difference_update(_cloned_intersection(froms, existing_froms))
+
+ if not len(froms):
+ raise exceptions.InvalidRequestError("Select statement '%s' returned no FROM clauses due to auto-correlation; specify correlate(<tables>) to control correlation manually." % self)
+
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.""")
@@ -3110,31 +3061,30 @@ class Select(_SelectBaseMixin, FromClause):
This set is a superset of that returned by the ``froms`` property, which
is specifically for those FromClause elements that would actually be rendered.
+
"""
if hasattr(self, '_all_froms'):
return self._all_froms
- froms = util.Set()
- for col in self._raw_columns:
- for f in col._get_from_objects():
- froms.add(f)
+ froms = util.Set(
+ itertools.chain(*
+ [self._froms] +
+ [f._get_from_objects() for f in self._froms] +
+ [col._get_from_objects() for col in self._raw_columns]
+ )
+ )
- if self._whereclause is not None:
- for f in self._whereclause._get_from_objects(is_where=True):
- froms.add(f)
+ if self._whereclause:
+ froms.update(self._whereclause._get_from_objects(is_where=True))
- for elem in self._froms:
- froms.add(elem)
- for f in elem._get_from_objects():
- froms.add(f)
self._all_froms = froms
return froms
def inner_columns(self):
- """a collection of all ColumnElement expressions which would
+ """an iteratorof all ColumnElement expressions which would
be rendered into the columns clause of the resulting SELECT statement.
- """
+ """
for c in self._raw_columns:
if isinstance(c, Selectable):
for co in c.columns:
@@ -3153,8 +3103,10 @@ class Select(_SelectBaseMixin, FromClause):
return False
def _copy_internals(self, clone=_clone):
- self._clone_from_clause()
- self._recorrelate_froms([(f, clone(f)) for f in self._froms])
+ self._reset_exported()
+ from_cloned = dict([(f, clone(f)) for f in self._froms.union(self._correlate)])
+ self._froms = util.Set([from_cloned[f] for f in self._froms])
+ self._correlate = util.Set([from_cloned[f] for f in self._correlate])
self._raw_columns = [clone(c) for c in self._raw_columns]
for attr in ('_whereclause', '_having', '_order_by_clause', '_group_by_clause'):
if getattr(self, attr) is not None:
@@ -3167,25 +3119,17 @@ class Select(_SelectBaseMixin, FromClause):
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):
"""return a new select() construct with the given column expression added to its columns clause."""
s = self._generate()
- s.append_column(column)
+ column = _literal_as_column(column)
+
+ if isinstance(column, _ScalarSelect):
+ column = column.self_group(against=operators.comma_op)
+
+ s._raw_columns = s._raw_columns + [column]
+
return s
def where(self, whereclause):
@@ -3216,7 +3160,8 @@ class Select(_SelectBaseMixin, FromClause):
columns clause, not using any commas."""
s = self._generate()
- s.append_prefix(clause)
+ clause = _literal_as_text(clause)
+ s._prefixes = s._prefixes + [clause]
return s
def select_from(self, fromclause):
@@ -3224,16 +3169,14 @@ class Select(_SelectBaseMixin, FromClause):
FROM objects."""
s = self._generate()
- s.append_from(fromclause)
- return s
+ if _is_literal(fromclause):
+ fromclause = _TextFromClause(fromclause)
- def __dont_correlate(self):
- s = self._generate()
- s._should_correlate = False
+ s._froms = s._froms.union([fromclause])
return s
- def correlate(self, fromclause):
- """return a new select() construct which will correlate the given FROM clause to that
+ def correlate(self, *fromclauses):
+ """return a new select() construct which will correlate the given FROM clauses to that
of an enclosing select(), if a match is found.
By "match", the given fromclause must be present in this select's list of FROM objects
@@ -3243,77 +3186,47 @@ class Select(_SelectBaseMixin, FromClause):
select() auto-correlates all of its FROM clauses to those of an embedded select when
compiled.
- If the fromclause is None, the select() will not correlate to anything.
+ If the fromclause is None, correlation is disabled for the returned select().
+
"""
-
s = self._generate()
s._should_correlate=False
- if fromclause is None:
- s.__correlate = util.Set()
+ if fromclauses == (None,):
+ s._correlate = util.Set()
else:
- s.append_correlation(fromclause)
+ s._correlate = s._correlate.union(fromclauses)
return s
- def append_correlation(self, fromclause, _copy_collection=True):
- """append the given correlation expression to this select() construct.
-
- Note that this mutates the Select construct such that derived attributes,
- such as the "primary_key", "oid_column", and child "froms" collection may
- be invalid if they have already been initialized. Consider the generative
- form of this method instead to prevent this issue.
- """
-
- if not _copy_collection:
- self.__correlate.add(fromclause)
- else:
- self.__correlate = util.Set(list(self.__correlate) + [fromclause])
-
- def append_column(self, column, _copy_collection=True):
- """append the given column expression to the columns clause of this select() construct.
+ def append_correlation(self, fromclause):
+ """append the given correlation expression to this select() construct."""
+
+ self._should_correlate=False
+ self._correlate.add(fromclause)
- Note that this mutates the Select construct such that derived attributes,
- such as the "primary_key", "oid_column", and child "froms" collection may
- be invalid if they have already been initialized. Consider the generative
- form of this method instead to prevent this issue.
- """
+ def append_column(self, column):
+ """append the given column expression to the columns clause of this select() construct."""
column = _literal_as_column(column)
if isinstance(column, _ScalarSelect):
column = column.self_group(against=operators.comma_op)
- if not _copy_collection:
- self._raw_columns.append(column)
- else:
- self._raw_columns = self._raw_columns + [column]
-
- def append_prefix(self, clause, _copy_collection=True):
- """append the given columns clause prefix expression to this select() construct.
+ self._raw_columns.append(column)
+ self._reset_exported()
- Note that this mutates the Select construct such that derived attributes,
- such as the "primary_key", "oid_column", and child "froms" collection may
- be invalid if they have already been initialized. Consider the generative
- form of this method instead to prevent this issue.
- """
+ def append_prefix(self, clause):
+ """append the given columns clause prefix expression to this select() construct."""
clause = _literal_as_text(clause)
- if not _copy_collection:
- self._prefixes.append(clause)
- else:
- self._prefixes = self._prefixes + [clause]
+ self._prefixes.append(clause)
def append_whereclause(self, whereclause):
"""append the given expression to this select() construct's WHERE criterion.
The expression will be joined to existing WHERE criterion via AND.
- Note that this mutates the Select construct such that derived attributes,
- such as the "primary_key", "oid_column", and child "froms" collection may
- be invalid if they have already been initialized. Consider the generative
- form of this method instead to prevent this issue.
"""
-
- if self._whereclause is not None:
+ if self._whereclause is not None:
self._whereclause = and_(self._whereclause, _literal_as_text(whereclause))
else:
self._whereclause = _literal_as_text(whereclause)
@@ -3323,33 +3236,20 @@ class Select(_SelectBaseMixin, FromClause):
The expression will be joined to existing HAVING criterion via AND.
- Note that this mutates the Select construct such that derived attributes,
- such as the "primary_key", "oid_column", and child "froms" collection may
- be invalid if they have already been initialized. Consider the generative
- form of this method instead to prevent this issue.
"""
-
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, _copy_collection=True):
+ def append_from(self, fromclause):
"""append the given FromClause expression to this select() construct's FROM clause.
- Note that this mutates the Select construct such that derived attributes,
- such as the "primary_key", "oid_column", and child "froms" collection may
- be invalid if they have already been initialized. Consider the generative
- form of this method instead to prevent this issue.
"""
-
if _is_literal(fromclause):
fromclause = _TextFromClause(fromclause)
- if not _copy_collection:
- self._froms.add(fromclause)
- else:
- self._froms = util.Set(list(self._froms) + [fromclause])
+ self._froms.add(fromclause)
def __exportable_columns(self):
for column in self._raw_columns:
@@ -3380,8 +3280,8 @@ class Select(_SelectBaseMixin, FromClause):
This produces an element that can be embedded in an expression. Note that
this method is called automatically as needed when constructing expressions.
- """
+ """
if isinstance(against, CompoundSelect):
return self
return _FromGrouping(self)
@@ -3454,6 +3354,11 @@ class _UpdateBase(ClauseElement):
def _table_iterator(self):
return iter([self.table])
+ def _generate(self):
+ s = self.__class__.__new__(self.__class__)
+ s.__dict__ = self.__dict__.copy()
+ return s
+
def _process_colparams(self, parameters):
if parameters is None:
@@ -3532,7 +3437,7 @@ class Insert(_ValuesBase):
If multiple prefixes are supplied, they will be separated with
spaces.
"""
- gen = self._clone()
+ gen = self._generate()
clause = _literal_as_text(clause)
gen._prefixes = self._prefixes + [clause]
return gen
@@ -3564,7 +3469,7 @@ class Update(_ValuesBase):
"""return a new update() construct with the given expression added to its WHERE clause, joined
to the existing clause via AND, if any."""
- s = self._clone()
+ s = self._generate()
if s._whereclause is not None:
s._whereclause = and_(s._whereclause, _literal_as_text(whereclause))
else:
@@ -3591,7 +3496,7 @@ class Delete(_UpdateBase):
"""return a new delete() construct with the given expression added to its WHERE clause, joined
to the existing clause via AND, if any."""
- s = self._clone()
+ s = self._generate()
if s._whereclause is not None:
s._whereclause = and_(s._whereclause, _literal_as_text(whereclause))
else: