those of the "excluded" namespace would not be table-qualified
in the WHERE clauses in the statement.
+ .. change::
+ :tags: bug, sql, postgresql
+ :tickets: 3806
+
+ Added compiler-level flags used by Postgresql to place additional
+ parenthesis than would normally be generated by precedence rules
+ around operations involving JSON, HSTORE indexing operators as well as
+ within their operands since it has been observed that Postgresql's
+ precedence rules for at least the HSTORE indexing operator is not
+ consistent between 9.4 and 9.5.
+
.. change::
:tags: bug, sql, mysql
:tickets: 3803
)
def visit_json_getitem_op_binary(self, binary, operator, **kw):
+ kw['eager_grouping'] = True
return self._generate_generic_binary(
binary, " -> ", **kw
)
def visit_json_path_getitem_op_binary(self, binary, operator, **kw):
+ kw['eager_grouping'] = True
return self._generate_generic_binary(
binary, " #> ", **kw
)
__all__ = ('HSTORE', 'hstore')
+idx_precedence = operators._PRECEDENCE[operators.json_getitem_op]
GETITEM = operators.custom_op(
- "->", precedence=15, natural_self_precedent=True,
+ "->", precedence=idx_precedence, natural_self_precedent=True,
+ eager_grouping=True
)
HAS_KEY = operators.custom_op(
- "?", precedence=15, natural_self_precedent=True
+ "?", precedence=idx_precedence, natural_self_precedent=True,
+ eager_grouping=True
)
HAS_ALL = operators.custom_op(
- "?&", precedence=15, natural_self_precedent=True
+ "?&", precedence=idx_precedence, natural_self_precedent=True,
+ eager_grouping=True
)
HAS_ANY = operators.custom_op(
- "?|", precedence=15, natural_self_precedent=True
+ "?|", precedence=idx_precedence, natural_self_precedent=True,
+ eager_grouping=True
)
CONTAINS = operators.custom_op(
- "@>", precedence=15, natural_self_precedent=True
+ "@>", precedence=idx_precedence, natural_self_precedent=True,
+ eager_grouping=True
)
CONTAINED_BY = operators.custom_op(
- "<@", precedence=15, natural_self_precedent=True
+ "<@", precedence=idx_precedence, natural_self_precedent=True,
+ eager_grouping=True
)
__all__ = ('JSON', 'JSONB')
+idx_precedence = operators._PRECEDENCE[operators.json_getitem_op]
+
ASTEXT = operators.custom_op(
- "->>", precedence=15, natural_self_precedent=True,
+ "->>", precedence=idx_precedence, natural_self_precedent=True,
+ eager_grouping=True
)
JSONPATH_ASTEXT = operators.custom_op(
- "#>>", precedence=15, natural_self_precedent=True,
+ "#>>", precedence=idx_precedence, natural_self_precedent=True,
+ eager_grouping=True
)
HAS_KEY = operators.custom_op(
- "?", precedence=15, natural_self_precedent=True
+ "?", precedence=idx_precedence, natural_self_precedent=True,
+ eager_grouping=True
)
HAS_ALL = operators.custom_op(
- "?&", precedence=15, natural_self_precedent=True
+ "?&", precedence=idx_precedence, natural_self_precedent=True,
+ eager_grouping=True
)
HAS_ANY = operators.custom_op(
- "?|", precedence=15, natural_self_precedent=True
+ "?|", precedence=idx_precedence, natural_self_precedent=True,
+ eager_grouping=True
)
CONTAINS = operators.custom_op(
- "@>", precedence=15, natural_self_precedent=True
+ "@>", precedence=idx_precedence, natural_self_precedent=True,
+ eager_grouping=True
)
CONTAINED_BY = operators.custom_op(
- "<@", precedence=15, natural_self_precedent=True
+ "<@", precedence=idx_precedence, natural_self_precedent=True,
+ eager_grouping=True
)
return "NOT %s" % self.visit_binary(
binary, override_operator=operators.match_op)
- def visit_binary(self, binary, override_operator=None, **kw):
+ def visit_binary(self, binary, override_operator=None,
+ eager_grouping=False, **kw):
+
# don't allow "? = ?" to render
if self.ansi_bind_rules and \
isinstance(binary.left, elements.BindParameter) and \
return self._generate_generic_binary(binary, opstring, **kw)
def visit_custom_op_binary(self, element, operator, **kw):
+ kw['eager_grouping'] = operator.eager_grouping
return self._generate_generic_binary(
element, " " + operator.opstring + " ", **kw)
return self._generate_generic_unary_modifier(
element, " " + operator.opstring, **kw)
- def _generate_generic_binary(self, binary, opstring, **kw):
- return binary.left._compiler_dispatch(self, **kw) + \
+ def _generate_generic_binary(
+ self, binary, opstring, eager_grouping=False, **kw):
+
+ _in_binary = kw.get('_in_binary', False)
+
+ kw['_in_binary'] = True
+ text = binary.left._compiler_dispatch(
+ self, eager_grouping=eager_grouping, **kw) + \
opstring + \
- binary.right._compiler_dispatch(self, **kw)
+ binary.right._compiler_dispatch(
+ self, eager_grouping=eager_grouping, **kw)
+
+ if _in_binary and eager_grouping:
+ text = "(%s)" % text
+ return text
def _generate_generic_unary_operator(self, unary, opstring, **kw):
return opstring + unary.element._compiler_dispatch(self, **kw)
self.process(binary.right, **kw)
)
+ def visit_json_getitem_op_binary(self, binary, operator, **kw):
+ return self.visit_getitem_binary(binary, operator, **kw)
+
+ def visit_json_path_getitem_op_binary(self, binary, operator, **kw):
+ return self.visit_getitem_binary(binary, operator, **kw)
+
def returning_clause(self, stmt, returning_cols):
columns = [
self._label_select_column(None, c, True, False, {})
def __init__(
self, opstring, precedence=0, is_comparison=False,
- natural_self_precedent=False):
+ natural_self_precedent=False, eager_grouping=False):
self.opstring = opstring
self.precedence = precedence
self.is_comparison = is_comparison
self.natural_self_precedent = natural_self_precedent
+ self.eager_grouping = eager_grouping
def __eq__(self, other):
return isinstance(other, custom_op) and \
from_: 15,
any_op: 15,
all_op: 15,
+ getitem: 15,
json_getitem_op: 15,
json_path_getitem_op: 15,
- getitem: 15,
+
mul: 8,
truediv: 8,
div: 8,
as_: -1,
exists: 0,
+
_asbool: -10,
_smallest: _smallest,
_largest: _largest
def test_where_getitem(self):
self._test_where(
self.hashcol['bar'] == None,
- "test_table.hash -> %(hash_1)s IS NULL"
+ "(test_table.hash -> %(hash_1)s) IS NULL"
)
def test_cols_get(self):
"(test_table.hash || test_table.hash) -> %(param_1)s AS anon_1"
)
+ def test_cols_against_is(self):
+ self._test_cols(
+ self.hashcol['foo'] != None,
+ "(test_table.hash -> %(hash_1)s) IS NOT NULL AS anon_1"
+ )
+
def test_cols_keys(self):
self._test_cols(
# hide from 2to3
def test_where_getitem(self):
self._test_where(
self.jsoncol['bar'] == None,
- "test_table.test_column -> %(test_column_1)s IS NULL"
+ "(test_table.test_column -> %(test_column_1)s) IS NULL"
)
def test_where_path(self):
self._test_where(
self.jsoncol[("foo", 1)] == None,
- "test_table.test_column #> %(test_column_1)s IS NULL"
+ "(test_table.test_column #> %(test_column_1)s) IS NULL"
)
def test_path_typing(self):
def test_where_getitem_as_text(self):
self._test_where(
self.jsoncol['bar'].astext == None,
- "test_table.test_column ->> %(test_column_1)s IS NULL"
+ "(test_table.test_column ->> %(test_column_1)s) IS NULL"
)
def test_where_getitem_astext_cast(self):
self._test_where(
self.jsoncol['bar'].astext.cast(Integer) == 5,
- "CAST(test_table.test_column ->> %(test_column_1)s AS INTEGER) "
+ "CAST((test_table.test_column ->> %(test_column_1)s) AS INTEGER) "
"= %(param_1)s"
)
def test_where_getitem_json_cast(self):
self._test_where(
self.jsoncol['bar'].cast(Integer) == 5,
- "CAST(test_table.test_column -> %(test_column_1)s AS INTEGER) "
+ "CAST((test_table.test_column -> %(test_column_1)s) AS INTEGER) "
"= %(param_1)s"
)
def test_where_path_as_text(self):
self._test_where(
self.jsoncol[("foo", 1)].astext == None,
- "test_table.test_column #>> %(test_column_1)s IS NULL"
+ "(test_table.test_column #>> %(test_column_1)s) IS NULL"
)
def test_cols_get(self):
).first()
eq_(result, ({'k1': 'r3v1', 'k2': 'r3v2'},))
+ result = engine.execute(
+ select([data_table.c.data]).where(
+ data_table.c.data['k1'].astext.cast(String) == 'r3v1'
+ )
+ ).first()
+ eq_(result, ({'k1': 'r3v1', 'k2': 'r3v2'},))
+
def _test_fixed_round_trip(self, engine):
s = select([
cast(
def visit_json_getitem_op_binary(self, binary, operator, **kw):
return self._generate_generic_binary(
- binary, " -> ", **kw
+ binary, " -> ", eager_grouping=True, **kw
)
def visit_json_path_getitem_op_binary(
self, binary, operator, **kw):
return self._generate_generic_binary(
- binary, " #> ", **kw
+ binary, " #> ", eager_grouping=True, **kw
)
def visit_getitem_binary(self, binary, operator, **kw):
checkparams={}
)
+ def test_getindex_sqlexpr_right_grouping(self):
+
+ col = Column('x', self.MyType())
+ col2 = Column('y', Integer())
+
self.assert_compile(
col[col2 + 8],
"x -> (y + :y_1)",
checkparams={'y_1': 8}
)
+ def test_getindex_sqlexpr_left_grouping(self):
+
+ col = Column('x', self.MyType())
+
+ self.assert_compile(
+ col[8] != None,
+ "(x -> :x_1) IS NOT NULL"
+ )
+
+ def test_getindex_sqlexpr_both_grouping(self):
+
+ col = Column('x', self.MyType())
+ col2 = Column('y', Integer())
+
+ self.assert_compile(
+ col[col2 + 8] != None,
+ "(x -> (y + :y_1)) IS NOT NULL",
+ checkparams={'y_1': 8}
+ )
+
def test_override_operators(self):
special_index_op = operators.custom_op('$$>')