Как избежать знака вопроса (?) С помощью Spring JpaRepository - PullRequest
0 голосов
/ 22 мая 2018

Postgres определяет дополнительные операторы jsonb , такие как ?|.

Однако при использовании построителя запросов Spring JpaRepository символ запроса всегда рассматривается как параметр, и я не могу понять, какчтобы избежать этого (кроме как внутри одной строки кавычки, но тогда запрос недействителен).

Пример:

@Query(value = "SELECT * FROM public.user u WHERE u.authorities ?| array['ROLE_1', 'ROLE_2']", nativeQuery = true)

Ошибка:

java.lang.IllegalArgumentException: Unable to resolve given parameter name [1] to QueryParameter reference
    at org.hibernate.query.internal.QueryParameterBindingsImpl.resolveQueryParameter(QueryParameterBindingsImpl.java:520)
    at org.hibernate.query.internal.QueryParameterBindingsImpl.getQueryParameterListBinding(QueryParameterBindingsImpl.java:498)
    at org.hibernate.query.internal.AbstractProducedQuery.setParameterList(AbstractProducedQuery.java:560)

Есть лиспособ избежать этого или другое решение, позволяющее использовать тезисные операторы postgres, содержащие символ ?.

Попытка избежать этого с помощью ?? |или \?в настоящее время не работает.

Примечание: я также пытался использовать пользовательскую функцию диалекта, но она заканчивается той же проблемой.

Библиотеки:

  • hibernate 5.2.16
  • hibernate-jpa 2.1
  • spring-data-jpa 2.0.6.RELEASE
  • postgresql 42.2.2

Спасибо за вашответы ребята!

Ответы [ 2 ]

0 голосов
/ 23 мая 2018

В случае, если экранирование ? невозможно, вы можете создать дублирующий оператор с другим именем.

Новый оператор

Синтаксис для создание операторов в Postgres:

CREATE OPERATOR name (
    PROCEDURE = function_name
    [, LEFTARG = left_type ] [, RIGHTARG = right_type ]
    [, COMMUTATOR = com_op ] [, NEGATOR = neg_op ]
    [, RESTRICT = res_proc ] [, JOIN = join_proc ]
    [, HASHES ] [, MERGES ]
)

В случае использования ?| в jsonb это будет:

CREATE OPERATOR ^|(
  PROCEDURE = jsonb_exists_any,
  LEFTARG = jsonb,
  RIGHTARG = _text,
  RESTRICT = contsel,
  JOIN = contjoinsel);

Я использовал ^| в качестве примера, альтернативное имя.Это может быть любая последовательность из этого списка: + - * / < > = ~ ! @ # % ^ & |? `.

Вы можете найти текущее определение интересующего вас оператора, запросив pg_catalog.pg_operator table.

SELECT oid, *
  FROM pg_catalog.pg_operator
 WHERE oprname = '?|'
   AND oprleft = (SELECT oid FROM pg_type WHERE typname = 'jsonb');

Вы также можете использовать инструмент GUI, такой как pgAdmin и просмотреть pg_catalog, чтобы подготовить определение SQL для повторного использования.

Включение индекса

Если вы хотите использовать индекс для этого "нового"оператор, вам потребуется создать новый операторский класс и опционально семейство.В нашем случае нам нужны оба варианта, поскольку мы не можем добавить их в существующее семейство, поскольку оператор по умолчанию уже занимает слот стратегии .

Так же, как и с операторами, рекомендуется использоватьGUI-инструмент, такой как pgAdmin, для просмотра классов операторов и просто его копирования и вставки.

Сначала мы берем OID оператора, из которого мы сделали дубликат:

SELECT oid, *
  FROM pg_catalog.pg_operator
 WHERE oprname = '?|'
   AND oprleft = (SELECT oid FROM pg_type WHERE typname = 'jsonb');

То же самое для семейства операторов (мы получим егоиз таблицы классов операторов вместо этого), мы ищем класс джина, так как он поддерживает ?|.opcdefault используется, поскольку существует необязательный класс jsonb_path_ops, который не поддерживает этот оператор:

SELECT opcfamily
  FROM pg_opclass
 WHERE opcintype = (SELECT oid FROM pg_type WHERE typname = 'jsonb')
   AND opcmethod = (SELECT oid FROM pg_am WHERE amname = 'gin')
   AND opcdefault

Затем мы получаем стратегию, используемую оператором , которую мы дублировали:

SELECT amopstrategy,
       (SELECT typname FROM pg_type WHERE oid = amoplefttype) AS left_t, 
       (SELECT typname FROM pg_type WHERE oid = amoprighttype) AS right_t,*
FROM pg_amop
WHERE amopfamily = 4036 --family oid
  AND amopopr = 3248 --operator oid

Затем функции, используемые классом :

SELECT amprocnum, amproc::text, pg_get_function_identity_arguments(amproc::oid) AS args,
      (SELECT typname FROM pg_type WHERE oid = amproclefttype) AS left_t,
      (SELECT typname FROM pg_type WHERE oid = amprocrighttype) AS right_t,*
FROM pg_amproc
WHERE amprocfamily = 4036 --op family

Это подводит нас к этому классу операторов .Это создаст семейство операторов, если оно еще не существует.

CREATE OPERATOR CLASS jsonb_ops_custom
   FOR TYPE jsonb USING gin AS
   OPERATOR 10  ^|(jsonb, _text),
   FUNCTION 1  gin_compare_jsonb(text, text),
   FUNCTION 2  gin_extract_jsonb(jsonb, internal, internal),
   FUNCTION 3  gin_extract_jsonb_query(jsonb, internal, smallint, internal, internal, internal, internal),
   FUNCTION 4  gin_consistent_jsonb(internal, smallint, jsonb, integer, internal, internal, internal, internal),
   FUNCTION 6  gin_triconsistent_jsonb(internal, smallint, jsonb, integer, internal, internal, internal);

Теперь вам просто нужно создать индекс, используя имя оператора, которое было создано, что-то вроде:

CREATE INDEX ON jsonb_table USING gin(jsonb_column jsonb_ops_custom)

И вам следуетуметь использовать индекс:

SET enable_seqscan = off;
EXPLAIN ANALYZE
SELECT * FROM jsonb_table WHERE jsonb_column ^| array['b', 'c'];
0 голосов
/ 23 мая 2018

В качестве обходного пути для этого конкретного случая я создал пользовательский оператор:

CREATE OPERATOR ~~~| (
    LEFTARG = jsonb,
    RIGHTARG = _text,
    PROCEDURE = pg_catalog.jsonb_exists_any
)

Затем в своем запросе: WHERE u.authorities ~~~| array['ROLE_1', 'ROLE_2']

@ ŁukaszKamiński подробно описал этот обход в своем ответе здесь:https://stackoverflow.com/a/50488457/1097926

...