Passing a null parameter to a native query using #Query JPA annotation - postgresql

In a Spring Boot application, I have a SQL query that is executed on a postgresql server as follows :
#Query(value = "select count(*) from servers where brand= coalesce(?1, brand) " +
"and flavour= coalesce(?2, flavour) ; ",
nativeQuery = true)
Integer icecreamStockCount(String country, String category);
However,
I get the following error when I execute the method :
ERROR: COALESCE types bytea and character varying in PostgreSQL
How do I pass String value = null to the query?
**NOTE : ** I found that my question varied from JPA Query to handle NULL parameter value

You need not coalesce, try this
#Query("select count(*) from servers where (brand = ?1 or ?1 is null)" +
" and (flavour = ?2 or ?2 is null)")
Integer icecreamStockCount(String country, String category);

When I encounted this error, I ended up using a combination of OR and CAST to solve the issue.
SELECT COUNT(*)
FROM servers
WHERE (?1 IS NULL OR brand = CAST(?1 AS CHARACTER VARYING))
AND (?2 IS NULL OR flavour = CAST(?2 AS CHARACTER VARYING))
This works even if ?1, ?2, brand and flavor are all nullable fields.
Note that passing null for ?1 means "all servers regardless of brand" rather than "all servers without a brand". For the latter, you could use IS DISTINCT FROM as follows.
SELECT COUNT(*)
FROM servers
WHERE (CAST(?1 AS CHARACTER VARYING) IS NOT DISTINCT FROM brand)
AND (CAST(?2 AS CHARACTER VARYING) IS NOT DISTINCT FROM flavour)
Finally, certain parameter types such as Boolean cannot be cast in SQL from BYTEA to BOOLEAN, for those cases you need a double cast:
SELECT COUNT(*)
FROM servers
WHERE (?1 IS NULL OR is_black = CAST(CAST(?1 AS CHARACTER VARYING) AS BOOLEAN))
In my eyes this is a problem in Hibernate which could be solved by passing Java null parameters as plain SQL NULLs rather than interpreting null as a value of type BYTEA.

If you really need to use native query, there is a problem because it's an improvement not implemented yet, see hibernate. If you don't need to use native you can do (where ?1 is null or field like ?1). Assuming you do need native,
you may treat the String before by setting this empty and then calling the repository and this one would be like:
#Query(value = "select count(*) from servers where (?1 like '' or brand like ?1) " +
"and (?2 like '' or flavour like ?2)",
nativeQuery = true)
Integer icecreamStockCount(String country, String category);
There is always javax.persistence.EntityManager bean as option for native query situations and I recommend it instead of previous approach. Here you can append to your query the way you want, as follows:
String queryString = "select count(*) from servers ";
if (!isNull(country)) queryString += "where brand like :country";
Query query = entityManager.createNativeQuery(queryString);
if (!isNull(country)) query.setParameter("country", country);
return query.getResultList();
Observations:
Newer versions have improved this '+' concatenation Strings. But you can build your queryString the way you want with StringBuilder or String Format, it doesn't matter.
Be careful with SQL injection, the setParameter method avoid this kind of problem, for more information see this Sql Injection Baeldung

So this is not the exact answer to the question above, but I was facing a similar issue, I figured I would add it here, for those that come across this question.
I was using a native query, in my case, it was not a singular value like above, but I was passing in a list to match this part of the query:
WHERE (cm.first_name in (:firstNames) OR :firstNames is NULL)
I was getting the bytea error, in the end I was able to send an empty list.
(null == entity.getFirstName()? Collections.emptyList() : entity.getFirstName())
In this case, sending the empty list to the resolver worked, where as null did not.
hope this saves you some time.

null parameters are not allowed before Hibernate 5.0.2.
See https://hibernate.atlassian.net/browse/HHH-9165
and the replies to https://www.postgresql.org/message-id/6ekbd7dm4d6su5b9i4hsf92ibv4j76n51f#4ax.com

Related

like and nvl in jpql query - springboot JPA

i am trying to write the below query into spring boot jpql (postgresql db)
first_name
FROM
employees
WHERE
first_name LIKE concat(concat('%',nvl(:key,first_name) ),'%'); --This is oracle style query
Iam trying to write the same logic in JPQL(am new to JPA and JPL , but i know this simple logic ,we can achieve using the implementation less queires , but the above mentioned is a part of a bigger query).
I tried like below. But stuck actually.
#Query("select pe.packageName from PackageEntity pe where " +
"lower(pe.packageName) like " +
"case when :packageName is null then lower(pe.packageName) else concat('%',lower(:packageName),'%') end " )
List<String> getValues( #Param("packageName") String roomIDList);
Error Message is below
org.postgresql.util.PSQLException: ERROR: function lower(bytea) does not exist Hint: No function matches the given name and argument types. You might need to add explicit type casts. Position: 190
Honestly , i've tried every possible solution , i can think of. found same error in stackoverflow , but there solution doesn't match, I really want to send the null values in the query , if the input is null then need to ignore the validation, for example if the input parameter is null, then i want to completely ignore the validation.
solution :-
#Query("select pe.packageName from PackageEntity pe where " +
":packageName is null or lower(pe.packageName) like
concat('%',lower(:packageName),'%')" )
The above workaround worked actually.
But I implemented like, In cotroller code
if (search.getPackageName() == null) {
LOGGGER.info("Input Name is NULL");
search.setPackageName("==!##$%^&*()!##$%^&*()==");
LOGGGER.info("Input Name is set to Default " + search.getPackageName());
}
searchValue = repoPackage.getPackageAll(search.getPackageName());
in repo, where condition is like below - Not sure , if the solution is the best, but it worked. Like this I avoided sending any null value to jpa.
where
(('==!##$%^&*()!##$%^&*()==' = :packageName)
or lower(pd.package_name) like concat('%', lower(:packageName), '%'))

How to properly parameterize my postgresql query

I'm trying to parameterize my postgresql query in order to prevent SQL injection in my ruby on rails application. The SQL query will sum a different value in my table depending on the input.
Here is a simplified version of my function:
def self.calculate_value(value)
calculated_value = ""
if value == "quantity"
calculated_value = "COALESCE(sum(amount), 0)"
elsif value == "retail"
calculated_value = "COALESCE(sum(amount * price), 0)"
elsif value == "wholesale"
calculated_value = "COALESCE(sum(amount * cost), 0)"
end
query = <<-SQL
select CAST(? AS DOUBLE PRECISION) as ? from table1
SQL
return Table1.find_by_sql([query, calculated_value, value])
end
If I call calculate_value("retail"), it will execute the query like this:
select location, CAST('COALESCE(sum(amount * price), 0)' AS DOUBLE PRECISION) as 'retail' from table1 group by location
This results in an error. I want it to execute without the quotes like this:
select location, CAST(COALESCE(sum(amount * price), 0) AS DOUBLE PRECISION) as retail from table1 group by location
I understand that the addition of quotations is what prevents the sql injection but how would I prevent it in this case? What is the best way to handle this scenario?
NOTE: This is a simplified version of the queries I'll be writing and I'll want to use find_by_sql.
Prepared statement can not change query structure: table or column names, order by clause, function names and so on. Only literals can be changed this way.
Where is SQL injection? You are not going to put a user-defined value in the query text. Instead, you check the given value against the allowed list and use only your own written parts of SQL. In this case, there is no danger of SQL injection.
I also want to link to this article. It is safe to create a query text dynamically if you control all parts of that query. And it's much better for RDBMS than some smart logic in query.

How to correctly insert a parameter into an existing sql query to avoid SQL Injections

I have seen some answers already but my query is a little bit different:
Here is an original query:
cmd.CommandText = "select count(Table1.UserID) from Table1 INNER JOIN
Table2 ON Table1.ID = Table2.ID where Table1.Userid = " + UserID + " and
Table1.Number != '" + Number +"' and Table2.ID < 4";
Here is a modified query for SQL Injections:
cmd.CommandText = "select count(Table1.UserID) from Table1 INNER JOIN
Table2 ON Table1.ID = Table2.ID where Table1.Userid = #userId and
Table1.ID != #Number and Table2.ID < 4";
If you can notice, the first query has UserId surrounded by double quotes: ..." + UserID +"... and Number us surrounded by single and double quotes: ...'" + Number + "'...
Here is how I'm setting parameters:
cmd.CommandType = CommandType.Text;
cmd.Parameters.AddWithValue("#Number", Number);
cmd.Parameters.AddWithValue("#userId",UserID);
where UserID is an integer and Number is a string.
So, my question is, if the modified query formatted the right way? Is there any difference how to put #UserId and #Number parameters into a query considering the different ways they are specified in the original query?
I have been working on .net Mvc for a long time, and I can ensure you the parameters are correctly fixed by yourself in the second case, and you do not need to worry. By the way you can still debug and test if you can inject yourself. Briefly, your code looks great and invulnerable.
This is how i do it, which is similar and also as safe as yours:
string Query = #"select a1, a2, a3, a4 from table1 where a1 in
(select b1 from table2 where b2 = #start or b2 = #end)";
using (SqlCommand Comm = new SqlCommand(Query, Conn))
{
Comm.Parameters.Add("#start", SqlDbType.NVarChar).Value = start;
Comm.Parameters.Add("#end", SqlDbType.Int).Value = end;
}
In your initial query, the double quotes belonged to the actual text of the query, not the parameter. The single quotes you would add when appending a string into the sql query. I do not know why you would put single quotes around something called Number. If in fact that is a numeric type variable, it can go into the query without the single quotes. But if it has single quotes, the only thing that happens is that Sql sees it as a string, and then converts it to a number if it is going to use it as one. For example, if Table1.Number is numeric.
But, as you have noted, building your query string by appending your parameters into your query string is terrible practice as it opens the door, wide open, for sql injection attacks. So, you go with parameterized queries, as you have.
In parameterized queries, you do not worry about quotes. For parameters that are string values, the environment will worry about encasing them in quotes as it builds the command to pass to your sql db. For parameters that are numeric, quotes are not needed, and again, that is taken care of for you.
I think your 2nd version of the query is much better and from the looks of it, it should work just fine.
Adding parameters instead of concatenating your values is much safer against sql injection. And in this example, and I can't see any way to do a sql injection.
EDIT
When using parametrised queries, you dont need to add any quotes, just like when you declare a variable and use it in a query - you dont need to use quotes.
DECLARE #x CHAR(10) = 'abc'
SELECT #x
When using concatenation of values inside a query, if the value you're trying to add into the query is a CHAR, you need to wrap it between single quotes. If it's an INT, it shouldn't be wrapped between single quotes.
SELECT 'abc', 1
The double quotes you have in your first query dont have anything to do with the sql statement, they are used in your c# code to build the sql statement string you're trying to assign to CommandText.
string abcVar = "abc";
int intVar = 1;
string sqlCommand = "SELECT '" + abcVar + "', " + intVar;

Avoid COUNT to CAST to BIGINT

Using Open JPA 2.0, Database is DB2 9.7. For query like SELECT COUNT(1) FROM USER WHERE FNAME := fname, JPA is converting the query to SELECT COUNT(CAST(? AS BIGINT)) FROM TABLENAME.
How to avoid the CAST to BIGINT?
Code sample below:
query = entityManager.createNamedQuery("qry.checkuser");
query.setParameter("fname", fname);
Long count = (Long)query.getSingleResult();
Which one is the problem in the CAST?
I think you can't avoid it since is SQL generated by the JPA provider.
BTW, I allways use Number super class instead of specific subclass:
query = entityManager.createNamedQuery("qry.checkuser", Number.class);
query.setParameter("fname", fname);
Number count = query.getSingleResult();
// Do whatever is needed
if (count.longValue()...
This way there's no problem if the JPA provider returns a integer, long or BigXXXX.
What worked for me is:
SELECT COUNT(USER_ID) FROM USER WHERE FNAME := fname
Basically we need to use a non nullable column like Primary key column and with this change the CAST can be avoided which consumes additional CPU, a minor gain.

JPA Native Query

I am trying to execute a native query and pass run-time parameters and get the result as a List. When I try to process the Object [], one of the columns fetched is a String. But it comes out as java.lang.Character instead of String. Here is the query below:
SELECT CASE
WHEN (TRUNC(abm.credit_card_expiration_date) BETWEEN trunc(SYSDATE) AND
trunc(last_day(SYSDATE))) THEN
'Expires'
ELSE
'Expired'
END EXP_STATUS,
TO_CHAR(abm.credit_card_expiration_date, 'MM/YY') EXP_DATE
FROM account_billing_methods abm
WHERE abm.account_id = 201103
AND abm.billing_type_id = 1
AND TRUNC(abm.credit_card_expiration_date) <= TRUNC(LAST_DAY(SYSDATE))
The EXP_STATUS column could not be typecasted into String as it is of type Character. Any ideas of why it does not work?
Regards,
-Anand
I had the same problem and changed the select clause of my query to:
EXP_STATUS || '' as EXP_STATUS
Then it is a VARCHAR instead of a CHAR and JPA will return it as a String instead of a Character.
If someone knows a better/more elegant solution, I would appreciate if you could share it.