Skip to content

Commit

Permalink
[#566] Avoid NPE during rendering of parameters as literals that don'…
Browse files Browse the repository at this point in the history
…t have a converter and update documentation and CHANGELOG
  • Loading branch information
beikov committed Sep 4, 2019
1 parent 364f195 commit dcf77e8
Show file tree
Hide file tree
Showing 11 changed files with 86 additions and 54 deletions.
22 changes: 19 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,31 @@ Not yet released

### New features

None
* Introduce support for adding whole predicates to predicate builders via `whereExpression`/`whereExpressionSubqueries` etc.
* Add support for subquery expression in the `ON` clause
* Introduce new comparison methods in predicate builders to allow making use of literal rendering in a type safe way
* Introduce new `*-jar` artifacts for modules to allow using Blaze-Persistence with older JVMs/ASM versions
* Shade ANTLR into parser to avoid conflicts
* Update ANTLR to 4.7.2
* Rework the parser grammar for better performance and to get rid of semantic predicates
* Introduce possibility to check if CTE type has been defined
* Add DeltaSpike 1.9 and Java 12 build support
* Introduce method to allow controlling the amount of extracted keyset objects
* Pass optional parameters to entity view `CTEProvider`
* Add support for window functions and named windows in builder and expressions via e.g. `SUM(x) OVER(PARTITION BY y)`
* Improve OpenJPA support
* Add support for MySQL 8
* Move `ConfigurationProperties` classes to API artifacts

### Bug fixes

None
* Fix Hibernate Envers issues with JPA Criteria implementation
* Fix a NPE happening due to doing a manual select in an exists subquery that joins associations
* Avoid a NPE happening when trying to render a parameter as literal that has no literal representation

### Backwards-incompatible changes

* Dropped module classified artifacts in favor of adding `module-info.class` file to standard artifacts requires e.g. Jandex 2.0.4.Final or later
* Dropped module classified artifacts in favor of adding `module-info.class` file to standard artifacts which now requires e.g. Jandex 2.0.4.Final or later

## 1.4.0-Alpha1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,9 @@ protected String getLiteralParameterValue(ParameterExpression expression) {

if (value != null) {
final TypeConverter<Object> converter = (TypeConverter<Object>) TypeUtils.getConverter(value.getClass());
return converter.toString(value);
if (converter != null) {
return converter.toString(value);
}
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.*;

import com.blazebit.lang.StringUtils;
import com.blazebit.persistence.ConfigurationProperties;
import com.blazebit.persistence.JoinType;
import com.blazebit.persistence.spi.CriteriaBuilderConfiguration;
import com.blazebit.persistence.spi.DbmsDialect;
Expand Down Expand Up @@ -70,7 +71,7 @@ protected CriteriaBuilderConfiguration configure(CriteriaBuilderConfiguration co

dbms = config.getEntityManagerIntegrators().get(0).getDbms(emf);
if ("postgresql".equals(dbms)) {
config.setProperty("com.blazebit.persistence.returning_clause_case_sensitive", "false");
config.setProperty(ConfigurationProperties.RETURNING_CLAUSE_CASE_SENSITIVE, "false");
}

return config;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,21 @@

package com.blazebit.persistence.testsuite;

import static com.googlecode.catchexception.CatchException.verifyException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.Calendar;
import java.util.Date;
import java.util.Set;
import com.blazebit.persistence.CriteriaBuilder;
import com.blazebit.persistence.testsuite.entity.Document;
import com.blazebit.persistence.testsuite.entity.DocumentType;
import org.junit.Assert;
import org.junit.Test;

import javax.persistence.Parameter;
import javax.persistence.TemporalType;
import javax.persistence.Tuple;
import java.util.Calendar;
import java.util.Date;
import java.util.Set;

import org.junit.Assert;
import org.junit.Test;

import com.blazebit.persistence.CriteriaBuilder;
import com.blazebit.persistence.testsuite.AbstractCoreTest;
import com.blazebit.persistence.testsuite.entity.Document;
import static com.googlecode.catchexception.CatchException.verifyException;
import static org.junit.Assert.*;

/**
*
Expand Down Expand Up @@ -164,4 +160,15 @@ public void testRenderParameterAsLiteral() {

assertEquals("SELECT 'test' FROM Document d", cb.getQueryString());
}

// Test for #566
@Test
public void testRenderEnumParameterAsLiteral() {
CriteriaBuilder<Document> cb = cbf.create(em, Document.class);
cb.from(Document.class, "d")
.select(":param");
cb.setParameter("param", DocumentType.NOVEL);

assertEquals("SELECT :param FROM Document d", cb.getQueryString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ The operators can't be used for date arithmetic. Instead the _date diff function

=== Function expressions

{projectname} decided to only allow the standard JPQL and some extension functions to be used directly with the function call syntax `FUNCTION_NAME ( (args)* )`.
Non-standard functions have to be used via the `FUNCTION ( function_name (, args)* )` syntax that got introduced in JPA 2.1.
{projectname} supports a direct function call syntax `FUNCTION_NAME ( (args)* )` for all functions and translates that to the JPA provider specific syntax.
Non-standard functions may also use the `FUNCTION ( function_name (, args)* )` syntax that got introduced in JPA 2.1 and are handled equally.

==== String functions

Expand Down Expand Up @@ -577,7 +577,7 @@ SELECT SUM(cat.age)
FROM Cat cat
----

`FUNCTION ( 'GROUP_CONCAT' (, 'DISTINCT' )? , string (, 'SEPARATOR', separatorString)? (, 'ORDER BY', ( orderByExpr, ( 'ASC' | 'DESC' ) )+ ) )`::
`GROUP_CONCAT ( ('DISTINCT' )? , string (, 'SEPARATOR', separatorString)? (, 'ORDER BY', ( orderByExpr, ( 'ASC' | 'DESC' ) )+ ) )`::
Concatenates elements to a single string connected with the `separatorString` in the requested order.

WARN: This is a non-standard function that might not be supported on all DBMS. See <<group_concat-function,JPQL functions>> for further information.
Expand All @@ -586,12 +586,12 @@ WARN: This is a non-standard function that might not be supported on all DBMS. S
----
CriteriaBuilder<String> cb = cbf.create(em, String.class)
.from(Cat.class, "cat")
.select("FUNCTION('GROUP_CONCAT', cat.name, 'SEPARATOR', ' - ', 'ORDER BY', cat.name, 'ASC')");
.select("GROUP_CONCAT(cat.name, 'SEPARATOR', ' - ', 'ORDER BY', cat.name, 'ASC')");
----

[source,sql]
----
SELECT FUNCTION('GROUP_CONCAT', cat.name, 'SEPARATOR', ' - ', 'ORDER BY', cat.name, 'ASC')
SELECT GROUP_CONCAT(cat.name, 'SEPARATOR', ' - ', 'ORDER BY', cat.name, 'ASC')
FROM Cat cat
----

Expand Down Expand Up @@ -623,11 +623,10 @@ Normally, users shouldn't get in touch with this directly. It is currently used

==== Function function

As of JPA 2.1 it is possible to invoke non-standard functions via the `FUNCTION ( function_name (, args)* )`. {projectname} not only has support for that,
but prefers this syntax for non-standard functions over a proprietary syntax. The reason for that is mainly that the JPQL rendered by {projectname} stays directly executable, even if proprietary functions are used.
As of JPA 2.1 it is possible to invoke non-standard functions via the `FUNCTION ( function_name (, args)* )`.

By default, all non-standard functions of the JPA provider are imported. This means that you can make use of functions provided by the JPA provider with the `FUNCTION ( function_name (, args)* )` syntax
and it will get rendered into the JPA provider specific way of invoking such functions automatically.
as well as with the direct function call syntax `function_name ( args* )` and it will get rendered into the JPA provider specific way of invoking such functions automatically.

A list of functions provided by {projectname} and information on how to implement a custom function can be found in the <<JPQL functions>> chapter.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ PagedList<Cat> page3 = cbf.create(em, Cat.class)
[.Count query]
[source,sql]
----
SELECT COUNT(*), FUNCTION('PAGE_POSITION',(
SELECT COUNT(*), PAGE_POSITION((
SELECT _page_position_cat.id
FROM Cat _page_position_cat
GROUP BY _page_position_cat.id, _page_position_cat.birthday
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,7 @@ CriteriaBuilder<Cat> cb =
[source,sql]
----
SELECT cat FROM Cat cat WHERE cat.id IN (
FUNCTION(
'SET_UNION_ALL',
SET_UNION_ALL(
(SELECT cat1.id FROM Cat cat1 WHERE cat1.name = :param_1),
(SELECT cat2.id FROM Cat cat2 WHERE cat2.name = :param_2),
(SELECT cat3.id FROM Cat cat3 WHERE cat3.name = :param_3)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

JPQL offers many built-in functions as you can see in the <<string-functions,expressions>> chapter and as of JPA 2.1 has a syntax for calling database specific functions.
Normally when using the function syntax `FUNCTION ( function_name (, args)* )`, the JPA provider puts a function call into the SQL like `function_name ( args* )`.
Instead of simply passing through the function invocation to the SQL, {projectname} decided to reuse the function syntax to allow calling so called JPQL functions.
Instead of simply passing through the function invocation to the SQL, {projectname} decided to reuse the function syntax but also allows the direct function call syntax `function_name ( args* )`
to allow calling custom `JpqlFunction` implementations that can be registered.

JPQL functions are registered at configuration time and are integrated into a persistence unit, so you could even use the functions by using the JPA provider specific invocation syntax directly.
Except for a few special built-in functions, every function has DBMS specific implementations of the link:{core_jdoc}/persistence/spi/JpqlFunction.html[`JpqlFunction`] interface
Expand Down Expand Up @@ -54,7 +55,7 @@ CriteriaBuilder<Tuple> cb = cbf.create(em, Tuple.class)

[source,sql]
----
SELECT cat.name, FUNCTION('COUNT_TUPLE', kittens_1.id)
SELECT cat.name, COUNT_TUPLE(kittens_1.id)
FROM Cat cat
LEFT JOIN cat.kittens kittens_1
GROUP BY cat.id, cat.name
Expand Down Expand Up @@ -102,7 +103,7 @@ Every of the following functions has to be invoked with the JPA 2.1 function syn

==== COUNT_TUPLE function

Syntax: `FUNCTION ( 'COUNT_TUPLE' (, 'DISTINCT' )?, args+ )`
Syntax: `COUNT_TUPLE ( ('DISTINCT')?, args+ )`

The `COUNT_TUPLE` function is like the regular `COUNT` function, but in addition allows to do distinct counting on multiple and embeddable attributes.
Some JPA providers ignore that some DBMS don't support distinct counts on multiple columns and generate broken SQL.
Expand Down Expand Up @@ -147,7 +148,7 @@ By doing a distinct count on the resulting string, the ANSI SQL distinct countin

==== CAST functions

Syntax: `FUNCTION ( 'CAST_XXX', argument (, sqlCastTypeOverride)? )`
Syntax: `CAST_XXX ( argument (, sqlCastTypeOverride)? )`

There are multiple different cast functions for different data types.

Expand Down Expand Up @@ -175,18 +176,18 @@ By providing a <<customize-dbms-dialect,custom DBMS dialect>> you can override t
----
CriteriaBuilder<String> cb = cbf.create(em, String.class)
.from(Cat.class, "cat")
.select("FUNCTION('CAST_STRING', cat.age)");
.select("CAST_STRING(cat.age)");
----

[source,sql]
----
SELECT FUNCTION('CAST_STRING', cat.age)
SELECT CAST_STRING(cat.age)
FROM Cat cat
----

==== TREAT functions

Syntax: `FUNCTION ( 'TREAT_XXX', argument )`
Syntax: `TREAT_XXX ( argument )`

WARNING: This function is used internally and no user should ever have the need for this!

Expand Down Expand Up @@ -215,7 +216,7 @@ WARNING: This is an internal function that is used to implement the <<values-cla

==== Temporal DIFF functions

Syntax: `FUNCTION ( 'XXX_DIFF', start, end )`
Syntax: `XXX_DIFF ( start, end )`

Calculates the difference between the two given temporals like `end - start` and returning the difference in the requested unit as truncated integer.

Expand All @@ -233,7 +234,7 @@ If `end < start` i.e. the value of `end` is before `start`, the result is negati

==== Temporal extract functions

Syntax: `FUNCTION ( 'XXX', argument )`
Syntax: `XXX ( argument )`

Extracts the requested field of temporal argument like specified by the ANSI SQL `EXTRACT` expression.

Expand All @@ -249,25 +250,25 @@ The possible fields and the respective function names are:

==== GREATEST function

Syntax: `FUNCTION ( 'GREATEST', argument1, argument2 (, ...)? )`
Syntax: `GREATEST ( argument1, argument2 (, ...)? )`

Returns the greatest value of all given arguments.

==== LEAST function

Syntax: `FUNCTION ( 'LEAST', argument1, argument2 (, ...)? )`
Syntax: `LEAST ( argument1, argument2 (, ...)? )`

Returns the smallest value of all given arguments.

==== REPEAT function

Syntax: `FUNCTION ( 'REPEAT', argument1, argument2 )`
Syntax: `REPEAT ( argument1, argument2 )`

Returns a string with the _argument1_ repeated for _argument2_ times.

==== LIMIT function

Syntax: `FUNCTION ( 'LIMIT', subquery, limit (, offset )? )`
Syntax: `LIMIT ( subquery, limit (, offset )? )`

Applies the DBMS native way of doing `LIMIT` and `OFFSET` with the given values `limit` and `offset` on the given subquery.

Expand All @@ -283,7 +284,7 @@ WARNING: If you use this function directly, beware that for some DBMS it might n

==== PAGE_POSITION function

Syntax: `FUNCTION ( 'PAGE_POSITION', id_query, entity_id )`
Syntax: `PAGE_POSITION ( id_query, entity_id )`

Returns the absolute 1-based position of the entity with the given id within the result produced by the given id query.
The id query must select only the id of an entity and must be of a basic type. The entity id can be a parameter or plain value.
Expand All @@ -292,7 +293,7 @@ WARNING: This is an internal function that is used to implement link:{core_jdoc}

==== GROUP_CONCAT function

Syntax: `FUNCTION ( 'GROUP_CONCAT' (, DISTINCT)?, expression (, 'SEPARATOR', separator_expression)? (, 'ORDER BY' (, order_by_expression (, order_specification ) )+ )? )`
Syntax: `GROUP_CONCAT ( ('DISTINCT')?, expression (, 'SEPARATOR', separator_expression)? (, 'ORDER BY' (, order_by_expression (, order_specification ) )+ )? )`

Where `order_specification` is one of `'ASC'`, `'DESC'`, `'ASC NULLS FIRST'`, `'ASC NULLS LAST'`, `'DESC NULLS FIRST'`, `'DESC NULLS LAST'` and `separator_expression` by is `','` by default.

Expand All @@ -302,7 +303,7 @@ WARNING: This function might not be supported by all DBMS, so make sure your tar

===== WINDOW functions

Syntax: `FUNCTION( 'WINDOW_XXX', arguments+, (, 'FILTER BY' (, filter_by_expression)+ )? (, 'PARTITION BY' (, partition_by_expression)+ )? (, 'ORDER BY' (, order_by_expression (, order_specification ) )+ )? ((, 'RANGE' | 'ROWS' | 'GROUPS') (, ( 'UNBOUNDED PRECEDING' | ( number_expression, 'PRECEDING') | 'CURRENT ROW'), 'AND', ( 'UNBOUNDED FOLLOWING' | ( number_expression, 'FOLLOWING') | 'CURRENT ROW' ) ) )`
Syntax: `WINDOW_XXX ( arguments+, (, 'FILTER BY' (, filter_by_expression)+ )? (, 'PARTITION BY' (, partition_by_expression)+ )? (, 'ORDER BY' (, order_by_expression (, order_specification ) )+ )? ((, 'RANGE' | 'ROWS' | 'GROUPS') (, ( 'UNBOUNDED PRECEDING' | ( number_expression, 'PRECEDING') | 'CURRENT ROW'), 'AND', ( 'UNBOUNDED FOLLOWING' | ( number_expression, 'FOLLOWING') | 'CURRENT ROW' ) ) )`

Where `order_specification` is one of `'ASC'`, `'DESC'`, `'ASC NULLS FIRST'`, `'ASC NULLS LAST'`, `'DESC NULLS FIRST'`, `'DESC NULLS LAST'`.

Expand All @@ -312,6 +313,7 @@ For every aggregate function, there is a window function.
* `AVG` - `WINDOW_AVG`
* `MAX` - `WINDOW_MAX`
* `MIN` - `WINDOW_MIN`
* `COUNT` - `WINDOW_COUNT`
* `GROUP_CONCAT` - `WINDOW_GROUP_CONCAT`
* `EVERY` - `WINDOW_EVERY`
* `OR_AGG` - `WINDOW_OR_AGG`
Expand All @@ -330,13 +332,17 @@ There also are the following window functions:
* `LAST_VALUE`
* `NTH_VALUE`

Window functions are explained in more depth in the <<window-functions,window functions>> chapter.

WARNING: This is an internal function which is not intended for direct use and might change without notice.

WARNING: This function might not be supported by all DBMS, so make sure your target database does before using it

// TODO: compatibility matrix

==== SET functions

Syntax: `FUNCTION ( 'SET_XXX', subqueries+ (, 'ORDER BY' (, order_by_expression (, order_specification ) )+ )? (, 'LIMIT', limit_expression (, 'OFFSET', offset_expression )? )? )`
Syntax: `SET_XXX ( subqueries+ (, 'ORDER BY' (, order_by_expression (, order_specification ) )+ )? (, 'LIMIT', limit_expression (, 'OFFSET', offset_expression )? )? )`

WARNING: This function is used internally and no user should ever have the need for this!

Expand All @@ -358,23 +364,23 @@ For further information on DBMS support take a look at the <<anchor-set-dbms-com

==== COMPARE_ROW_VALUE function

Syntax: `FUNCTION ( 'COMPARE_ROW_VALUE', comparison_operator, CASE WHEN (1=NULLIF(1,1) AND row_value_1_1=row_value_2_1 AND row_value_1_2=nullif(1,1) ... AND row_value_1_n=row_value_2_n AND row_value_1_2=row_value_2_2 THEN 1 ELSE 0 END)`
Syntax: `COMPARE_ROW_VALUE ( comparison_operator, CASE WHEN (1=NULLIF(1,1) AND row_value_1_1=row_value_2_1 AND row_value_1_2=nullif(1,1) ... AND row_value_1_n=row_value_2_n AND row_value_1_2=row_value_2_2 THEN 1 ELSE 0 END )`

Produces a DBMS native row value comparison expression such as `(row_value_1_1, row_value_1_2, ..., row_value_1_n) < (row_value_2_1, row_value_2_2, ..., row_value_2_n)`.

WARNING: This is an internal function that is used to implement optimized keyset pagination. It is not intended for direct use and might change without notice.

==== SUBQUERY function

Syntax: `FUNCTION ( 'SUBQUERY', subquery)`
Syntax: `SUBQUERY ( subquery )`

Simply renders the subquery argument.

WARNING: This is an internal function that is used to bypass the Hibernate parser for rendering subqueries as aggregate function arguments.

==== ENTITY_FUNCTION function

Syntax: `FUNCTION ( 'ENTITY_FUNCTION', subquery, entityName, valuesClause, valuesAliases, syntheticPredicate)`
Syntax: `ENTITY_FUNCTION ( subquery, entityName, valuesClause, valuesAliases, syntheticPredicate )`

Rewrites the passed in query by replacing placeholder SQL parts with the proper SQL.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ The property can be changed for a criteria builder before constructing a query.
[[configuration-jpql-functions]]
=== Jpql functions

Custom functions that can be invoked via the JPA 2.1 function syntax `FUNCTION('function_name', args...)` can be registered with `registerFunction(JpqlFunctionGroup)`.
Custom functions that can be invoked via the JPA 2.1 function syntax `FUNCTION('function_name', args...)` or the non-standard function syntax `function_name(args...)` can be registered with `registerFunction(JpqlFunctionGroup)`.
A `JpqlFunctionGroup` represents a logical function that can contain multiple implementations for various dbms and can be defined as being an aggregate function.

These functions are registered as native persistence provider functions and can therefore also be used with plain JPA APIs or the native persistence provider APIs.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Any built-in aggregate function can be used as a window function. These are:
* `AVG` - Returns the average value across the rows in the window
* `MAX` - Returns the maximal across the rows in the window
* `MIN` - Returns the minimal across the rows in the window
* `COUNT` - Returns the count across the rows in the window

[source,java]
----
Expand Down Expand Up @@ -81,4 +82,4 @@ CriteriaBuilder<Tuple> criteria = cbf.create(em, Tuple.class)
.select("SUM(per.age) OVER (x ORDER BY per.id)")
----

WARNING: Note that the partition, order or range of a window definition can only bee specified if the base window does not specify any partition, order or range.
WARNING: Note that the partition, order or range of a window definition can only be specified if the base window does not specify any partition, order or range.
Loading

0 comments on commit dcf77e8

Please sign in to comment.