Skip to content

Commit e0417d5

Browse files
committed
HHH-18496 Add json_object and json_array functions
1 parent f68af8d commit e0417d5

File tree

70 files changed

+2911
-31
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+2911
-31
lines changed

documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1629,22 +1629,90 @@ The following functions deal with SQL JSON types, which are not supported on eve
16291629
|===
16301630
| Function | Purpose
16311631
1632+
| `json_object()` | Constructs a JSON object from pairs of key and value arguments
1633+
| `json_array()` | Constructs a JSON array from arguments
16321634
| `json_value()` | Extracts a value from a JSON document by JSON path
16331635
|===
16341636
1637+
1638+
[[hql-json-object-function]]
1639+
===== `json_object()`
1640+
1641+
Constructs a JSON object from pairs of key and value arguments.
1642+
1643+
[[hql-json-object-bnf]]
1644+
[source, antlrv4, indent=0]
1645+
----
1646+
include::{extrasdir}/json_object_bnf.txt[]
1647+
----
1648+
1649+
Argument count must be even and expressions alternate between keys and values i.e. `key1, value1, key2, value2, ...`.
1650+
Alternatively, it is also possible to use a `:` (colon) to separate keys from values or the `value` keyword.
1651+
1652+
[[hql-json-object-example]]
1653+
====
1654+
[source, java, indent=0]
1655+
----
1656+
include::{json-example-dir-hql}/JsonObjectTest.java[tags=hql-json-object-example]
1657+
----
1658+
====
1659+
1660+
Although database dependent, usually `null` values are present in the resulting JSON object.
1661+
To remove `null` value entries, use the `absent on null` clause.
1662+
1663+
[[hql-json-object-on-null-example]]
1664+
====
1665+
[source, java, indent=0]
1666+
----
1667+
include::{json-example-dir-hql}/JsonObjectTest.java[tags=hql-json-object-on-null-example]
1668+
----
1669+
====
1670+
1671+
[[hql-json-array-function]]
1672+
===== `json_array()`
1673+
1674+
Constructs a JSON array from arguments.
1675+
1676+
[[hql-json-array-bnf]]
1677+
[source, antlrv4, indent=0]
1678+
----
1679+
include::{extrasdir}/json_array_bnf.txt[]
1680+
----
1681+
1682+
[[hql-json-array-example]]
1683+
====
1684+
[source, java, indent=0]
1685+
----
1686+
include::{json-example-dir-hql}/JsonArrayTest.java[tags=hql-json-array-example]
1687+
----
1688+
====
1689+
1690+
Although database dependent, usually `null` values are `absent` in the resulting JSON array.
1691+
To retain `null` elements, use the `null on null` clause.
1692+
1693+
[[hql-json-array-on-null-example]]
1694+
====
1695+
[source, java, indent=0]
1696+
----
1697+
include::{json-example-dir-hql}/JsonArrayTest.java[tags=hql-json-array-on-null-example]
1698+
----
1699+
====
1700+
16351701
[[hql-json-value-function]]
16361702
===== `json_value()`
16371703
1638-
Extracts a value by https://www.ietf.org/archive/id/draft-goessner-dispatch-jsonpath-00.html[JSON path] from a JSON document.
1704+
Extracts a scalar value by https://www.ietf.org/archive/id/draft-goessner-dispatch-jsonpath-00.html[JSON path] from a JSON document.
16391705
1640-
[[hql-like-json-value-bnf]]
1706+
[[hql-json-value-bnf]]
16411707
[source, antlrv4, indent=0]
16421708
----
16431709
include::{extrasdir}/json_value_bnf.txt[]
16441710
----
16451711
16461712
The first argument is an expression to a JSON document. The second argument is a JSON path as String expression.
16471713
1714+
WARNING: Some databases might also return non-scalar values. Beware that this behavior is not portable.
1715+
16481716
NOTE: It is recommended to only us the dot notation for JSON paths, since most databases support only that.
16491717
16501718
[[hql-json-value-example]]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"json_array(" (expressionOrPredicate ("," expressionOrPredicate)* jsonNullClause?)? ")"
2+
3+
jsonNullClause
4+
: ("absent"|"null") "on null"
5+
;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"json_object(" (jsonObjectFunctionEntries jsonNullClause?)? ")"
2+
3+
jsonObjectFunctionEntries
4+
: expressionOrPredicate "," expressionOrPredicate ("," expressionOrPredicate "," expressionOrPredicate)*
5+
| jsonObjectFunctionEntry ("," jsonObjectFunctionEntry)*
6+
;
7+
8+
jsonObjectFunctionEntry
9+
: "key"? expressionOrPredicate "value" expressionOrPredicate
10+
| expressionOrPredicate ":" expressionOrPredicate
11+
;
12+
13+
jsonNullClause
14+
: ("absent"|"null") "on null"
15+
;

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
import static org.hibernate.type.SqlTypes.INET;
9292
import static org.hibernate.type.SqlTypes.INTEGER;
9393
import static org.hibernate.type.SqlTypes.JSON;
94+
import static org.hibernate.type.SqlTypes.JSON_ARRAY;
9495
import static org.hibernate.type.SqlTypes.LONG32NVARCHAR;
9596
import static org.hibernate.type.SqlTypes.LONG32VARBINARY;
9697
import static org.hibernate.type.SqlTypes.LONG32VARCHAR;
@@ -255,9 +256,11 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR
255256
if ( getVersion().isSameOrAfter( 20 ) ) {
256257
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( INET, "inet", this ) );
257258
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "jsonb", this ) );
259+
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON_ARRAY, "jsonb", this ) );
258260
}
259261
else {
260262
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "json", this ) );
263+
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON_ARRAY, "json", this ) );
261264
}
262265
ddlTypeRegistry.addDescriptor( new NamedNativeEnumDdlTypeImpl( this ) );
263266
ddlTypeRegistry.addDescriptor( new NamedNativeOrdinalEnumDdlTypeImpl( this ) );
@@ -362,19 +365,23 @@ protected void contributeCockroachTypes(TypeContributions typeContributions, Ser
362365
if ( getVersion().isSameOrAfter( 20, 0 ) ) {
363366
jdbcTypeRegistry.addDescriptorIfAbsent( PgJdbcHelper.getInetJdbcType( serviceRegistry ) );
364367
jdbcTypeRegistry.addDescriptorIfAbsent( PgJdbcHelper.getJsonbJdbcType( serviceRegistry ) );
368+
jdbcTypeRegistry.addDescriptorIfAbsent( PgJdbcHelper.getJsonbArrayJdbcType( serviceRegistry ) );
365369
}
366370
else {
367371
jdbcTypeRegistry.addDescriptorIfAbsent( PgJdbcHelper.getJsonJdbcType( serviceRegistry ) );
372+
jdbcTypeRegistry.addDescriptorIfAbsent( PgJdbcHelper.getJsonArrayJdbcType( serviceRegistry ) );
368373
}
369374
}
370375
else {
371376
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingIntervalSecondJdbcType.INSTANCE );
372377
if ( getVersion().isSameOrAfter( 20, 0 ) ) {
373378
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingInetJdbcType.INSTANCE );
374379
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonJdbcType.JSONB_INSTANCE );
380+
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonArrayJdbcType.JSONB_INSTANCE );
375381
}
376382
else {
377383
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonJdbcType.JSON_INSTANCE );
384+
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonArrayJdbcType.JSON_INSTANCE );
378385
}
379386
}
380387
}
@@ -384,9 +391,11 @@ protected void contributeCockroachTypes(TypeContributions typeContributions, Ser
384391
if ( getVersion().isSameOrAfter( 20, 0 ) ) {
385392
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingInetJdbcType.INSTANCE );
386393
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonJdbcType.JSONB_INSTANCE );
394+
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonArrayJdbcType.JSONB_INSTANCE );
387395
}
388396
else {
389397
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonJdbcType.JSON_INSTANCE );
398+
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonArrayJdbcType.JSON_INSTANCE );
390399
}
391400
}
392401

@@ -487,6 +496,8 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
487496
functionFactory.arrayToString_postgresql();
488497

489498
functionFactory.jsonValue_cockroachdb();
499+
functionFactory.jsonObject_postgresql();
500+
functionFactory.jsonArray_postgresql();
490501

491502
// Postgres uses # instead of ^ for XOR
492503
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "bitxor", "(?1#?2)" )

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,8 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
433433

434434
if ( getDB2Version().isSameOrAfter( 11 ) ) {
435435
functionFactory.jsonValue();
436+
functionFactory.jsonObject_db2();
437+
functionFactory.jsonArray_db2();
436438
}
437439
}
438440
}

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
import static org.hibernate.type.SqlTypes.GEOMETRY;
9898
import static org.hibernate.type.SqlTypes.INTERVAL_SECOND;
9999
import static org.hibernate.type.SqlTypes.JSON;
100+
import static org.hibernate.type.SqlTypes.JSON_ARRAY;
100101
import static org.hibernate.type.SqlTypes.LONG32NVARCHAR;
101102
import static org.hibernate.type.SqlTypes.LONG32VARBINARY;
102103
import static org.hibernate.type.SqlTypes.LONG32VARCHAR;
@@ -264,6 +265,7 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR
264265
}
265266
if ( getVersion().isSameOrAfter( 1, 4, 200 ) ) {
266267
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "json", this ) );
268+
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON_ARRAY, "json", this ) );
267269
}
268270
}
269271
ddlTypeRegistry.addDescriptor( new NativeEnumDdlTypeImpl( this ) );
@@ -294,6 +296,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry
294296
}
295297
if ( getVersion().isSameOrAfter( 1, 4, 200 ) ) {
296298
jdbcTypeRegistry.addDescriptorIfAbsent( H2JsonJdbcType.INSTANCE );
299+
jdbcTypeRegistry.addDescriptorIfAbsent( H2JsonArrayJdbcType.INSTANCE );
297300
}
298301
jdbcTypeRegistry.addDescriptor( EnumJdbcType.INSTANCE );
299302
jdbcTypeRegistry.addDescriptor( OrdinalEnumJdbcType.INSTANCE );
@@ -404,6 +407,9 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
404407
}
405408
}
406409
else {
410+
functionFactory.jsonObject();
411+
functionFactory.jsonArray();
412+
407413
// Use group_concat until 2.x as listagg was buggy
408414
functionFactory.listagg_groupConcat();
409415
}

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,11 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
270270
functionFactory.arrayFill_hsql();
271271
functionFactory.arrayToString_hsql();
272272

273+
if ( getVersion().isSameOrAfter( 2, 7 ) ) {
274+
functionFactory.jsonObject_hsqldb();
275+
functionFactory.jsonArray_hsqldb();
276+
}
277+
273278
//trim() requires parameters to be cast when used as trim character
274279
functionContributions.getFunctionRegistry().register( "trim", new TrimFunction(
275280
this,

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MariaDBLegacyDialect.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.hibernate.type.SqlTypes;
3232
import org.hibernate.type.StandardBasicTypes;
3333
import org.hibernate.type.descriptor.jdbc.JdbcType;
34+
import org.hibernate.type.descriptor.jdbc.JsonArrayJdbcType;
3435
import org.hibernate.type.descriptor.jdbc.JsonJdbcType;
3536
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
3637
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
@@ -92,6 +93,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
9293
.resolve( StandardBasicTypes.BOOLEAN )
9394
);
9495
commonFunctionFactory.jsonValue_mariadb();
96+
commonFunctionFactory.jsonArray_mariadb();
9597
if ( getVersion().isSameOrAfter( 10, 3, 3 ) ) {
9698
commonFunctionFactory.inverseDistributionOrderedSetAggregates_windowEmulation();
9799
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "median", "median(?1) over ()" )
@@ -141,6 +143,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry
141143
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration().getJdbcTypeRegistry();
142144
// Make sure we register the JSON type descriptor before calling super, because MariaDB does not need casting
143145
jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON, JsonJdbcType.INSTANCE );
146+
jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON_ARRAY, JsonArrayJdbcType.INSTANCE );
144147

145148
super.contributeTypes( typeContributions, serviceRegistry );
146149
if ( getVersion().isSameOrAfter( 10, 7 ) ) {

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,8 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
656656

657657
if ( getMySQLVersion().isSameOrAfter( 5, 7 ) ) {
658658
functionFactory.jsonValue_mysql();
659+
functionFactory.jsonObject_mysql();
660+
functionFactory.jsonArray_mysql();
659661
}
660662
}
661663

@@ -667,6 +669,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry
667669

668670
if ( getMySQLVersion().isSameOrAfter( 5, 7 ) ) {
669671
jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON, MySQLCastingJsonJdbcType.INSTANCE );
672+
jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON_ARRAY, MySQLCastingJsonArrayJdbcType.INSTANCE );
670673
}
671674

672675
// MySQL requires a custom binder for binding untyped nulls with the NULL type

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@
135135
import static org.hibernate.type.SqlTypes.GEOMETRY;
136136
import static org.hibernate.type.SqlTypes.INTEGER;
137137
import static org.hibernate.type.SqlTypes.JSON;
138+
import static org.hibernate.type.SqlTypes.JSON_ARRAY;
138139
import static org.hibernate.type.SqlTypes.NUMERIC;
139140
import static org.hibernate.type.SqlTypes.NVARCHAR;
140141
import static org.hibernate.type.SqlTypes.REAL;
@@ -323,6 +324,8 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
323324

324325
if ( getVersion().isSameOrAfter( 12 ) ) {
325326
functionFactory.jsonValue_literal_path();
327+
functionFactory.jsonObject_oracle();
328+
functionFactory.jsonArray_oracle();
326329
}
327330
}
328331

@@ -720,9 +723,11 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR
720723
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "MDSYS.SDO_GEOMETRY", this ) );
721724
if ( getVersion().isSameOrAfter( 21 ) ) {
722725
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "json", this ) );
726+
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON_ARRAY, "json", this ) );
723727
}
724728
else if ( getVersion().isSameOrAfter( 12 ) ) {
725729
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "blob", this ) );
730+
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON_ARRAY, "blob", this ) );
726731
}
727732
}
728733

0 commit comments

Comments
 (0)