From e5afffc5296860cda235fa18be5cf139cfef2f1d Mon Sep 17 00:00:00 2001 From: Felix Date: Sat, 20 May 2023 15:09:44 +0300 Subject: [PATCH 1/3] HHH-16648 Add documentation for implementing custom functions --- .../chapters/query/hql/QueryLanguage.adoc | 138 ++++++++++++++++-- .../hql-user-defined-function-example.sql | 20 +++ .../CountItemsGreaterValSqmFunction.java | 135 +++++++++++++++++ .../CustomDialectFunctionTest.java | 74 ++++++++++ .../test/hql/customFunctions/Employee.java | 63 ++++++++ .../customFunctions/ExtendedPGDialect.java | 49 +++++++ 6 files changed, 470 insertions(+), 9 deletions(-) create mode 100644 documentation/src/main/asciidoc/userguide/chapters/query/hql/extras/hql-user-defined-function-example.sql create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CountItemsGreaterValSqmFunction.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CustomDialectFunctionTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/Employee.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/ExtendedPGDialect.java diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc index 5824fa11bd8c..462889390898 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc @@ -2506,16 +2506,9 @@ There are several ways to call native or user-defined SQL functions. - A native or user-defined function may be called using JPQL's `function` syntax, for example, ``function('sinh', phi)``. (This is the easiest way, but not the best way.) - A user-written `FunctionContributor` may register user-defined functions. -- A custom `Dialect` may register additional native functions by overriding `initializeFunctionRegistry()`. +- A custom `Dialect` may register additional native functions by overriding `initializeFunctionRegistry()`. <> -[TIP] -==== -Registering a function isn't hard, but is beyond the scope of this chapter. - -(It's even possible to use the APIs Hibernate provides to make your own _portable_ functions!) -==== - -Fortunately, every built-in `Dialect` already registers many native functions for the database it supports. +Fortunately, every built-in `Dialect` already registers many native functions for the database it supports. So there is no need to define this functions explicitly. [TIP] ==== @@ -4160,3 +4153,130 @@ Hibernate does emulate the `search` and `cycle` clauses though if necessary, so Note that most modern database versions support recursive CTEs already. ==== + +[[hql-user-defined-functions-implementation]] +=== User-defined functions implementation + +Hibernate Dialects can register additional functions known to be available for that particular database product. +These functions are also available in HQL (and JPQL, though only when using Hibernate as the JPA provider, obviously). +However, they would only be available when using that database Dialect. +Applications that aim for database portability should avoid using functions in this category. + +Application developers can also supply their own set of functions. +This would usually represent either user-defined SQL functions or aliases for snippets of SQL. + +Such function can be declared by using the `register()` method of `org.hibernate.query.sqm.function.SqmFunctionRegistry`. + +For example, we have the following SQL function: + +[[hql-user-defined-function-example]] +.Custom aggregate function +==== +[source, SQL, indent=0] +---- +include::{extrasdir}/hql-user-defined-function-example.sql[] +---- +==== + +Also, we have the `Employee` entity. + +[[hql-user-defined-function-domain-model]] +.Domain model +==== +[source, JAVA, indent=0] +---- +include::{example-dir-hql}/customFunctions/Employee.java[tags=hql-examples-domain-model-example] +---- +==== + +Let’s persist the following entities in our database: + +[[hql-user-defined-function-inital-data]] +.Initial data +==== +[source, JAVA, indent=0] +---- +include::{example-dir-hql}/customFunctions/CustomDialectFunctionTest.java[tags=hql-user-defined-dialect-function-inital-data] +---- +==== + +The first step for implementing a custom function is to create a custom dialect `ExtendedPGDialect`, which inherits from `PostgreSQLDialect`. + +[[hql-user-defined-dialect-function-cutom-dialect]] +.Custom dialect +==== +[source, JAVA, indent=0] +---- +include::{example-dir-hql}/customFunctions/ExtendedPGDialect.java[tags=hql-user-defined-dialect-function-custom-dialect] +---- +==== + +Secondly, we will set the `ExtendedPGDialect` to Hibernate config. + +[[hql-user-defined-dialect-function-cutom-dialect-property]] +.Custom dialect property +==== +[source, xml, indent=0] +---- + + + path.to.the.ExtendedPGDialect + +---- +==== + +For implementing custom function we should inherit the new class `CountItemsGreaterValSqmFunction` from `AbstractSqmSelfRenderingFunctionDescriptor` class. + +[NOTE] +==== +Constructor of `org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor` contains the following fields: + +* `String name` - name of the function _in the database_ +* `FunctionKind` - type of the function: `NORMAL`, `AGGREGATE`, `ORDERED_SET_AGGREGATE` or `WINDOW` +* `ArgumentsValidator` - validator of the arguments provided to an JPQL/HQL function +* `FunctionReturnTypeResolver` - resolver of the function return type +* `FunctionArgumentTypeResolver` - resolver of the function argument types +==== + +[[hql-user-defined-dialect-function-sqm-renderer]] +.Custom function renderer +==== +[source, JAVA, indent=0] +---- +include::{example-dir-hql}/customFunctions/CountItemsGreaterValSqmFunction.java[tags=hql-user-defined-dialect-function-sqm-renderer] +---- +==== + +Next step we should define the renderer: + +[[hql-user-defined-dialect-function-sqm-renderer-definition]] +.Custom function renderer definition +==== +[source, JAVA, indent=0] +---- +include::{example-dir-hql}/customFunctions/CountItemsGreaterValSqmFunction.java[tags=hql-user-defined-dialect-function-sqm-renderer-definition] +---- +==== + +Then we'll extend the `initializeFunctionRegistry()` method of the `ExtendedPGDialect` with new the logic: +adding `CountItemsGreaterValSqmFunction` to the default function registry of `FunctionContributions`. + +[[hql-user-defined-dialect-function-registry-extending]] +.Custom dialect +==== +[source, JAVA, indent=0] +---- +include::{example-dir-hql}/customFunctions/ExtendedPGDialect.java[tags=hql-user-defined-dialect-function-registry-extending] +---- +==== + +Once the `countItemsGreaterVal` function has been registered, we are able to use it in our JPQL/HQL queries. + +[[hql-user-defined-dialect-function-test]] +.Test of the custom function +==== +[source, JAVA, indent=0] +---- +include::{example-dir-hql}/customFunctions/CustomDialectFunctionTest.java[tags=hql-user-defined-dialect-function-test] +---- +==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/hql/extras/hql-user-defined-function-example.sql b/documentation/src/main/asciidoc/userguide/chapters/query/hql/extras/hql-user-defined-function-example.sql new file mode 100644 index 000000000000..02bfb4170e0a --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/extras/hql-user-defined-function-example.sql @@ -0,0 +1,20 @@ +CREATE OR REPLACE FUNCTION greater_than(count BIGINT, value NUMERIC, gr_val NUMERIC) + RETURNS BIGINT AS +$$ +BEGIN + RETURN CASE WHEN value > gr_val THEN (count + 1)::BIGINT ELSE count::BIGINT END; +END; +$$ LANGUAGE "plpgsql"; + +CREATE OR REPLACE FUNCTION agg_final(c bigint) RETURNS BIGINT AS +$$ +BEGIN + return c; +END; +$$ LANGUAGE "plpgsql"; + +CREATE OR REPLACE AGGREGATE count_items_greater_val(NUMERIC, NUMERIC) ( + SFUNC = greater_than, + STYPE = BIGINT, + FINALFUNC = agg_final, + INITCOND = 0); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CountItemsGreaterValSqmFunction.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CountItemsGreaterValSqmFunction.java new file mode 100644 index 000000000000..b296b0b0a68a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CountItemsGreaterValSqmFunction.java @@ -0,0 +1,135 @@ +package org.hibernate.orm.test.hql.customFunctions; + +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.function.CastFunction; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.function.FunctionKind; +import org.hibernate.query.sqm.produce.function.*; +import org.hibernate.sql.ast.Clause; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.CastTarget; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.predicate.Predicate; +import org.hibernate.type.BasicType; +import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.spi.TypeConfiguration; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; + +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.NUMERIC; + +//tag::hql-user-defined-dialect-function-sqm-renderer[] +public class CountItemsGreaterValSqmFunction extends AbstractSqmSelfRenderingFunctionDescriptor { + private final CastFunction castFunction; + private final BasicType bigDecimalType; + + public CountItemsGreaterValSqmFunction(String name, Dialect dialect, TypeConfiguration typeConfiguration) { + super( + name, + FunctionKind.AGGREGATE, + /* Function consumes 2 numeric typed args: + - the aggregation argument + - the bottom edge for the count predicate*/ + new ArgumentTypesValidator(StandardArgumentsValidators.exactly(2), + FunctionParameterType.NUMERIC, + FunctionParameterType.NUMERIC + ), + // Function returns one value - the number of items + StandardFunctionReturnTypeResolvers.invariant( + typeConfiguration.getBasicTypeRegistry() + .resolve(StandardBasicTypes.BIG_INTEGER) + ), + StandardFunctionArgumentTypeResolvers.invariant( + typeConfiguration, NUMERIC, NUMERIC + ) + ); + // Extracting cast function for setting input arguments to correct the type + castFunction = new CastFunction( + dialect, + dialect.getPreferredSqlTypeCodeForBoolean() + ); + bigDecimalType = typeConfiguration.getBasicTypeRegistry() + .resolve(StandardBasicTypes.BIG_DECIMAL); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + SqlAstTranslator walker) { + render(sqlAppender, sqlAstArguments, null, walker); + } + + //tag::hql-user-defined-dialect-function-sqm-renderer-definition[] + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + Predicate filter, + SqlAstTranslator translator) { + // Renderer definition + //end::hql-user-defined-dialect-function-sqm-renderer[] + + // Appending name of SQL function to result query + sqlAppender.appendSql(getName()); + sqlAppender.appendSql('('); + + // Extracting 2 arguments + final Expression first_arg = (Expression) sqlAstArguments.get(0); + final Expression second_arg = (Expression) sqlAstArguments.get(1); + + // If JPQL contains "filter" expression, but database doesn't support it + // then append: function_name(case when (filter_expr) then (argument) else null end) + final boolean caseWrapper = filter != null && !translator.supportsFilterClause(); + if (caseWrapper) { + translator.getCurrentClauseStack().push(Clause.WHERE); + sqlAppender.appendSql("case when "); + + filter.accept(translator); + translator.getCurrentClauseStack().pop(); + + sqlAppender.appendSql(" then "); + renderArgument(sqlAppender, translator, first_arg); + sqlAppender.appendSql(" else null end)"); + } else { + renderArgument(sqlAppender, translator, first_arg); + sqlAppender.appendSql(", "); + renderArgument(sqlAppender, translator, second_arg); + sqlAppender.appendSql(')'); + if (filter != null) { + translator.getCurrentClauseStack().push(Clause.WHERE); + sqlAppender.appendSql(" filter (where "); + + filter.accept(translator); + sqlAppender.appendSql(')'); + translator.getCurrentClauseStack().pop(); + } + } + //tag::hql-user-defined-dialect-function-sqm-renderer[] + } + + //end::hql-user-defined-dialect-function-sqm-renderer[] + private void renderArgument( + SqlAppender sqlAppender, + SqlAstTranslator translator, + Expression arg) { + // Extracting the type of argument + final JdbcMapping sourceMapping = arg.getExpressionType().getJdbcMappings().get(0); + if (sourceMapping.getJdbcType().isNumber()) { + castFunction.render(sqlAppender, + Arrays.asList(arg, new CastTarget(bigDecimalType)), + translator + ); + } else { + arg.accept(translator); + } + } + //tag::hql-user-defined-dialect-function-sqm-renderer[] + //end::hql-user-defined-dialect-function-sqm-renderer-definition[] +} +//end::hql-user-defined-dialect-function-sqm-renderer[] diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CustomDialectFunctionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CustomDialectFunctionTest.java new file mode 100644 index 000000000000..17dad1128de5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CustomDialectFunctionTest.java @@ -0,0 +1,74 @@ +package org.hibernate.orm.test.hql.customFunctions; + +import jakarta.persistence.EntityManager; +import org.hibernate.Session; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.PostgreSQLDialect; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.junit.Test; + + +import java.sql.Statement; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +@RequiresDialect(PostgreSQLDialect.class) +public class CustomDialectFunctionTest extends BaseCoreFunctionalTestCase { + + @Override + protected void configure(Configuration configuration) { + super.configure(configuration); + configuration.addAnnotatedClass(Employee.class); + + configuration.setProperty(AvailableSettings.DIALECT, "org.hibernate.orm.test.hql.customFunctions.ExtendedPGDialect"); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + Employee.class + }; + } + + @Test + @RequiresDialect(PostgreSQLDialect.class) + public void test_custom_sqm_functions() { + doInJPA(this::sessionFactory, session -> { + try (EntityManager entityManager = session.getEntityManagerFactory().createEntityManager()) { + var tx = entityManager.getTransaction(); + tx.begin(); + + entityManager.unwrap(Session.class).doWork(connection -> { + try (Statement statement = connection.createStatement()) { + statement.executeUpdate(""" + create or replace function greater_than(c bigint, val numeric, gr_val numeric) returns bigint as $$ begin return case when val > gr_val then (c + 1)::bigint else c::bigint end; end; $$ language "plpgsql"; + create or replace function agg_final(c bigint) returns bigint as $$ begin return c; end; $$ language "plpgsql"; + create or replace aggregate count_items_greater_val(numeric, numeric) (sfunc = greater_than, stype = bigint, finalfunc = agg_final, initcond = 0); + """ + ); + } + }); + + //tag::hql-user-defined-dialect-function-inital-data[] + entityManager.persist(new Employee(1L, 200L, "Jonn", "Robson")); + entityManager.persist(new Employee(2L, 350L, "Bert", "Marshall")); + entityManager.persist(new Employee(3L, 360L, "Joey", "Barton")); + entityManager.persist(new Employee(4L, 400L, "Bert", "Marshall")); + //end::hql-user-defined-dialect-function-inital-data[] + + tx.commit(); + //tag::hql-user-defined-dialect-function-test[] + var res = entityManager + .createQuery("select count_items_greater_val(salary, 220) from Employee") + .getSingleResult(); + assertEquals(3L, res); + //end::hql-user-defined-dialect-function-test[] + } + }); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/Employee.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/Employee.java new file mode 100644 index 000000000000..5447e58ed2a5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/Employee.java @@ -0,0 +1,63 @@ +package org.hibernate.orm.test.hql.customFunctions; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + + +//tag::hql-examples-domain-model-example[] +@Entity +public class Employee { + @Id + private Long id; + private Long salary; + private String name; + private String surname; + + public Employee(Long id, Long salary, String name, String surname) { + this.id = id; + this.salary = salary; + this.name = name; + this.surname = surname; + } + + //Getters and setters are omitted for brevity + +//end::hql-examples-domain-model-example[] + + public Employee() { + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getSalary() { + return salary; + } + + public void setSalary(Long salary) { + this.salary = salary; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSurname() { + return surname; + } + + public void setSurname(String surname) { + this.surname = surname; + } + //tag::hql-examples-domain-model-example[] +} +//end::hql-examples-domain-model-example[] \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/ExtendedPGDialect.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/ExtendedPGDialect.java new file mode 100644 index 000000000000..9fed49956d66 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/ExtendedPGDialect.java @@ -0,0 +1,49 @@ +package org.hibernate.orm.test.hql.customFunctions; + +import org.hibernate.boot.model.FunctionContributions; +import org.hibernate.dialect.DatabaseVersion; +import org.hibernate.dialect.PostgreSQLDialect; +import org.hibernate.dialect.PostgreSQLDriverKind; +import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; + +//tag::hql-user-defined-dialect-function-custom-dialect[] +public class ExtendedPGDialect extends PostgreSQLDialect { + + // Default constructors + + //end::hql-user-defined-dialect-function-custom-dialect[] + public ExtendedPGDialect() { + super(); + } + + public ExtendedPGDialect(DialectResolutionInfo info) { + super(info); + } + + public ExtendedPGDialect(DatabaseVersion version) { + super(version); + } + + public ExtendedPGDialect(DatabaseVersion version, PostgreSQLDriverKind driverKind) { + super(version, driverKind); + } + + //tag::hql-user-defined-dialect-function-custom-dialect[] + //tag::hql-user-defined-dialect-function-registry-extending[] + @Override + public void initializeFunctionRegistry(FunctionContributions functionContributions) { + super.initializeFunctionRegistry(functionContributions); + //end::hql-user-defined-dialect-function-custom-dialect[] + // Custom aggregate function + functionContributions.getFunctionRegistry().register( + "countItemsGreaterVal", // Name that can be used in JPQL queries + new CountItemsGreaterValSqmFunction( + "count_items_greater_val", // Name of the function in the database + this, + functionContributions.getTypeConfiguration()) + ); + //tag::hql-user-defined-dialect-function-custom-dialect[] + } + //end::hql-user-defined-dialect-function-registry-extending[] +} +//end::hql-user-defined-dialect-function-custom-dialect[] From 98fab3592652033f004fc314bc2960da705f2e97 Mon Sep 17 00:00:00 2001 From: Felix Date: Sat, 20 May 2023 15:57:51 +0300 Subject: [PATCH 2/3] HHH-16648 Fixed multiline string --- .../CountItemsGreaterValSqmFunction.java | 6 ++++++ .../CustomDialectFunctionTest.java | 16 ++++++++++------ .../orm/test/hql/customFunctions/Employee.java | 6 ++++++ .../hql/customFunctions/ExtendedPGDialect.java | 7 ++++++- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CountItemsGreaterValSqmFunction.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CountItemsGreaterValSqmFunction.java index b296b0b0a68a..d340583552b8 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CountItemsGreaterValSqmFunction.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CountItemsGreaterValSqmFunction.java @@ -1,3 +1,9 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ package org.hibernate.orm.test.hql.customFunctions; import org.hibernate.dialect.Dialect; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CustomDialectFunctionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CustomDialectFunctionTest.java index 17dad1128de5..d449b5bef204 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CustomDialectFunctionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CustomDialectFunctionTest.java @@ -1,10 +1,15 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ package org.hibernate.orm.test.hql.customFunctions; import jakarta.persistence.EntityManager; import org.hibernate.Session; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.orm.junit.RequiresDialect; @@ -44,11 +49,10 @@ public void test_custom_sqm_functions() { entityManager.unwrap(Session.class).doWork(connection -> { try (Statement statement = connection.createStatement()) { - statement.executeUpdate(""" - create or replace function greater_than(c bigint, val numeric, gr_val numeric) returns bigint as $$ begin return case when val > gr_val then (c + 1)::bigint else c::bigint end; end; $$ language "plpgsql"; - create or replace function agg_final(c bigint) returns bigint as $$ begin return c; end; $$ language "plpgsql"; - create or replace aggregate count_items_greater_val(numeric, numeric) (sfunc = greater_than, stype = bigint, finalfunc = agg_final, initcond = 0); - """ + statement.executeUpdate( + "create or replace function greater_than(c bigint, val numeric, gr_val numeric) returns bigint as $$ begin return case when val > gr_val then (c + 1)::bigint else c::bigint end; end; $$ language 'plpgsql'; " + + "create or replace function agg_final(c bigint) returns bigint as $$ begin return c; end; $$ language 'plpgsql'; " + + "create or replace aggregate count_items_greater_val(numeric, numeric) (sfunc = greater_than, stype = bigint, finalfunc = agg_final, initcond = 0);" ); } }); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/Employee.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/Employee.java index 5447e58ed2a5..748644ceb9ab 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/Employee.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/Employee.java @@ -1,3 +1,9 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ package org.hibernate.orm.test.hql.customFunctions; import jakarta.persistence.Entity; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/ExtendedPGDialect.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/ExtendedPGDialect.java index 9fed49956d66..6b1d7d8e3f92 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/ExtendedPGDialect.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/ExtendedPGDialect.java @@ -1,5 +1,10 @@ package org.hibernate.orm.test.hql.customFunctions; - +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ import org.hibernate.boot.model.FunctionContributions; import org.hibernate.dialect.DatabaseVersion; import org.hibernate.dialect.PostgreSQLDialect; From adc1885ea33092d66bedc17ee09ce7e838965ad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 3 Oct 2025 19:39:52 +0200 Subject: [PATCH 3/3] Fix license headers --- .../CountItemsGreaterValSqmFunction.java | 198 +++++++++--------- .../CustomDialectFunctionTest.java | 94 ++++----- .../test/hql/customFunctions/Employee.java | 86 ++++---- .../customFunctions/ExtendedPGDialect.java | 81 ++++--- 4 files changed, 226 insertions(+), 233 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CountItemsGreaterValSqmFunction.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CountItemsGreaterValSqmFunction.java index d340583552b8..47922016a880 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CountItemsGreaterValSqmFunction.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CountItemsGreaterValSqmFunction.java @@ -1,8 +1,6 @@ /* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.orm.test.hql.customFunctions; @@ -31,111 +29,111 @@ //tag::hql-user-defined-dialect-function-sqm-renderer[] public class CountItemsGreaterValSqmFunction extends AbstractSqmSelfRenderingFunctionDescriptor { - private final CastFunction castFunction; - private final BasicType bigDecimalType; + private final CastFunction castFunction; + private final BasicType bigDecimalType; - public CountItemsGreaterValSqmFunction(String name, Dialect dialect, TypeConfiguration typeConfiguration) { - super( - name, - FunctionKind.AGGREGATE, - /* Function consumes 2 numeric typed args: - - the aggregation argument - - the bottom edge for the count predicate*/ - new ArgumentTypesValidator(StandardArgumentsValidators.exactly(2), - FunctionParameterType.NUMERIC, - FunctionParameterType.NUMERIC - ), - // Function returns one value - the number of items - StandardFunctionReturnTypeResolvers.invariant( - typeConfiguration.getBasicTypeRegistry() - .resolve(StandardBasicTypes.BIG_INTEGER) - ), - StandardFunctionArgumentTypeResolvers.invariant( - typeConfiguration, NUMERIC, NUMERIC - ) - ); - // Extracting cast function for setting input arguments to correct the type - castFunction = new CastFunction( - dialect, - dialect.getPreferredSqlTypeCodeForBoolean() - ); - bigDecimalType = typeConfiguration.getBasicTypeRegistry() - .resolve(StandardBasicTypes.BIG_DECIMAL); - } + public CountItemsGreaterValSqmFunction(String name, Dialect dialect, TypeConfiguration typeConfiguration) { + super( + name, + FunctionKind.AGGREGATE, + /* Function consumes 2 numeric typed args: + - the aggregation argument + - the bottom edge for the count predicate*/ + new ArgumentTypesValidator(StandardArgumentsValidators.exactly(2), + FunctionParameterType.NUMERIC, + FunctionParameterType.NUMERIC + ), + // Function returns one value - the number of items + StandardFunctionReturnTypeResolvers.invariant( + typeConfiguration.getBasicTypeRegistry() + .resolve(StandardBasicTypes.BIG_INTEGER) + ), + StandardFunctionArgumentTypeResolvers.invariant( + typeConfiguration, NUMERIC, NUMERIC + ) + ); + // Extracting cast function for setting input arguments to correct the type + castFunction = new CastFunction( + dialect, + dialect.getPreferredSqlTypeCodeForBoolean() + ); + bigDecimalType = typeConfiguration.getBasicTypeRegistry() + .resolve(StandardBasicTypes.BIG_DECIMAL); + } - @Override - public void render( - SqlAppender sqlAppender, - List sqlAstArguments, - SqlAstTranslator walker) { - render(sqlAppender, sqlAstArguments, null, walker); - } + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + SqlAstTranslator walker) { + render(sqlAppender, sqlAstArguments, null, walker); + } - //tag::hql-user-defined-dialect-function-sqm-renderer-definition[] - @Override - public void render( - SqlAppender sqlAppender, - List sqlAstArguments, - Predicate filter, - SqlAstTranslator translator) { - // Renderer definition - //end::hql-user-defined-dialect-function-sqm-renderer[] + //tag::hql-user-defined-dialect-function-sqm-renderer-definition[] + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + Predicate filter, + SqlAstTranslator translator) { + // Renderer definition + //end::hql-user-defined-dialect-function-sqm-renderer[] - // Appending name of SQL function to result query - sqlAppender.appendSql(getName()); - sqlAppender.appendSql('('); + // Appending name of SQL function to result query + sqlAppender.appendSql(getName()); + sqlAppender.appendSql('('); - // Extracting 2 arguments - final Expression first_arg = (Expression) sqlAstArguments.get(0); - final Expression second_arg = (Expression) sqlAstArguments.get(1); + // Extracting 2 arguments + final Expression first_arg = (Expression) sqlAstArguments.get(0); + final Expression second_arg = (Expression) sqlAstArguments.get(1); - // If JPQL contains "filter" expression, but database doesn't support it - // then append: function_name(case when (filter_expr) then (argument) else null end) - final boolean caseWrapper = filter != null && !translator.supportsFilterClause(); - if (caseWrapper) { - translator.getCurrentClauseStack().push(Clause.WHERE); - sqlAppender.appendSql("case when "); + // If JPQL contains "filter" expression, but database doesn't support it + // then append: function_name(case when (filter_expr) then (argument) else null end) + final boolean caseWrapper = filter != null && !translator.supportsFilterClause(); + if (caseWrapper) { + translator.getCurrentClauseStack().push(Clause.WHERE); + sqlAppender.appendSql("case when "); - filter.accept(translator); - translator.getCurrentClauseStack().pop(); + filter.accept(translator); + translator.getCurrentClauseStack().pop(); - sqlAppender.appendSql(" then "); - renderArgument(sqlAppender, translator, first_arg); - sqlAppender.appendSql(" else null end)"); - } else { - renderArgument(sqlAppender, translator, first_arg); - sqlAppender.appendSql(", "); - renderArgument(sqlAppender, translator, second_arg); - sqlAppender.appendSql(')'); - if (filter != null) { - translator.getCurrentClauseStack().push(Clause.WHERE); - sqlAppender.appendSql(" filter (where "); + sqlAppender.appendSql(" then "); + renderArgument(sqlAppender, translator, first_arg); + sqlAppender.appendSql(" else null end)"); + } else { + renderArgument(sqlAppender, translator, first_arg); + sqlAppender.appendSql(", "); + renderArgument(sqlAppender, translator, second_arg); + sqlAppender.appendSql(')'); + if (filter != null) { + translator.getCurrentClauseStack().push(Clause.WHERE); + sqlAppender.appendSql(" filter (where "); - filter.accept(translator); - sqlAppender.appendSql(')'); - translator.getCurrentClauseStack().pop(); - } - } - //tag::hql-user-defined-dialect-function-sqm-renderer[] - } + filter.accept(translator); + sqlAppender.appendSql(')'); + translator.getCurrentClauseStack().pop(); + } + } + //tag::hql-user-defined-dialect-function-sqm-renderer[] + } - //end::hql-user-defined-dialect-function-sqm-renderer[] - private void renderArgument( - SqlAppender sqlAppender, - SqlAstTranslator translator, - Expression arg) { - // Extracting the type of argument - final JdbcMapping sourceMapping = arg.getExpressionType().getJdbcMappings().get(0); - if (sourceMapping.getJdbcType().isNumber()) { - castFunction.render(sqlAppender, - Arrays.asList(arg, new CastTarget(bigDecimalType)), - translator - ); - } else { - arg.accept(translator); - } - } - //tag::hql-user-defined-dialect-function-sqm-renderer[] - //end::hql-user-defined-dialect-function-sqm-renderer-definition[] + //end::hql-user-defined-dialect-function-sqm-renderer[] + private void renderArgument( + SqlAppender sqlAppender, + SqlAstTranslator translator, + Expression arg) { + // Extracting the type of argument + final JdbcMapping sourceMapping = arg.getExpressionType().getJdbcMappings().get(0); + if (sourceMapping.getJdbcType().isNumber()) { + castFunction.render(sqlAppender, + Arrays.asList(arg, new CastTarget(bigDecimalType)), + translator + ); + } else { + arg.accept(translator); + } + } + //tag::hql-user-defined-dialect-function-sqm-renderer[] + //end::hql-user-defined-dialect-function-sqm-renderer-definition[] } //end::hql-user-defined-dialect-function-sqm-renderer[] diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CustomDialectFunctionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CustomDialectFunctionTest.java index d449b5bef204..4deb1e6d1b50 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CustomDialectFunctionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/CustomDialectFunctionTest.java @@ -1,8 +1,6 @@ /* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.orm.test.hql.customFunctions; @@ -24,55 +22,55 @@ @RequiresDialect(PostgreSQLDialect.class) public class CustomDialectFunctionTest extends BaseCoreFunctionalTestCase { - @Override - protected void configure(Configuration configuration) { - super.configure(configuration); - configuration.addAnnotatedClass(Employee.class); + @Override + protected void configure(Configuration configuration) { + super.configure(configuration); + configuration.addAnnotatedClass(Employee.class); - configuration.setProperty(AvailableSettings.DIALECT, "org.hibernate.orm.test.hql.customFunctions.ExtendedPGDialect"); - } + configuration.setProperty(AvailableSettings.DIALECT, "org.hibernate.orm.test.hql.customFunctions.ExtendedPGDialect"); + } - @Override - protected Class[] getAnnotatedClasses() { - return new Class[]{ - Employee.class - }; - } + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + Employee.class + }; + } - @Test - @RequiresDialect(PostgreSQLDialect.class) - public void test_custom_sqm_functions() { - doInJPA(this::sessionFactory, session -> { - try (EntityManager entityManager = session.getEntityManagerFactory().createEntityManager()) { - var tx = entityManager.getTransaction(); - tx.begin(); + @Test + @RequiresDialect(PostgreSQLDialect.class) + public void test_custom_sqm_functions() { + doInJPA(this::sessionFactory, session -> { + try (EntityManager entityManager = session.getEntityManagerFactory().createEntityManager()) { + var tx = entityManager.getTransaction(); + tx.begin(); - entityManager.unwrap(Session.class).doWork(connection -> { - try (Statement statement = connection.createStatement()) { - statement.executeUpdate( - "create or replace function greater_than(c bigint, val numeric, gr_val numeric) returns bigint as $$ begin return case when val > gr_val then (c + 1)::bigint else c::bigint end; end; $$ language 'plpgsql'; " + - "create or replace function agg_final(c bigint) returns bigint as $$ begin return c; end; $$ language 'plpgsql'; " + - "create or replace aggregate count_items_greater_val(numeric, numeric) (sfunc = greater_than, stype = bigint, finalfunc = agg_final, initcond = 0);" - ); - } - }); + entityManager.unwrap(Session.class).doWork(connection -> { + try (Statement statement = connection.createStatement()) { + statement.executeUpdate( + "create or replace function greater_than(c bigint, val numeric, gr_val numeric) returns bigint as $$ begin return case when val > gr_val then (c + 1)::bigint else c::bigint end; end; $$ language 'plpgsql'; " + + "create or replace function agg_final(c bigint) returns bigint as $$ begin return c; end; $$ language 'plpgsql'; " + + "create or replace aggregate count_items_greater_val(numeric, numeric) (sfunc = greater_than, stype = bigint, finalfunc = agg_final, initcond = 0);" + ); + } + }); - //tag::hql-user-defined-dialect-function-inital-data[] - entityManager.persist(new Employee(1L, 200L, "Jonn", "Robson")); - entityManager.persist(new Employee(2L, 350L, "Bert", "Marshall")); - entityManager.persist(new Employee(3L, 360L, "Joey", "Barton")); - entityManager.persist(new Employee(4L, 400L, "Bert", "Marshall")); - //end::hql-user-defined-dialect-function-inital-data[] + //tag::hql-user-defined-dialect-function-inital-data[] + entityManager.persist(new Employee(1L, 200L, "Jonn", "Robson")); + entityManager.persist(new Employee(2L, 350L, "Bert", "Marshall")); + entityManager.persist(new Employee(3L, 360L, "Joey", "Barton")); + entityManager.persist(new Employee(4L, 400L, "Bert", "Marshall")); + //end::hql-user-defined-dialect-function-inital-data[] - tx.commit(); - //tag::hql-user-defined-dialect-function-test[] - var res = entityManager - .createQuery("select count_items_greater_val(salary, 220) from Employee") - .getSingleResult(); - assertEquals(3L, res); - //end::hql-user-defined-dialect-function-test[] - } - }); - } + tx.commit(); + //tag::hql-user-defined-dialect-function-test[] + var res = entityManager + .createQuery("select count_items_greater_val(salary, 220) from Employee") + .getSingleResult(); + assertEquals(3L, res); + //end::hql-user-defined-dialect-function-test[] + } + }); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/Employee.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/Employee.java index 748644ceb9ab..cbccdac197ed 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/Employee.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/Employee.java @@ -1,8 +1,6 @@ /* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.orm.test.hql.customFunctions; @@ -13,57 +11,57 @@ //tag::hql-examples-domain-model-example[] @Entity public class Employee { - @Id - private Long id; - private Long salary; - private String name; - private String surname; + @Id + private Long id; + private Long salary; + private String name; + private String surname; - public Employee(Long id, Long salary, String name, String surname) { - this.id = id; - this.salary = salary; - this.name = name; - this.surname = surname; - } + public Employee(Long id, Long salary, String name, String surname) { + this.id = id; + this.salary = salary; + this.name = name; + this.surname = surname; + } - //Getters and setters are omitted for brevity + //Getters and setters are omitted for brevity //end::hql-examples-domain-model-example[] - public Employee() { - } + public Employee() { + } - public Long getId() { - return id; - } + public Long getId() { + return id; + } - public void setId(Long id) { - this.id = id; - } + public void setId(Long id) { + this.id = id; + } - public Long getSalary() { - return salary; - } + public Long getSalary() { + return salary; + } - public void setSalary(Long salary) { - this.salary = salary; - } + public void setSalary(Long salary) { + this.salary = salary; + } - public String getName() { - return name; - } + public String getName() { + return name; + } - public void setName(String name) { - this.name = name; - } + public void setName(String name) { + this.name = name; + } - public String getSurname() { - return surname; - } + public String getSurname() { + return surname; + } - public void setSurname(String surname) { - this.surname = surname; - } - //tag::hql-examples-domain-model-example[] + public void setSurname(String surname) { + this.surname = surname; + } + //tag::hql-examples-domain-model-example[] } -//end::hql-examples-domain-model-example[] \ No newline at end of file +//end::hql-examples-domain-model-example[] diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/ExtendedPGDialect.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/ExtendedPGDialect.java index 6b1d7d8e3f92..a8c4e1afd17e 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/ExtendedPGDialect.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/customFunctions/ExtendedPGDialect.java @@ -1,10 +1,9 @@ -package org.hibernate.orm.test.hql.customFunctions; /* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors */ +package org.hibernate.orm.test.hql.customFunctions; + import org.hibernate.boot.model.FunctionContributions; import org.hibernate.dialect.DatabaseVersion; import org.hibernate.dialect.PostgreSQLDialect; @@ -14,41 +13,41 @@ //tag::hql-user-defined-dialect-function-custom-dialect[] public class ExtendedPGDialect extends PostgreSQLDialect { - // Default constructors - - //end::hql-user-defined-dialect-function-custom-dialect[] - public ExtendedPGDialect() { - super(); - } - - public ExtendedPGDialect(DialectResolutionInfo info) { - super(info); - } - - public ExtendedPGDialect(DatabaseVersion version) { - super(version); - } - - public ExtendedPGDialect(DatabaseVersion version, PostgreSQLDriverKind driverKind) { - super(version, driverKind); - } - - //tag::hql-user-defined-dialect-function-custom-dialect[] - //tag::hql-user-defined-dialect-function-registry-extending[] - @Override - public void initializeFunctionRegistry(FunctionContributions functionContributions) { - super.initializeFunctionRegistry(functionContributions); - //end::hql-user-defined-dialect-function-custom-dialect[] - // Custom aggregate function - functionContributions.getFunctionRegistry().register( - "countItemsGreaterVal", // Name that can be used in JPQL queries - new CountItemsGreaterValSqmFunction( - "count_items_greater_val", // Name of the function in the database - this, - functionContributions.getTypeConfiguration()) - ); - //tag::hql-user-defined-dialect-function-custom-dialect[] - } - //end::hql-user-defined-dialect-function-registry-extending[] + // Default constructors + + //end::hql-user-defined-dialect-function-custom-dialect[] + public ExtendedPGDialect() { + super(); + } + + public ExtendedPGDialect(DialectResolutionInfo info) { + super(info); + } + + public ExtendedPGDialect(DatabaseVersion version) { + super(version); + } + + public ExtendedPGDialect(DatabaseVersion version, PostgreSQLDriverKind driverKind) { + super(version, driverKind); + } + + //tag::hql-user-defined-dialect-function-custom-dialect[] + //tag::hql-user-defined-dialect-function-registry-extending[] + @Override + public void initializeFunctionRegistry(FunctionContributions functionContributions) { + super.initializeFunctionRegistry(functionContributions); + //end::hql-user-defined-dialect-function-custom-dialect[] + // Custom aggregate function + functionContributions.getFunctionRegistry().register( + "countItemsGreaterVal", // Name that can be used in JPQL queries + new CountItemsGreaterValSqmFunction( + "count_items_greater_val", // Name of the function in the database + this, + functionContributions.getTypeConfiguration()) + ); + //tag::hql-user-defined-dialect-function-custom-dialect[] + } + //end::hql-user-defined-dialect-function-registry-extending[] } //end::hql-user-defined-dialect-function-custom-dialect[]