Spring Data JPA JSONB Paramaterization - postgresql

What is the correct syntax (JPA, Spring Data, or SpEL) to convert this query into a Spring Data Repository nativeQuery?
SELECT *
FROM mytable
WHERE f_jsonb_arr_lower(myjsonb -> 'myArray', 'subItem', 'email')
#> '"foo#foo.com"';
I want to use an input parameter instead of hard-coding "foo#foo.com".
My model: Postgres myTable with a JSONB column myJsonb:
{
"myArray": [
{
"subItem": {
"email": "bar#bar.com"
}
},
{
"subItem": {
"email": "foo#foo.com"
}
}
]
}
Index described here.
The hard-coded version works:
#Query(value =
"SELECT m.* " +
" FROM mytable AS m " +
" WHERE f_jsonb_arr_lower(myjsonb -> 'myArray' ,'subItem', 'email') " +
" #> '\"foo#foo.com\"' " +
" ORDER BY ?#{#pageable} ",
// Spring Data nativeQueries with Pageable require a separate countQuery:
countQuery =
"SELECT count(m.id) " +
" FROM mytable AS m " +
" WHERE f_jsonb_arr_lower(myjsonb -> 'myArray' ,'subItem', 'email') " +
" #> '\"foo#foo.com\"' ",
nativeQuery = true)
Page<MyTableEntity> findAllHardcodedPageable(Pageable pageable);
But trying to leverage the lowercaseEmailAddress parameter in a Spring Data repository nativeQuery does not work:
#Query(value =
"SELECT m.* " +
" FROM mytable AS m " +
" WHERE f_jsonb_arr_lower(myjsonb -> 'myArray' ,'subItem', 'email') " +
" #> '\"?{lowercaseEmailAddress}\"' " +
" ORDER BY ?#{#pageable} ",
countQuery =
"SELECT count(m.id) " +
" FROM mytable AS m " +
" WHERE f_jsonb_arr_lower(myjsonb -> 'myArray' ,'subItem', 'email') " +
" #> '\"?{lowercaseEmailAddress}\"' ",
nativeQuery = true)
Page<MyTableEntity> findAllByEmailPageable
(String lowercaseEmailAddress, Pageable pageable);
In my Postgres query logging, I can see that the lowercaseEmailAddress parameter is never set:
LOG: execute S_2: COMMIT
LOG: execute S_3: BEGIN
LOG: execute <unnamed>: SELECT count(m.id) FROM mytable
AS m WHERE f_jsonb_arr_lower(myjsonb -> 'myArray',
'subitem', 'email') #> '"?1"'
LOG: execute S_11: ROLLBACK

Found the answer:
1) Pass only a double-quoted String to the spring data repository method:
String emailAddressWithDoubleQuotes = String.format("\"%s\"",emailAddress);
result = repository.findAllByEmailPageable(emailAddressWithDoubleQuotes, pageRequest).getContent();
2) The Spring Repository #Query needs to have the SpEL expression in parenthesis and be casted to jsonb:
static final String FIND_ALL_BY_EMAIL_QUERY = " FROM mytable AS m " +
" WHERE f_jsonb_arr_lower(metadata -> 'myArray', 'subItem', 'email') " +
" #> ( ?#{#lowercaseEmailAddress} )\\:\\:jsonb";
#Query( // only use 'ORDER BY #pageableWithNativeSort' on 'value' query:
value = "SELECT m.* " + FIND_ALL_BY_EMAIL_QUERY + " ORDER BY ?#{#pageableWithNativeSort} ",
// Spring Data nativeQueries with Pageable require a separate 'countQuery':
countQuery = "SELECT count(m.id) " + FIND_ALL_BY_EMAIL_QUERY,
nativeQuery = true)
Page<OrderEntity> findAllBysubItemEmail(
#Param("lowercaseEmailAddress") String lowercaseEmailAddress,
#Param("pageableWithNativeSort") Pageable pageableWithNativeSort);

Related

JPQL: How to rewrite postgres native query to JPQL query that uses filter keyword

Im trying to avoid using native query. I have this query that uses the filter function, how could I rewrite this to not use that and work in regular jpql?
#Query(
"SELECT time_bucket(make_interval(:intervalType), d.time) as groupedDate, " +
"CAST(d.team_Id as varchar) as teamId, CAST(d.service_Id as varchar) as serviceId, CAST(d.work_id as varchar) as workId, " +
"ROUND(CAST(count(d.value) filter ( where d.type = 'A') AS numeric) /" +
" (CAST(count(d.value) filter ( where d.type = 'B') AS numeric)), 4) as total " +
"FROM datapoint d " +
"WHERE d.team_Id = :teamId and d.service_id in :serviceIds and d.work_id = :workspaceId and d.type in ('A', 'B') " +
"AND d.time > :startDate " +
"GROUP BY groupedDate, d.team_Id, d.service_Id, d.workspace_Id " +
"ORDER BY groupedDate DESC",
nativeQuery = true
)
in the FROM statement you have to use the DAO object instead of the table name

JpaSystemException: No Dialect mapping for JDBC type: 1111

I have viewed some of the related question but the solutions on them are bit older. Most of them are using EntityManager. I have written the following native query in JpaRepository and when I get geojson as a String I'm getting the error mentioned in the title.
Here is my query
#Query(value = "SELECT\n"
+ " json_build_object(\n"
+ " 'type', 'FeatureCollection',\n"
+ " 'features', json_agg(\n"
+ " json_build_object(\n"
+ " 'type', 'Feature',\n"
+ " 'geometry', ST_AsGeoJSON(a.check_in_geom)\\:\\:json,\n"
+ " 'properties', json_build_object(\n"
+ " 'username', a.username,\n"
+ " 'users', (\n"
+ " -- Generate json array of \"users\"\n"
+ " SELECT array_to_json(array_agg(u.*)) \n"
+ " FROM users u \n"
+ " WHERE u.username = a.username\n"
+ " GROUP BY u.username\n"
+ " )\n"
+ " )\n"
+ " )\n"
+ " )\n"
+ " ) Json\n"
+ "FROM\n"
+ " attendances a, users u\n"
+ " WHERE a.username = u.username AND u.designation = 'Manager' AND date_trunc('day', a.created_at)\\:\\:DATE = '2022-04-04'"
+ " ;", nativeQuery = true)
String getAttendanceGeoJsonByDesignationAndDate(String designation, String dateStr);
For now I'm passing designation and date as static values.
When I run this query on Postgresql it runs successfully and return the expected geojson. But spring boot does not allow the result to be as String or it throws exception while running the query.
Please let me know how can I fix it. Also if I need to provide more detail, do let me know.

How to get results of select query in postgresql r2dbc?

I can execute this query just fine but I don't know how to extract the information returned by the select statement.
Flux<PostgresqlResult> checkTableQuery = connectionMono.flatMapMany(connection -> connection
.createStatement("select exists(\n" +
" select table_name from information_schema.tables\n" +
" where table_name='sequence1'\n" +
");")
.execute());
checkTableQuery.subscribe();
Flux<PostgresqlResult> checkTableQuery = connectionMono.flatMapMany(connection -> connection
.createStatement("select exists(\n" +
" select table_name from information_schema.tables\n" +
" where table_name='sequence1'\n" +
");")
.execute())
.flatmap(result -> result.map(
(row,rowMetadata) -> row.get("exists",Boolean.class)));
checkTableQuery.subscribe();

Transforming complex postgres query into JPQL for Spring Hibernate repository

I have the following Postgres SQL query, in order to get alerts with the latest date:
SELECT latest_alerts.subject_id,
latest_alerts.alertconfiguration_id_id,
latest_alerts.alert_level,
latest_alerts.maxdate
FROM (SELECT subject_id,
alertconfiguration_id_id,
alert_level,
Max(date) AS maxdate
FROM alert
WHERE subject_id IN ( 'da157532-8de5-4c0c-8608-d924e670d5db', '63b99886-77c8-4784-b8f0-7ff5310f1272' )
AND alertconfiguration_id_id IN (
'6feb6b8b-6b96-4d5d-ac58-713b3cd637a0'
)
GROUP BY subject_id,
alertconfiguration_id_id,
alert_level) AS latest_alerts
INNER JOIN alert
ON alert.date = latest_alerts.maxdate
AND alert.subject_id = latest_alerts.subject_id
AND alert.alertconfiguration_id_id =
latest_alerts.alertconfiguration_id_id
AND alert.alert_level IN ( 'WARNING' )
ORDER BY latest_alerts.maxdate DESC;
This runs well on the postgres database generated by Hibernate. Note the odd id_id construction is because of an embedded key.
But I'm struggling to transform this into a JPA/JPQL query that I can use in a Spring Boot application. So far I have this:
#Query("SELECT" +
" latest_alerts.subject," +
" latest_alerts.alertConfiguration," +
" latest_alerts.alertLevel," +
" latest_alerts.max_date" +
"FROM" +
" (SELECT" +
" subject," +
" alertConfiguration," +
" alertLevel," +
" MAX(date) AS max_date" +
" FROM" +
" alert" +
" WHERE" +
" subject.id IN (:subjectIds) AND alertConfiguration.id.id IN (:alertConfigurationIds)" +
" GROUP BY" +
" subject, alertConfiguration, alertLevel) AS latest_alerts" +
" INNER JOIN" +
" alert" +
" ON" +
" alert.date = latest_alerts.max_date" +
" AND alert.subject = latest_alerts.subject" +
" AND alert.alertConfiguration = latest_alerts.alertConfiguration" +
" AND alert.alertlevel IN (:alertLevels)" +
" ORDER BY latest_alerts.date DESC")
Page<Alert> findLatest(#Param("subjectIds") List<UUID> subjectIds,
#Param("alertConfigurationIds") List<UUID> alertConfigurationIds,
#Param("alertLevels") List<AlertLevel> alertLevels,
Pageable pageable);
But Hibernate doesn't understand what to do with this to the point where it actually throws a nullpointer while parsing this query.
Caused by: java.lang.IllegalArgumentException: Validation failed for query for method public abstract org.springframework.data.domain.Page ournamespace.sense.repository.AlertRepository.findLatest(java.util.List,java.util.List,java.util.List,org.springframework.data.domain.Pageable)!
at org.springframework.data.jpa.repository.query.SimpleJpaQuery.validateQuery(SimpleJpaQuery.java:93)
at org.springframework.data.jpa.repository.query.SimpleJpaQuery.<init>(SimpleJpaQuery.java:63)
... many more
Caused by: java.lang.NullPointerException
at org.hibernate.hql.internal.antlr.HqlBaseParser.identPrimary(HqlBaseParser.java:4355)
at org.hibernate.hql.internal.antlr.HqlBaseParser.primaryExpression(HqlBaseParser.java:993)
at org.hibernate.hql.internal.antlr.HqlBaseParser.atom(HqlBaseParser.java:3549)
Any idea if this kind of query is even possible? With a nullpointer thrown by Hibernate it's a bit hard to see what part of the query is the problem.
Cannot do it with Hibernate. From the documentation:
Note that HQL subqueries can occur only in the select or where
clauses.
But you can use native query:
#Query(
value = "SELECT latest_alerts.subject_id,
latest_alerts.alertconfiguration_id_id,
latest_alerts.alert_level,
latest_alerts.maxdate ...
FROM ...",
nativeQuery = true)
Page<Alert> findLatest(#Param("subjectIds") List<UUID> subjectIds,
#Param("alertConfigurationIds") List<UUID> alertConfigurationIds,
#Param("alertLevels") List<AlertLevel> alertLevels,
Pageable pageable);

An identification variable must be provided for a range variable declaration

I'm trying to use this query in my jpa but it doesn't work:
List<Object[]> query = em.createQuery("SELECT Tstat.idStatistiques, TL.codeLieu, TL.materiel, TL.zone, sum(Tstat.colis) as colis, Tstat.defaut, sum(Tstat.nbreDefaut) as nbreDefaut,"
+ " sum(Tstat.nonLu) as nonLu, sum(Tstat.multiple) as multiple, sum(Tstat.nonRecu) as nonRecu, sum(Tstat.incoherent) as incoherent, sum(Tstat.requete) as requete , "
+ "sum(Tstat.tempsFonctionnement) as tempsFonctionnement, SUM(Tstat.tempsUtilisation) as tempsUtilisation, Tstat.modeFonctionnement FROM "
+ "( SELECT CURRENT_DATE as horodatage, St.idStatistiques, St.colis, St.defaut, St.nbreDefaut, St.nonLu, St.requete, St.multiple, St.nonRecu, St.incoherent, St.tempsFonctionnement, St.tempsUtilisation, St.modeFonctionnement FROM Statistique St )"
+ " UNION "
+ "(SELECT h.horodatage, h.idStatistiques, h.colis, h.defaut, h.nbreDefaut, h.nonLu, h.nonRecu, h.requete, h.multiple, h.incoherent, h.tempsFonctionnement, h.tempsUtilisation, h.modeFonctionnement FROM Statistiqueshisto h )"
+ " Tstat "
+ "LEFT JOIN (SELECT * FROM Lieux) as TL on Tstat.idStatistiques = TL.code_VI WHERE idStatistiques like :A ").setParameter("A", 0040+"%").getResultList();
This gives me error
The expression is invalid, which means it does not follow the JPQL
grammar.