Injecting JSON parameter in nativeQuery - java

This works when
#Query(
value = "SELECT * FROM person WHERE school = :schoolId AND details #> '{\"id\":\"1234\",\"name\":\"John\"}'",
nativeQuery = true
)
I am passing #Param("schoolId") String schoolId
But when I pass the JSON as a param, it fails with
org.springframework.dao.InvalidDataAccessResourceUsageException, could not extract ResultSet; SQL [n/a]; nested exception is
org.hibernate.exception.SQLGrammarException: could not extract ResultSet
org.postgresql.util.PSQLException: ERROR: operator does not exist: jsonb #> character varying
Hint: No operator matches the given name and argument type(s). You might need to add explicit type casts.
#Query(value = "SELECT * FROM person WHERE school = :schoolId AND details #> :details", nativeQuery = true)
#Param("schoolId") String schoolId, #Param("details") String details

Spring+JDBC binds Strings as VARCHAR by default. The cheap solution here is to use cast(s):
details #> CAST(:details AS jsonb)
But, if you have a lot of queries, in which some non-standard types are used as parameters & you want to bind their String representation, you can use the
stringtype=unspecified
JDBC DSN parameter in your connection string. That way, every parameter, which is bound by setString() will have an unknown type in PostgreSQL, thus it will try to infer their actual types.

Thanks for this.
Just in addition to the solution, you can also do the cast as:
details #> :details::jsonb
I'm using version 11 of postgres, not sure when it was introduced.

Related

JPA Stored Procedure throwing exception: could not extract ResultSet

I am trying to call a basic stored procedure from azure sql which is just returning the number 1, which looks something like this
CREATE PROCEDURE [dbo].[testProc]
#TableName varchar(100)
AS
BEGIN
SET NOCOUNT ON
SELECT 1
END
I have a spring boot app trying call the stored procedure using the #Query annotation
#Repository
#Transactional
public interface TestDAO extends JpaRepository<TestEntity, Long> {
#Query(value = "CALL testProc(:TableName)", nativeQuery = true)
Long invokeTestProc(#Param("TableName") String TableName);
}
however, I get an exception which says
"Incorrect syntax near '#P0'"
and SQLGrammarException: could not extract ResultSet.
I am not sure how to fix this, I tried using the #Procedure with #NamedStoredProcedureQueries annotations and it threw another exception saying "Cannot mix positional parameter with named parameter registrations;"
According to the documentation for Azure Sql, you have to call the stored procedure with the following
EXECUTE HumanResources.uspGetEmployeesTest2 N'Ackerman', N'Pilar';
-- Or
EXEC HumanResources.uspGetEmployeesTest2 #LastName = N'Ackerman', #FirstName = N'Pilar';
So in your case this would be translated as
#Query(value = "EXECUTE testProc :TableName", nativeQuery = true)
Long invokeTestProc(#Param("TableName") String TableName);
or
#Query(value = "EXEC testProc #TableName = :TableName", nativeQuery = true)
Long invokeTestProc(#Param("TableName") String TableName);
So considering that you use native queries, the
exception which says "Incorrect syntax near '#P0'" and
SQLGrammarException: could not extract ResultSet.
reffers to the wrong use of sql grammar where you don't use execute or exec and you also pass the parameter in a way not expected for Azure SQL.

JDBC prepared statement to query JSON using json_exists

I'm facing trouble transforming the below query to jdbc prepared statement and setting the parameters.
oracle query:
select * from TRANSACTION_DUMMY where ID = 'aa'
and JSON_EXISTS(TRANSACTION_DUMMY_INDEX FORMAT JSON,
'$.header.lineItems[*].status?(#=="complete")')
translated query:
select * from TRANSACTION_DUMMY where ID = ?
and JSON_EXISTS(TRANSACTION_DUMMY_INDEX FORMAT JSON,
'$.header.lineItems[*].status?(#==?)')
the issue is how to set parameters in the query.
tried playing around with indexes but always getting the error, invalid column index.
any pointers how to handle the above scenario using java jdbc prepared statement?
thanks
According to the documentation, the second argument to JSON_EXISTS is a special string literal called JSON_path_expression.
If the value of the expression should change dynamically, it will be easiest to create it on the client (Java) side and then concatenate it into the query. You cannot pass the path expression as a bind variable because Oracle expects it to be a literal, i.e. a "parse-time constant". As you noticed, you'll get an ORA-40454: path expression not a literal error message if you try to pass the expression as a bind value.
The following code uses Java's String.format() for injecting the expression into the SQL template:
String sql = "select * from TRANSACTION_DUMMY where ID = 'aa' "
+ "and JSON_EXISTS(TRANSACTION_DUMMY_INDEX_FORMAT_JSON, %s)";
// here you could have some code for modifying jsonPathExpression dynamically,
// e.g. changing the status based on some criteria
String jsonPathExpression = "'$.header.lineItems[*].status?(#==\"complete\")'";
try (Statement st = myConnection.createStatement(String.format(sql, jsonPathExpression))) {
ResultSet st = ps.executeQuery();
// Process result set
}

Spring data repository sends null as bytea to PostgreSQL database

After switching from MySQL to PostgreSQL I found out that my SQL query (#Query in spring data repository interface) does not work anymore. The issue is caused by null value being sent as bytea and I'm getting following exception:
Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: bigint = bytea
Hint: No operator matches the given name and argument type(s). You might need to add explicit type casts.
Repository with #Query:
public interface WineRepository extends PagingAndSortingRepository<Wine, Long> {
#Query(value = "SELECT * FROM WINE w WHERE (?1 IS NULL OR w.id = ?1)", nativeQuery = true)
Wine simpleTest(Long id);
}
Simple test:
LOGGER.warn("test1: {}", wineRepository.simpleTest(1L)); //ok
LOGGER.warn("test2: {}", wineRepository.simpleTest(null)); //PSQLException
In the real case I have multiple parameters which can be null and I would prefer not checking them in java code but sending them to sql query. I have checked questions here on stackoverflow but found none with a good answer especially for spring data repository #query annotation.
What is a correct way of handling null values with PostgreSQL? Or do you have any hints how to fix my approach? Thanks!
Update:
Issue seems to be related to nativeQuery = true, when value is false, null values work as expected. So the question is whether it is possible to make it function even with nativeQuery enabled.
Try this.
SELECT *
FROM WINE w
WHERE ?1 IS NULL OR w.id = CAST(CAST(?1 AS TEXT) AS BIGINT)
It satisfies the type checker and should have the same properties as the original query. CAST is not a big performance hit if it happens on a constant value rather than a value from a database row.
You are trying to check whether a Java null is equal to Postgres NULL which I think is not necessary. You can rewrite as following and avoid sending null to the simpleTest function at all.
#Query(value = "SELECT * FROM WINE w WHERE (w.id IS NULL OR w.id = ?1)", nativeQuery = true)
Wine simpleTest(Long id);
I had a similar issue in an #Query where a Long was being interpreted by Postgres as a bytea. The solution for me was to unbox the #Param...by passing a long value, Postgres correctly interpreted the value as a bigint
I know it is an old issue, but you should be able to fix that bay casting the java value. Something like:
public interface WineRepository extends PagingAndSortingRepository<Wine, Long> {
#Query(value = "SELECT * FROM WINE w WHERE (?1\\:\\:bigint IS NULL OR w.id = ?1\\:\\:bigint)", nativeQuery = true)
Wine simpleTest(Long id);

Get SQL String substituted with parameters using java

Is there any easy way to get a completed SQL statement after parameter substitution?
I am using elasticsearch-sql to query elastic search with sql statements, however I have to submit the query with all the parameters substituted.
I tried Hibernate Query getQueryString, but the parameter substitution is not happening for those sql strings.
The following sql string is produced:
"SELECT * FROM USER WHERE NAME=? AND SURNAME=?"
rather than:
"SELECT * FROM USER WHERE NAME='Selva' AND SURNAME='Esra'
Appreciate any better idea/thoughts?
1. Named parameters
This is the most common and user friendly way. It use colon followed by a parameter name (:example) to define a named parameter. See examples…
String hql = "SELECT * FROM USER WHERE NAME= :userName AND SURNAME= :surName";
Query query = session.createQuery(hql);
query.setParameter("userName ", "userName");
query.setParameter("surName", "SurName");
List results = query.list();
An object-oriented representation of a Hibernate query. A Query instance is obtained by calling Session.createQuery(). This interface exposes some extra functionality beyond that provided by Session.iterate() and Session.find():
a particular page of the result set may be selected by calling setMaxResults(), setFirstResult()
named query parameters may be used
the results may be returned as an instance of ScrollableResults
Named query parameters are tokens of the form :name in the query string. A value is bound to the integer parameter :foo by calling
setParameter("foo", foo, Hibernate.INTEGER);
for example. A name may appear multiple times in the query string.
JDBC-style ? parameters are also supported. To bind a value to a JDBC-style parameter use a set method that accepts an int positional argument (numbered from zero, contrary to JDBC).
You may not mix and match JDBC-style parameters and named parameters in the same query.
2. Positional parameters
It’s use question mark (?) to define a named parameter, and you have to set your parameter according to the position sequence. See example…
Java
String hql = "from Stock s where s.stockCode = ? and s.stockName = ?";
List result = session.createQuery(hql)
.setString(0, "7277")
.setParameter(1, "DIALOG")
.list();
This approach is not support the setProperties function. In addition, it’s vulnerable to easy breakage because every change of the position of the bind parameters requires a change to the parameter binding code.
Java
String hql = "from Stock s where s.stockName = ? and s.stockCode = ?";
List result = session.createQuery(hql)
.setParameter(0, "DIALOG")
.setString(1, "7277")
.list();
Conclusion
In Hibernate parameter binding, i would recommend always go for “Named parameters“, as it’s more easy to maintain, and the compiled SQL statement can be reuse (if only bind parameters change) to increase the performance.

Passing clojure vec to POSTGRES IN statement (?)

I'm trying to pass an array of strings to a select statement and I keep getting the error:
org.postgresql.util.PSQLException: Can't infer the SQL type to use for an instance of clojure.lang.PersistentVector. Use setObject() with an explicit Types value to specify the type to use.
I know that the column type is correct, it looks as if passing a vector is the culprit. What is the correct way to do this?
The sql statement is formatted like so:
"SELECT * FROM said_table WHERE item_id IN (?)"
This answer assumes you are using jdbc and not korma or something like it and need to generate the sql directly instead of going through some tool:
the in directive requires you to crate one ? for each item in the list. I end up using this pattern when something else requires me to build the SQL manually:
(let [placeholders (s/join ", " (repeat (count things-go-here) "?"))
query "SELECT * FROM said_table WHERE item_id IN (%s)"]
(exec-raw [(format query placeholders) things-go-here] :results)
....)

Categories

Resources