Postgresql jsonb operators not working with spring data jpa - spring-data-jpa

I am trying to use postgresql jsonb operators with spring data jpa query as:
#Query(value="SELECT * from Employee e WHERE e.details #> '{\"province\":{\"city\":{\"town\": \":town\"}}, \"hobbies\": [\":hobby\"]}'",nativeQuery = true)
town and hobby are inputs.
There is no error but no result is returned, though records are there which meets the criteria
It seems parameter binding is not working.
What can be the solution?

Here, :town and :hobby is inside ''(single quote) means string literal, so parameter can't be replaced. You can use || to concat as string so that parameters are not inside in '' and can be replaced.
#Query(value="SELECT * from Employee e WHERE e.details #> ''||'{\"province\":{\"city\":{\"town\": \"' || :town || '\"}}, \"hobbies\": [\"' || :hobby || '\"]}'||'' ", nativeQuery = true)

Related

Springboot JPA Cast numeric substring to string

I have a JPA/Springboot application backed by a Postgres database. I need to get a records that is equal to a substring passed back to the server.
For example:
Select * from dp1_attachments where TRIM(RIGHT(dp1_submit_date_dp1_number::text, 5)) ='00007'
This query works in PgAdmin, but not in the JPA #Query statement.
#Query("SELECT a.attachmentsFolder as attachmentsFolder, a.attachmentNumber as attachmentNumber, a.attachmentName as attachmentName, a.dp1SubmitDateDp1Number as dp1SubmitDateDp1Number,a.attachmentType as attachmentType, a.attachmentDate as attachmentDate, a.attachmentBy as attachmentBy "
+ "FROM DP1Attachments a WHERE TRIM(SUBSTRING(a.dp1SubmitDateDp1Number::text, 5 )) = :dp1Number")
I've also tried CASTing the parameter like this:
#Query("SELECT a.attachmentsFolder as attachmentsFolder, a.attachmentNumber as attachmentNumber, a.attachmentName as attachmentName, a.dp1SubmitDateDp1Number as dp1SubmitDateDp1Number,a.attachmentType as attachmentType, a.attachmentDate as attachmentDate, a.attachmentBy as attachmentBy "
+ "FROM DP1Attachments a WHERE TRIM(SUBSTRING(CAST(a.dp1SubmitDateDp1Number as string, 5 ))) = :dp1Number")
but the application won't even run, and returns an error that the query isn't valid.
If I make no attempt to cast it, I get an error that function pg_catalog.substring(numeric, integer) does not exist
UPDATE
I've also tried creating a native query instead but that also doesn't seem to work.
List<DP1AttachmentsProjection> results = em.createNativeQuery("Select * FROM dp1_attachments WHERE TRIM(RIGHT(CAST(dp1_submit_date_dp1_number as varchar),5)) =" + dp1Number).getResultList();
In place of varchar I have also tried string and text.
Errors come back similar to ERROR: operator does not exist: text = integer. Its like the CAST is being ignored and I'm not sure why.
I also tried the following as a native query:
em.createNativeQuery("Select * FROM dp1_attachments WHERE TRIM(RIGHT(dp1_submit_date_dp1_number::varchar),5)) =" + dp1Number).getResultList();
and get ERROR: syntax error at or near ":"
FINAL SOLUTION
Thanks to #Nenad J I altered the query to get the final working solution:
#Query(value = "SELECT a.attachments_Folder as attachmentsFolder, a.attachment_Number as attachmentNumber, a.attachment_Name as attachmentName, a.dp1_Submit_Date_Dp1_Number as dp1SubmitDateDp1Number,a.attachment_Type as attachmentType, a.attachment_Date as attachmentDate, a.attachment_By as attachmentBy FROM DP1_Attachments a WHERE TRIM(RIGHT(CAST(a.dp1_Submit_Date_Dp1_Number as varchar ), 5 )) = :dp1Number", nativeQuery = true)"
Default substring returns a string, so substring(integer data,5) returns a string. Thus no need for the cast.
#Query("SELECT * FROM DP1Attachments a WHERE TRIM(SUBSTRING(a.dp1SubmitDateDp1Number, 5)) = :dp1Number")
But I recommend in this case use native query like this:
Put this code in your attachment repository.
#Query(value="SELECT * FROM DP1Attachments AS a WHERE TRIM(SUBSTRING(a.dp1SubmitDateDp1Number, 5 )) = :dp1Number", nativeQuery=true)
Be careful with the column's name.

The column index is out of range: 2, number of columns: 1 error while updating jsonb column

I am trying to update jsonb column in java with mybatis.
Following is my mapper method
#Update("update service_user_assn set external_group = external_group || '{\"service_name\": \"#{service_name}\" }' where user=#{user} " +
" and service_name= (select service_name from services where service_name='Google') " )
public int update(#Param("service_name")String service_name,#Param("user") Integer user);
I am getting the following error while updating the jsonb (external_group) cloumn.
### Error updating database. Cause: org.postgresql.util.PSQLException: The column index is out of range: 2, number of columns: 1.
### The error may involve com.apds.mybatis.mapper.ServiceUserMapper.update-Inline
I am able to update with the same way for non-jsonb columns.
Also if I am putting hardcoded value it's working for jsonb columns.
How to solve this error while updating jsonb column?
You should not enclose #{} in single quotes because it will become part of a literal rather than a placeholder. i.e.
external_group = external_group || '{"service_name": "?"}' where ...
So, there will be only one placeholder in the PreparedStatement and you get the error.
The correct way is to concatenate the #{} in SQL.
You may also need to cast the literal to jsonb type explicitly.
#Update({
"update service_user_assn set",
"external_group = external_group",
"|| ('{\"service_name\": \"' || #{service_name} || '\" }')::jsonb",
"where user=#{user} and",
"service_name= (select service_name from services where service_name='Google')"})
The SQL being executed would look as follows.
external_group = external_group || ('{"service_name": "' || ? || '"}')::jsonb where ...

Spring data query with parameters

I have to create this N1QL couchbase query in Spring data
select... LIKE "TASK:100:%"
where 100 is a parameter, but I don't know if it is possible
#Query("#{#n1ql.selectEntity} where META().id like \"TASK:$1%:\" ")
List<Task> findTasks(String taskId);
The correct syntax would be the following:
#Query("#{#n1ql.selectEntity} where META().id like ('TASK:' || $1 || '%') ")
List<Task> findTasks(String taskId);
Although I think you should concatenate the id on the backend instead of inside the query.

Postgresql SQLSTATE[42P18]: Indeterminate datatype with PDO and CONCAT

I'm having issues with CONCAT() when used on a WHERE, in PDO.
The code:
<?php
require_once('config.php');
$fdate = '01/01/2010';
$tdate = '31/12/2030';
$identification = '';
$count = "SELECT count(*) as total FROM ( select time_id from doc_sent WHERE date >= :fdate AND date <= :tdate AND identification LIKE concat('%',:identification,'%') ) x;";
//$count = "SELECT count(*) as total FROM ( select time_id from doc_sent WHERE date >= :fdate AND date <= :tdate ) x;";
$stmt_count_row_main_table = $pdo->prepare($count);
$stmt_count_row_main_table->execute(['fdate' => $fdate, 'tdate' => $tdate, 'identification' => $identification]);
//$stmt_count_row_main_table->execute(['fdate' => $fdate, 'tdate' => $tdate]);
$count_row_main_table = $stmt_count_row_main_table->fetch();
print_r( $count_row_main_table);
?>
The code works when the 'identification' part is commented.
When I'm trying to use CONCAT(), it doesn't.
I tried many "version" of CONCAT() (and read many other questions, like this one: How do I create a PDO parameterized query with a LIKE statement? ) but I am always referring to the main documentation:
https://www.postgresql.org/docs/9.1/static/functions-string.html
Which say:
concat('abcde', 2, NULL, 22) --> abcde222
The FULL error when I use CONCAT() is:
PHP Fatal error: Uncaught PDOException: SQLSTATE[42P18]: Indeterminate datatype: 7 ERROR: could not determine data type of parameter $3 in /var/www/pdo-reporter/show.php:17\nStack trace:\n#0 /var/www/pdo-reporter/show.php(17): PDOStatement->execute(Array)\n#1 {main}\n thrown in /var/www/pdo-reporter/show.php on line 17
What's wrong with my code?
CONCAT is a function that takes a VARIADIC argument list, which means that internally postgres will convert them into an array of the same type.
postgres=# \df concat
List of functions
Schema | Name | Result data type | Argument data types | Type
------------+--------+------------------+---------------------+------
pg_catalog | concat | text | VARIADIC "any" | func
When trying to resolve the input type to a single type, the SQL parser fails. It can be reproduced in this simpler form:
postgres=# PREPARE p AS select concat('A', $1);
ERROR: could not determine data type of parameter $1
The parser can't figure out the datatype of $1 so it errs on the side of caution.
One easy solution is to cast the parameter as text:
postgres=# PREPARE p AS select concat($1::text);
PREPARE
or with the CAST operator:
postgres=# PREPARE p AS select concat(cast($1 as text));
PREPARE
I haven't tested with PDO but presumably it would work (given how it deals with parameters to produce prepared statements) to change the query to:
"...identification LIKE '%' || :identification || '::text%'..."
or use the '||' operator instead of concat in the query:
identification LIKE '%' || :identification || '%'
EDIT: BTW if you want to find that a parameter :X is a substring of identification, this clause is more secure: strpos(identification, :X) > 0, because :X may contain '%' or '_' without causing any side-effect in the match, contrary to what happens with LIKE.

Escaping hstore contains operators in a JDBC Prepared statement

I am using PostgreSQL 9.1.4 with hstore and the PostgreSQL JDBC driver (9.1-901.jdbc4).
I am trying to use the contains operators (?, ?&, ?|) in a PreparedStatement, however the ? character is parsed as a variable placeholder. Can this character be escaped to send the correct operator in the query?
An example:
PreparedStatement stmt = conn.prepareStatement("SELECT a, b FROM table1 WHERE c ? 'foo' AND d = ?");
stmt.setInt(1, dValue);
stmt.executeQuery();
In this form the following example would raise an exception:
org.postgresql.util.PSQLException: No value specified for parameter 2.
Update:
After investigating the query parser in the pgjdbc driver this snippet seems to indicate that it is not possible to escape the ? character. The questions that remain are:
Is there anything in the JDBC spec which allows a ? to be escaped and be anything other than a parameter placeholder?
Is there any better work around for this issue than just using plain Statements with variables manually inserted into the query string?
Effectively, it looks like the java SQL parser is not hstore compliant.
But since the syntax c ? 'foo' is equivalent to exist(c, 'foo'), you can easily workaround this problem. Have a look at the following page to see what the verbose operators for hstore are.
Postgres hstore documentation
There is a discussion about this issue on pgsql-hackers mailing list: http://grokbase.com/t/postgresql/pgsql-hackers/1325c6ys9n/alias-hstores-to-so-that-it-works-with-jdbc
For now I like most this workaround which also supports indexes:
CREATE FUNCTION exist_inline (hstore, text) RETURNS bool AS $$ SELECT $1 ? $2; $$ LANGUAGE sql;
You can use this query to find the function backing an operator in PostgreSQL like this. In your example:
SELECT
oprname,
oprcode || '(' || format_type(oprleft, NULL::integer) || ', '
|| format_type(oprright, NULL::integer) || ')' AS function
FROM pg_operator
WHERE oprname LIKE '?%'
AND (SELECT oid FROM pg_type WHERE typname = 'hstore') IN (oprleft, oprright);
This produces:
|oprname|function |
|-------|--------------------------|
|? |exist(hstore, text) |
|?| |exists_any(hstore, text[])|
|?& |exists_all(hstore, text[])|
See also a related question about using JSON operators containing ?. Note that the function usage may not profit from the same indexing capability when using a GIN index on your HSTORE column.
If you'd like to add multiple key-value pairs using PreparedStatement then you can do:
PreparedStatement ps = c.prepareStatement(
"insert into xyz(id, data) values(?, hstore(?, ?))");
ps.setLong(1, 23456L);
ps.setArray(2, c.createArrayOf("text", new String[]{"name", "city"}));
ps.setArray(3, c.createArrayOf("text", new String[]{"Duke", "Valley"}));
This will insert: 23456, 'name=>Duke, city=>Valley'