I would like to call the postgresql function jsonb_path_exists (https://www.postgresql.org/docs/12/functions-json.html) using JPA criteria api.
Lets assume i have the following query:
select id from person where jsonb_path_exists(person.json,'$.some_property[0].typ ? (# =="Something")');
Via the CriteriaBuilder i would do something like:
var jsonQuery = jsonPath + " ? (# ==\"" + value + "\")"; // evaluates to '$.some_property[0].typ ? (# =="Something")'
criteriaBuilder.function("jsonb_path_exists",
String.class,
entityRoot.get("json"),
criteriaBuilder.literal(jsonQuery)
)
.in(Boolean.TRUE);
However i have not figured out how to cast the jsonQuery which is provided as string to postgres jsonpath type. Hence i receive the following exception:
org.postgresql.util.PSQLException: ERROR: function jsonb_path_exists(jsonb, character varying) does not exist
The correct signature is
jsonb_path_exists(target jsonb, path jsonpath [, vars jsonb [, silent bool]])
This issue exists for all functions with a jsonpath parameter e.g. jsonb_path_query,jsonb_path_match,jsonb_path_query_first
Does anybody have a clue on how to solve this?
I have solved this issue by writing a PostgreSQL wrapper function which I now call instead of jsonb_path_exists:
CREATE OR REPLACE FUNCTION jsonb_filter(target jsonb, path varchar)
RETURNS boolean
LANGUAGE plpgsql IMMUTABLE STRICT cost 1 AS
$func$
BEGIN
RETURN jsonb_path_exists(target,CAST(path AS jsonpath));
END
$func$;
Calling the created jsonb_filter function does the job:
return criteriaBuilder.function("jsonb_filter",
String.class,
entityRoot.get("json"),
jsonPathExpression
).in(Boolean.TRUE);
Make sure not to include surrounding single quotes in the jsonPathExpression, e.g. use $.your_expression instead of '$.your_expression'.
Related
Trying to create my first PostgreSQL function, I don't understand why I can't call this function.
CREATE FUNCTION public."SampledImpCountToOriginal"(IN integer)
RETURNS numeric
LANGUAGE 'plpgsql'
AS $BODY$
BEGIN
RETURN CASE WHEN $1 > 4 THEN EXP(POW($1 - 1.88, 1/2.3)) ELSE $1 END;
END;
$BODY$;
SELECT SampledImpCountToOriginal(5)
ERROR: function sampledimpcounttooriginal(integer) does not exist
LINE 1: SELECT SampledImpCountToOriginal(5)
^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. SQL state:
42883 Character: 8
Calling from the same database I created it in, tried changing owner, can't figure it out. The function is listed in pgAdmin as SampledImpCountToOriginal(IN integer).
You have to call like this - SELECT "SampledImpCountToOriginal"(5)
When ever you use Double Quotes "" to create Function, you have to use in calling process. like -
SELECT public."SampledImpCountToOriginal"(
<integer>
)
If you don't use double quotes "" to calling your created function with "". it consider different function.
SELECT public.sampledimpcounttooriginal(
<integer>
)
I am trying to do a case-insensitive partial search (contains string) on a property value - stored in a jsonb field in postgres.
The search is looking for a value within the title column of table destination which has an array of elements as follows:
[{"key": "EN", "text":"london and milk"},{"key": "FR", "text":"Edinburgh with milk and honey"}]
I have created a GIN index on the title field and a function to deal with the search.
CREATE OR REPLACE FUNCTION search(query_string character varying)
RETURNS SETOF destination
LANGUAGE 'plpgsql'
AS $BODY$
begin
return query select *
from destination
--where title #? '$.* ? (# like_regex ' || query_string || ' flag "i")';
where title #? '$.* ? (# like_regex ".*milk.*" flag "i")';
end;
$BODY$;
So the function works nicely if the regexp string is hardcoded (as shown above), but the search should be based on the incoming query_string. The commented line in the function shows an attempt to try to include the parameter in the query. (this will result in unterminated string constant error)
How can I exchange the hard-coded milk to parameter search_query?
Are there other (simpler) ways that would yield the same end result?
Your problem is one of precedence. #? and '||' are tied and are processed left to right, so you are applying #? to only a fragment of the string not the completely built string. Then you are trying to concat things to the Boolean result of #?. You can fix this by constructing the string inside parentheses. A side affect of this is that you then have to cast it to jsonpath explicitly.
where title #? ( '$.* ? (# like_regex "' || query_string || '" flag "i")' )::jsonpath;
But I think it would be cleaner to construct the jsonpath in a variable, rather than on the fly in the query itself. Could someone inject something into the jsonpath string that could do something nasty? I don't know enough about jsonpath to rule that out.
(code part of the suggested solution edited by question author to include the double quotes missing - see comment)
I'm trying to create a function in PostgreSQL that uses the keyword IN. For example:
SELECT * FROM test WHERE test.value IN (1,2,23,5,123 ...etc)
I would like to make the part after the IN a function argument. However I don't know what the data type is for this. I've looked at the data type in the docs, but couldn't figure it out.
Complete example:
create function testing(test_value ???) returns SETOF test
stable
language sql
as
$$
SELECT * FROM test WHERE test.value IN test_value
$$;
alter function testing(???) owner to postgres;
You can pass an array and use the ANY operator (the condition IN (...) is being converted to = ANY(array[...])` by the optimizer anyway).
create function testing(test_values int[]) returns SETOF test
stable
language sql
as
$$
SELECT * FROM test WHERE test.value = any(test_values)
$$;
Then use it like this:
select *
from testing(array[1,2,3,4]);
I haven't find a way to return data from a postgres function that return a TABLE:
CREATE OR REPLACE FUNCTION doc_codes(device_id TEXT) RETURNS TABLE("name" TEXT, "suffix" TEXT) AS $$
SELECT name, prefix_fordevice(name, device_id) AS pf FROM doccode;
$$ LANGUAGE SQL;
Using:
f = sq.sql.text("SELECT name, suffix FROM doc_codes(:deviceId)")
return self.con.execute(f, deviceId=deviceId)
Or
f = sq.sql.func.doc_codes(deviceId, type_=types.String)
return self.con.execute(
select([sq.Column('name', types.String), sq.Column('suffix', types.String)]). \
select_from(f)
).fetchall()
I get the error (ProgrammingError) a column definition list is required for functions returning "record".
Using f = sq.sql.func.doc_code(deviceId, type_=types.String) I get No function matches the given name and argument types. You might need to add explicit type casts and the generated SQL is SELECT doc_code('1') AS doc_code_1, instead of SELECT * FROM.
If a generate a table, I can't pass the deviceId to the table name... now what I can do?
The function definition must actually be RETURNS SETOF RECORD not RETURNS TABLE to produce that error.
If you actually needed to do it with RETURNS SETOF RECORD, you must call it with a column-definition list, like the error message says:
a column definition list is required for functions returning "record"
See the documentation. You can either use OUT parameters (equivalent to RETURNS TABLE), or call with a col-list, eg:
SELECT name, suffix
FROM doc_codes(:deviceId) AS dc("name" TEXT, "suffix" TEXT)
I am writing a stored procedure in PlPython with a user defined type. I know Plpython does not support user defined types, so, I have created a CAST for the user defined type. Still I keep getting an error when I call plpy.prepare. I am not sure if I am using the CAST incorrectly - the example code is below:
#User Defined Type - person
CREATE TYPE person As( name character varying(50), state character(2));
#Table definition using 'person'
CREATE TABLE manager As(id integer, mgr person)
#CAST for person
CREATE OR REPLACE FUNCTION person_to_text(person) RETURNS text AS 'SELECT ROW($1.*)::text' LANGUAGE SQL;
CREATE CAST (cv_person as text) WITH FUNCTION person_to_text(person)
#PlPython procedure
CREATE OR REPLACE FUNCTION load_portfolio_assoc (name text, state text) RETURNS integer AS $$
mgr_str ="('"+name+"','"+state+"')"
insert_qry = 'Insert into manager (mgr) values($1)'
value_type = ['text']
qry = plpy.prepare(insert_qry,value_type)
rv = plpy.execute(qry, [mgr_str])
return 1
$$ LANGUAGE plpython3u;
An update :
Plpython accepts the query when it is written as follows with the user defined type specified with the variable in this format $1:: and stops throwing composite type not supported exceptions,
insert_qry = 'Insert into manager (mgr) values($1::person)'
value_type = ['text']
At the end, I really didn't have to do any extra casting operation on the database. It worked just by tweaking the variable as above.