chains of a single non-associative operator.
I.e. "x - (y - z)" will compile as "x - (y - z)"
and not "x - y - z". Also works with labels,
i.e. "x - (y - z).label('foo')"
[ticket:1984]
- Single element tuple expressions inside an IN clause
parenthesize correctly, also from [ticket:1984],
added tests for PG
- re-fix again importlater, [ticket:1983]
[ticket:1976]
- sql
+ - Fixed operator precedence rules for multiple
+ chains of a single non-associative operator.
+ I.e. "x - (y - z)" will compile as "x - (y - z)"
+ and not "x - y - z". Also works with labels,
+ i.e. "x - (y - z).label('foo')"
+ [ticket:1984]
+
- The 'info' attribute of Column is copied during
Column.copy(), i.e. as occurs when using columns
in declarative mixins. [ticket:1967]
[ticket:1871]
- postgresql
+ - Single element tuple expressions inside an IN clause
+ parenthesize correctly, also from [ticket:1984]
+
- Ensured every numeric, float, int code, scalar + array,
are recognized by psycopg2 and pg8000's "numeric"
base type. [ticket:1955]
return []
def self_group(self, against=None):
+ """Apply a 'grouping' to this :class:`.ClauseElement`.
+
+ This method is overridden by subclasses to return a
+ "grouping" construct, i.e. parenthesis. In particular
+ it's used by "binary" expressions to provide a grouping
+ around themselves when placed into a larger expression,
+ as well as by :func:`.select` constructs when placed into
+ the FROM clause of another :func:`.select`. (Note that
+ subqueries should be normally created using the
+ :func:`.Select.alias` method, as many platforms require
+ nested SELECT statements to be named).
+
+ As expressions are composed together, the application of
+ :meth:`self_group` is automatic - end-user code should never
+ need to use this method directly. Note that SQLAlchemy's
+ clause constructs take operator precedence into account -
+ so parenthesis might not be needed, for example, in
+ an expression like ``x OR (y AND z)`` - AND takes precedence
+ over OR.
+
+ The base :meth:`self_group` method of :class:`.ClauseElement`
+ just returns self.
+ """
return self
# TODO: remove .bind as a method from the root ClauseElement.
return list(itertools.chain(*[c._from_objects for c in self.clauses]))
def self_group(self, against=None):
- if self.group and self.operator is not against and \
- operators.is_precedent(self.operator, against):
+ if self.group and operators.is_precedent(self.operator, against):
return _Grouping(self)
else:
return self
)
def self_group(self, against=None):
- # use small/large defaults for comparison so that unknown
- # operators are always parenthesized
- if self.operator is not against and \
- operators.is_precedent(self.operator, against):
+ if operators.is_precedent(self.operator, against):
return _Grouping(self)
else:
return self
@util.memoized_property
def element(self):
return self._element.self_group(against=operators.as_)
-
+
+ def self_group(self, against=None):
+ sub_element = self._element.self_group(against=against)
+ if sub_element is not self._element:
+ return _Label(self.name,
+ sub_element,
+ type_=self._type)
+ else:
+ return self._element
+
@property
def primary_key(self):
return self.element.primary_key
def asc_op(a):
return a.asc()
+
_commutative = set([eq, ne, add, mul])
def is_commutative(op):
return op in _commutative
+_associative = _commutative.union([concat_op, and_, or_])
+
+
_smallest = symbol('_smallest')
_largest = symbol('_largest')
}
def is_precedent(operator, against):
- return (_PRECEDENCE.get(operator, _PRECEDENCE[_smallest]) <=
+ if operator is against and operator in _associative:
+ return False
+ else:
+ return (_PRECEDENCE.get(operator, _PRECEDENCE[_smallest]) <=
_PRECEDENCE.get(against, _PRECEDENCE[_largest]))
@memoized_property
def _il_module(self):
if self._il_addtl:
- m = __import__(self._il_path + "." + self._il_addtl)
- else:
- m = __import__(self._il_path)
- for token in self._il_path.split(".")[1:]:
- m = getattr(m, token)
- if self._il_addtl:
+ m = __import__(self._il_path, globals(), locals(),
+ [self._il_addtl])
try:
return getattr(m, self._il_addtl)
except AttributeError:
(self._il_path, self._il_addtl)
)
else:
+ m = __import__(self._il_path)
+ for token in self._il_path.split(".")[1:]:
+ m = getattr(m, token)
return m
def __getattr__(self, key):
assert (cc1==cc2).compare(c1 == c2)
assert not (cc1==cc3).compare(c2 == c3)
-
-
class LRUTest(TestBase):
def test_lru(self):
matchtable.c.title.match('nutshells'
)))).order_by(matchtable.c.id).execute().fetchall()
eq_([1, 3, 5], [r.id for r in results])
+
+
+class TupleTest(TestBase):
+ __only_on__ = 'postgresql'
+
+ def test_tuple_containment(self):
+
+ for test, exp in [
+ ([('a', 'b')], True),
+ ([('a', 'c')], False),
+ ([('f', 'q'), ('a', 'b')], True),
+ ([('f', 'q'), ('a', 'c')], False)
+ ]:
+ eq_(
+ testing.db.execute(
+ select([
+ tuple_(
+ literal_column("'a'"),
+ literal_column("'b'")
+ ).\
+ in_([
+ tuple_(*[
+ literal_column("'%s'" % letter)
+ for letter in elem
+ ]) for elem in test
+ ])
+ ])
+ ).scalar(),
+ exp
+ )
+
select([func.count(distinct(table1.c.myid))]),
"SELECT count(DISTINCT mytable.myid) AS count_1 FROM mytable"
)
-
+
def test_operators(self):
for (py_op, sql_op) in ((operator.add, '+'), (operator.mul, '*'),
(operator.sub, '-'),
self.assert_compile(
select([value_tbl.c.id], value_tbl.c.val1 / (value_tbl.c.val2 - value_tbl.c.val1) /value_tbl.c.val1 > 2.0),
- "SELECT values.id FROM values WHERE values.val1 / (values.val2 - values.val1) / values.val1 > :param_1"
+ "SELECT values.id FROM values WHERE (values.val1 / (values.val2 - values.val1)) / values.val1 > :param_1"
)
def test_collate(self):
tuple_(table1.c.myid, table1.c.name).in_(
[tuple_(table2.c.otherid, table2.c.othername)]
),
- "(mytable.myid, mytable.name) IN (myothertable.otherid, myothertable.othername)"
+ "(mytable.myid, mytable.name) IN ((myothertable.otherid, myothertable.othername))"
)
self.assert_compile(
self.assert_compile(table.select(between((table.c.field == table.c.field), False, True)),
"SELECT op.field FROM op WHERE (op.field = op.field) BETWEEN :param_1 AND :param_2")
+ def test_associativity(self):
+ f = column('f')
+ self.assert_compile( f - f, "f - f" )
+ self.assert_compile( f - f - f, "(f - f) - f" )
+
+ self.assert_compile( (f - f) - f, "(f - f) - f" )
+ self.assert_compile( (f - f).label('foo') - f, "(f - f) - f" )
+
+ self.assert_compile( f - (f - f), "f - (f - f)" )
+ self.assert_compile( f - (f - f).label('foo'), "f - (f - f)" )
+
+ # because - less precedent than /
+ self.assert_compile( f / (f - f), "f / (f - f)" )
+ self.assert_compile( f / (f - f).label('foo'), "f / (f - f)" )
+
+ self.assert_compile( f / f - f, "f / f - f" )
+ self.assert_compile( (f / f) - f, "f / f - f" )
+ self.assert_compile( (f / f).label('foo') - f, "f / f - f" )
+
+ # because / more precedent than -
+ self.assert_compile( f - (f / f), "f - f / f" )
+ self.assert_compile( f - (f / f).label('foo'), "f - f / f" )
+ self.assert_compile( f - f / f, "f - f / f" )
+ self.assert_compile( (f - f) / f, "(f - f) / f" )
+
+ self.assert_compile( ((f - f) / f) - f, "(f - f) / f - f")
+ self.assert_compile( (f - f) / (f - f), "(f - f) / (f - f)")
+
+ # higher precedence
+ self.assert_compile( (f / f) - (f / f), "f / f - f / f")
+
+ self.assert_compile( (f / f) - (f - f), "f / f - (f - f)")
+ self.assert_compile( (f / f) / (f - f), "(f / f) / (f - f)")
+ self.assert_compile( f / (f / (f - f)), "f / (f / (f - f))")
+
+
def test_delayed_col_naming(self):
my_str = Column(String)