summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2016-09-30 10:09:56 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2016-10-01 09:46:11 -0400
commit333414fe94941a6a58e7d8e45042548eb2d58119 (patch)
treed4e82336605dbfbeda0afb5735b3c56449db9b56 /lib
parent9bfd0289383bfcaf650fe516862df545dcf95c2e (diff)
downloadsqlalchemy-333414fe94941a6a58e7d8e45042548eb2d58119.tar.gz
Add "eager_parenthesis" late-compilation rule, use w/ PG JSON/HSTORE
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. Fixes: #3806 Change-Id: I5899677b330595264543b055abd54f3c76bfabf2
Diffstat (limited to 'lib')
-rw-r--r--lib/sqlalchemy/dialects/postgresql/base.py2
-rw-r--r--lib/sqlalchemy/dialects/postgresql/hstore.py19
-rw-r--r--lib/sqlalchemy/dialects/postgresql/json.py23
-rw-r--r--lib/sqlalchemy/sql/compiler.py28
-rw-r--r--lib/sqlalchemy/sql/operators.py7
5 files changed, 60 insertions, 19 deletions
diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py
index a9f11aae0..bde855fbe 100644
--- a/lib/sqlalchemy/dialects/postgresql/base.py
+++ b/lib/sqlalchemy/dialects/postgresql/base.py
@@ -1270,11 +1270,13 @@ class PGCompiler(compiler.SQLCompiler):
)
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
)
diff --git a/lib/sqlalchemy/dialects/postgresql/hstore.py b/lib/sqlalchemy/dialects/postgresql/hstore.py
index 67923fe39..d3ff30efb 100644
--- a/lib/sqlalchemy/dialects/postgresql/hstore.py
+++ b/lib/sqlalchemy/dialects/postgresql/hstore.py
@@ -16,29 +16,36 @@ from ... import util
__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
)
diff --git a/lib/sqlalchemy/dialects/postgresql/json.py b/lib/sqlalchemy/dialects/postgresql/json.py
index 05c4d014d..821018471 100644
--- a/lib/sqlalchemy/dialects/postgresql/json.py
+++ b/lib/sqlalchemy/dialects/postgresql/json.py
@@ -17,33 +17,42 @@ from ... import util
__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
)
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py
index a2dbcee5c..6527eb8c6 100644
--- a/lib/sqlalchemy/sql/compiler.py
+++ b/lib/sqlalchemy/sql/compiler.py
@@ -994,7 +994,9 @@ class SQLCompiler(Compiled):
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 \
@@ -1014,6 +1016,7 @@ class SQLCompiler(Compiled):
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)
@@ -1025,10 +1028,21 @@ class SQLCompiler(Compiled):
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)
@@ -2215,6 +2229,12 @@ class StrSQLCompiler(SQLCompiler):
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, {})
diff --git a/lib/sqlalchemy/sql/operators.py b/lib/sqlalchemy/sql/operators.py
index 142606680..69eee28ab 100644
--- a/lib/sqlalchemy/sql/operators.py
+++ b/lib/sqlalchemy/sql/operators.py
@@ -215,11 +215,12 @@ class custom_op(object):
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 \
@@ -935,9 +936,10 @@ _PRECEDENCE = {
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,
@@ -985,6 +987,7 @@ _PRECEDENCE = {
as_: -1,
exists: 0,
+
_asbool: -10,
_smallest: _smallest,
_largest: _largest