QueryDSL and SQL Function from Postgres - postgresql

I would like to use Postgres FullText search with QueryDsl JPA.
Generated in SQL:
select *
from film
where to_tsquery ('tarzan') ## to_tsvector('french',film.title) = true
to get all film containing tarzan in their title.
In JPA I define a custom function 'ftsMatch' which I could use like that:
String jpql = "select film from Film film where ftsMatch(:queryString, film.titre) = true";
I QueryDSL I would like to have an opportunity to define a predicate on String type :
QFilm.film.titre.ftsMatch('tarzan')
I haven't found any solution

What I would like to do is to extends the com.querydsl.core.types.dsl.StringExpression.class
and add a custom function fullTextMatch() which could be used like :
BooleanBuilder booleanBuilder = new BooleanBuilder(QFilm.film.titre.fullTextMatch(_titre, "french"));
it would turn into SQL :
select film0_.id as id1_2_ .. from film film0_
where to_tsquery (?) ## to_tsvector('pg_catalog.french',film0_.titre)=true
I haven't found how to get the QueryDSL syntax above, but found the following solution:
1/ define Custom Dialect for Postgres
and register the Customm function on this dialect :
public class CustomFullTextPostgresDialect extends PostgreSQL94Dialect {
public CustomFullTextPostgresDialect() {
registerFunction("ftsMatch", new PostgreSQLFullTextSearchFunction());
}
}
2/ Code the custom function PostgreSQLFullTextSearchFunction implementing org.hibernate.dialect.function.SQLFunction
This function 'ftsMacth' will generate the SQL :
String fragment = " to_tsquery (" + value + ") ## to_tsvector(" + ftsConfig + "," + field + ")";
This step give me access to Posgres FullText in JPA :
String jpql = "select film from Film film "
+ "where FUNCTION( 'ftsMatch', :titre,'pg_catalog.french', film.titre) = true";
TypedQuery<Film> typedQuery = em.createQuery(jpql, Film.class);
typedQuery.setParameter("titre", _titre);
List<Film> list = typedQuery.getResultList();
3/Use QueryDsl to relay to the custom function defined on the extended postgres Dialect :
BooleanTemplate predicate = Expressions
.booleanTemplate("FUNCTION('ftsMatch', {0},'pg_catalog.french', film.titre) = true ", _titre);
Page<Film> page = filmRepository.findAll(predicate, _pageable);
But with this QueryDSL solution, I still need the Hibernate customization. And the syntax is no longer DSL oriented

Related

how to use a placeholder syntax for pattern matching using like operator in PostgreSQL with Spring Data JPA

I am using Spring Data JPA with postgresql as data base.Want to search a pattern using user given keyword
#Query(value = "SELCET * FROM table_name WHERE column_name LIKE (CONCAT('%',:pattern,'%'))")
List<Class> findPattern(String pattern);
but :pattern is not replacing with pattern value
You may try binding the entire LIKE expression to a single placeholder:
#Query(value = "SELECT * FROM table_name WHERE column_name LIKE :pattern")
List<Object[]> findPattern(String pattern);
The usage would be something like this:
String value = "blah";
String pattern = "%" + value + "%";
List<Object[]> rs = repo.findPattern(pattern);
Since like %pattern% is the same as contains pattern, your use case doesn't even need a query coded:
List<MyClass> findByColumnNameContaining(String pattern);

How convert java type to domain of postgres with hibernate(springData)?

i created domain in postgres:
create domain arrayofids as numeric[];
Now i want to use the domain in spring data like this:
String fakeQuery = "unnest(CAST (:ids AS arrayofids))";
Query nativeQuery = entityManager.createNativeQuery(fakeQuery);
BigInteger[] arrayOfids = new BigInteger[] {new BigInteger("1"),new BigInteger("2)} //or It can be List. It is not important
nativeQuery.setParameter("ids", arrayOfids);
List resultList = nativeQuery.getResultList();
Of course i get Error:
org.postgresql.util.PSQLException: ERROR: cannot cast type bytea to arrayofIds
Before i used https://dalesbred.org/docs/api/org/dalesbred/datatype/SqlArray.html and it worked fine or did custom types in JDBC myself. Hibernate doesn't allow use my domain easy.
Query like that:
select * from mtTbale where id in :ids
is not interested. I should use the domain with unnest and CAST
Make :ids as a text representation of an array of numbers, i.e. "{1, 2}" and add SELECT to the query.
Try this:
String fakeQuery = "SELECT unnest(CAST (:ids AS arrayofids))";
Query nativeQuery = entityManager.createNativeQuery(fakeQuery);
String arrayOfids = "{" + 1 + ", " + 2 + "}"; // obtain the string as is relevant in your case
nativeQuery.setParameter("ids", arrayOfids);
List resultList = nativeQuery.getResultList();
and the second query should look like this:
select * from mtTbale where id = ANY(CAST(:ids AS arrayofids));
You may also use Postgres shorthand (and more readable) cast syntax.
Instead of CAST(:ids AS arrayofids) use :ids::arrayofids.

"Column count does not match" for h2 database Select query using projection in Spring-Boot #DataJpaTest

I have a Spring-Boot app that has some native queries that use content projection. It runs Postgres in production and works fine. I'm trying to set up integration tests for the repositories using #DataJpaTest and a h2 in-memory database, but my queries that are using content projection are failing with a JdbcSQLException out of the driver:
org.h2.jdbc.JdbcSQLException: Column count does not match
I successfully save to the TestEntityManager, so there are records in the database, but I am unable to invoke the SELECT via the repository method. It works properly in production on Postgres -- is this a limitation to h2 and is there a workaround I could apply so I can properly test this?
The repository method looks like this (one inner join, two params in the where clause, table names and columns changed to protect the guilty):
public interface OrderRepository extends PagingAndSortingRepository<Order, Long> {
#Query(nativeQuery = true,
value = "SELECT order.id, order.total, pizza.name " +
"FROM example.order " +
"INNER JOIN example.pizza USING (pizza_id) " +
"WHERE order.customer_id = :custId " +
"AND order.order_date = :orderDate ",
countQuery = "SELECT count(order.id) " +
"FROM example.order " +
"INNER JOIN example.pizza USING (pizza_id) " +
"WHERE order.customer_id = :custId " +
"AND order.order_date = :orderDate")
<T> Page<T> findAllByCustIdAndOrderDate(String custId, OffsetDateTime orderDate, Pageable paging, Class<T> type);
}
And the projection looks like this:
public interface PizzaOrderProjection {
Long getId();
Double getTotal();
String getName();
}
The exception triggers when I call findAllByCustIdAndOrderDate, and the SQL statement that it prints is causing it is the SELECT. The SELECT it prints looks perfectly normal:
Hibernate:
/* dynamic native SQL query */ SELECT
order.id,
order.total,
pizza.name
FROM
example.order
INNER JOIN
example.pizza USING (pizza_id)
WHERE
order.customer_id = ?
AND order.order_date = ? limit ?
2019-04-09 12:42:18.704 WARN 17568 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 21002, SQLState: 21S02
2019-04-09 12:42:18.708 ERROR 17568 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Column count does not match; SQL statement:
It turns out that the error message actually has nothing to do with the underlying issue.
The H2 database does not support the using keyword on the inner join clause, only the on keyword.
The issue was resolved by changing the inner join to use on instead, like this:
public interface OrderRepository extends PagingAndSortingRepository<Order, Long> {
#Query(nativeQuery = true,
value = "SELECT order.id, order.total, pizza.name " +
"FROM example.order " +
"INNER JOIN example.pizza ON order.pizza_id = pizza.pizza_id " +
"WHERE order.customer_id = :custId " +
"AND order.order_date = :orderDate ",
countQuery = "SELECT count(order.id) " +
"FROM example.order " +
"INNER JOIN example.pizza ON order.pizza_id = pizza.pizza_id " +
"WHERE order.customer_id = :custId " +
"AND order.order_date = :orderDate")
<T> Page<T> findAllByCustIdAndOrderDate(String custId, OffsetDateTime orderDate, Pageable paging, Class<T> type);
}
This change makes the queries valid in both postgres and h2.

Calling StoredProcedure using JPA in Spring Boot

I am trying to call a stored procedure, which is built in mysql, in my Spring boot app using JPA. My stored procedure returns the result which cant be contain in single model as it fetches data from combination of tables.
I can do this with "call " but i guess that is not JPA's way. COuld you please let me know what is the best way to do it?
In case you're using plain JPA you need to do a native query call. Something like below.
Query q = em.createNativeQuery("select my_store_pro(?, ?)");
List<Object[]> results = q.getResultList();
for (Object[] a : results) {
System.out.println("result " + a[0] + " " + a[1]);
}
If you're using Spring Data repositories then you want something like below.
#Query(nativeQuery = true, value = "select my_store_pro(?, ?)")
Date callMyStoreProc(int val1, int val2);

Injecting JSON parameter in nativeQuery of Spring JPA

I have a table with a JSONB column. In my project I am using Spring JPA and to query that column I want to use nativeQuery. My problem is to inject varibale into the query like below:
#Query(nativeQuery = true, value = "SELECT * FROM items WHERE item_json -> 'attributes' #> '{\"Param1\": \"" + :param + "\"}' ")
List<Items> findByParameter(#Param("param") String param);
The above query does not work as param is not considered as JPA parameter. I am wondering if anyone knows how to do this? or I should do it in another way?