# mypy: ignore-errors
+from .array import ARRAY
+from .array import array as _pg_array
from ... import types as sqltypes
+from ...sql import cast
from ...sql import operators
eager_grouping=True,
)
+DELETE_PATH = operators.custom_op(
+ "#-",
+ precedence=idx_precedence,
+ natural_self_precedent=True,
+ eager_grouping=True,
+)
+
+PATH_EXISTS = operators.custom_op(
+ "@?",
+ precedence=idx_precedence,
+ natural_self_precedent=True,
+ eager_grouping=True,
+)
+
+PATH_MATCH = operators.custom_op(
+ "@@",
+ precedence=idx_precedence,
+ natural_self_precedent=True,
+ eager_grouping=True,
+)
+
class JSONPathType(sqltypes.JSON.JSONPathType):
def _processor(self, dialect, super_proc):
It also adds additional operators specific to JSONB, including
:meth:`.JSONB.Comparator.has_key`, :meth:`.JSONB.Comparator.has_all`,
:meth:`.JSONB.Comparator.has_any`, :meth:`.JSONB.Comparator.contains`,
- and :meth:`.JSONB.Comparator.contained_by`.
+ :meth:`.JSONB.Comparator.contained_by`,
+ :meth:`.JSONB.Comparator.delete_path`,
+ :meth:`.JSONB.Comparator.path_exists` and
+ :meth:`.JSONB.Comparator.path_match`.
Like the :class:`_types.JSON` type, the :class:`_postgresql.JSONB`
type does not detect
CONTAINED_BY, other, result_type=sqltypes.Boolean
)
+ def delete_path(self, array):
+ """JSONB expression. Deletes field or array element specified in
+ the argument array.
+
+ The input may be a list of strings that will be coerced to an
+ ``ARRAY`` or an instance of :meth:`_postgres.array`.
+
+ .. versionadded:: 2.0
+ """
+ if not isinstance(array, _pg_array):
+ array = _pg_array(array)
+ right_side = cast(array, ARRAY(sqltypes.TEXT))
+ return self.operate(DELETE_PATH, right_side, result_type=JSONB)
+
+ def path_exists(self, other):
+ """Boolean expression. Test for presence of item given by the
+ argument JSONPath expression.
+
+ .. versionadded:: 2.0
+ """
+ return self.operate(
+ PATH_EXISTS, other, result_type=sqltypes.Boolean
+ )
+
+ def path_match(self, other):
+ """Boolean expression. Test if JSONPath predicate given by the
+ argument JSONPath expression matches.
+
+ Only the first item of the result is taken into account.
+
+ .. versionadded:: 2.0
+ """
+ return self.operate(
+ PATH_MATCH, other, result_type=sqltypes.Boolean
+ )
+
comparator_factory = Comparator
lambda self: self.jsoncol.contained_by({"foo": "1", "bar": None}),
"test_table.test_column <@ %(test_column_1)s",
),
+ (
+ lambda self: self.jsoncol.delete_path(["a", "b"]),
+ "test_table.test_column #- CAST(ARRAY[%(param_1)s, "
+ "%(param_2)s] AS TEXT[])",
+ ),
+ (
+ lambda self: self.jsoncol.delete_path(array(["a", "b"])),
+ "test_table.test_column #- CAST(ARRAY[%(param_1)s, "
+ "%(param_2)s] AS TEXT[])",
+ ),
+ (
+ lambda self: self.jsoncol.path_exists("$.k1"),
+ "test_table.test_column @? %(test_column_1)s",
+ ),
+ (
+ lambda self: self.jsoncol.path_match("$.k1[0] > 2"),
+ "test_table.test_column @@ %(test_column_1)s",
+ ),
)
def test_where(self, whereclause_fn, expected):
super().test_where(whereclause_fn, expected)
go("$.k1.k2", 0)
go("$.k1.r6v1", 1)
+ @testing.combinations(
+ ["k1", "r6v1", "subr", 1],
+ array(["k1", "r6v1", "subr", 1]),
+ argnames="path",
+ )
+ def test_delete_path(self, connection, path):
+ self._fixture_data(connection)
+ q = select(self.data_table.c.data.delete_path(path)).where(
+ self.data_table.c.name == "r6"
+ )
+ res = connection.scalar(q)
+ eq_(res, {"k1": {"r6v1": {"subr": [1, 3]}}})
+
class JSONBSuiteTest(suite.JSONTest):
__requires__ = ("postgresql_jsonb",)