Skip to content

Commit 016b463

Browse files
committed
HHH-18496 Add json_object and json_array functions
1 parent d5a3f04 commit 016b463

File tree

72 files changed

+3029
-45
lines changed

Some content is hidden

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

72 files changed

+3029
-45
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;
@@ -261,9 +262,11 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR
261262
if ( getVersion().isSameOrAfter( 20 ) ) {
262263
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( INET, "inet", this ) );
263264
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "jsonb", this ) );
265+
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON_ARRAY, "jsonb", this ) );
264266
}
265267
else {
266268
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "json", this ) );
269+
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON_ARRAY, "json", this ) );
267270
}
268271
ddlTypeRegistry.addDescriptor( new NamedNativeEnumDdlTypeImpl( this ) );
269272
ddlTypeRegistry.addDescriptor( new NamedNativeOrdinalEnumDdlTypeImpl( this ) );
@@ -368,19 +371,23 @@ protected void contributeCockroachTypes(TypeContributions typeContributions, Ser
368371
if ( getVersion().isSameOrAfter( 20, 0 ) ) {
369372
jdbcTypeRegistry.addDescriptorIfAbsent( PgJdbcHelper.getInetJdbcType( serviceRegistry ) );
370373
jdbcTypeRegistry.addDescriptorIfAbsent( PgJdbcHelper.getJsonbJdbcType( serviceRegistry ) );
374+
jdbcTypeRegistry.addDescriptorIfAbsent( PgJdbcHelper.getJsonbArrayJdbcType( serviceRegistry ) );
371375
}
372376
else {
373377
jdbcTypeRegistry.addDescriptorIfAbsent( PgJdbcHelper.getJsonJdbcType( serviceRegistry ) );
378+
jdbcTypeRegistry.addDescriptorIfAbsent( PgJdbcHelper.getJsonArrayJdbcType( serviceRegistry ) );
374379
}
375380
}
376381
else {
377382
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingIntervalSecondJdbcType.INSTANCE );
378383
if ( getVersion().isSameOrAfter( 20, 0 ) ) {
379384
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingInetJdbcType.INSTANCE );
380385
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonJdbcType.JSONB_INSTANCE );
386+
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonArrayJdbcType.JSONB_INSTANCE );
381387
}
382388
else {
383389
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonJdbcType.JSON_INSTANCE );
390+
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonArrayJdbcType.JSON_INSTANCE );
384391
}
385392
}
386393
}
@@ -390,9 +397,11 @@ protected void contributeCockroachTypes(TypeContributions typeContributions, Ser
390397
if ( getVersion().isSameOrAfter( 20, 0 ) ) {
391398
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingInetJdbcType.INSTANCE );
392399
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonJdbcType.JSONB_INSTANCE );
400+
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonArrayJdbcType.JSONB_INSTANCE );
393401
}
394402
else {
395403
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonJdbcType.JSON_INSTANCE );
404+
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonArrayJdbcType.JSON_INSTANCE );
396405
}
397406
}
398407

@@ -493,6 +502,8 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
493502
functionFactory.arrayToString_postgresql();
494503

495504
functionFactory.jsonValue_cockroachdb();
505+
functionFactory.jsonObject_postgresql();
506+
functionFactory.jsonArray_postgresql();
496507

497508
// Postgres uses # instead of ^ for XOR
498509
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
@@ -432,6 +432,8 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
432432

433433
if ( getDB2Version().isSameOrAfter( 11 ) ) {
434434
functionFactory.jsonValue();
435+
functionFactory.jsonObject_db2();
436+
functionFactory.jsonArray_db2();
435437
}
436438
}
437439
}

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
@@ -29,6 +29,7 @@
2929
import org.hibernate.type.SqlTypes;
3030
import org.hibernate.type.StandardBasicTypes;
3131
import org.hibernate.type.descriptor.jdbc.JdbcType;
32+
import org.hibernate.type.descriptor.jdbc.JsonArrayJdbcType;
3233
import org.hibernate.type.descriptor.jdbc.JsonJdbcType;
3334
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
3435
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
@@ -90,6 +91,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
9091
.resolve( StandardBasicTypes.BOOLEAN )
9192
);
9293
commonFunctionFactory.jsonValue_mariadb();
94+
commonFunctionFactory.jsonArray_mariadb();
9395
if ( getVersion().isSameOrAfter( 10, 3, 3 ) ) {
9496
commonFunctionFactory.inverseDistributionOrderedSetAggregates_windowEmulation();
9597
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "median", "median(?1) over ()" )
@@ -139,6 +141,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry
139141
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration().getJdbcTypeRegistry();
140142
// Make sure we register the JSON type descriptor before calling super, because MariaDB does not need casting
141143
jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON, JsonJdbcType.INSTANCE );
144+
jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON_ARRAY, JsonArrayJdbcType.INSTANCE );
142145

143146
super.contributeTypes( typeContributions, serviceRegistry );
144147
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
@@ -654,6 +654,8 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
654654

655655
if ( getMySQLVersion().isSameOrAfter( 5, 7 ) ) {
656656
functionFactory.jsonValue_mysql();
657+
functionFactory.jsonObject_mysql();
658+
functionFactory.jsonArray_mysql();
657659
}
658660
}
659661

@@ -665,6 +667,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry
665667

666668
if ( getMySQLVersion().isSameOrAfter( 5, 7 ) ) {
667669
jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON, MySQLCastingJsonJdbcType.INSTANCE );
670+
jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON_ARRAY, MySQLCastingJsonArrayJdbcType.INSTANCE );
668671
}
669672

670673
// 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: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@
125125
import static org.hibernate.type.SqlTypes.ARRAY;
126126
import static org.hibernate.type.SqlTypes.BIGINT;
127127
import static org.hibernate.type.SqlTypes.BINARY;
128+
import static org.hibernate.type.SqlTypes.BIT;
128129
import static org.hibernate.type.SqlTypes.BOOLEAN;
129130
import static org.hibernate.type.SqlTypes.DATE;
130131
import static org.hibernate.type.SqlTypes.DECIMAL;
@@ -133,6 +134,7 @@
133134
import static org.hibernate.type.SqlTypes.GEOMETRY;
134135
import static org.hibernate.type.SqlTypes.INTEGER;
135136
import static org.hibernate.type.SqlTypes.JSON;
137+
import static org.hibernate.type.SqlTypes.JSON_ARRAY;
136138
import static org.hibernate.type.SqlTypes.NUMERIC;
137139
import static org.hibernate.type.SqlTypes.NVARCHAR;
138140
import static org.hibernate.type.SqlTypes.REAL;
@@ -321,6 +323,8 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
321323

322324
if ( getVersion().isSameOrAfter( 12 ) ) {
323325
functionFactory.jsonValue_literal_path();
326+
functionFactory.jsonObject_oracle();
327+
functionFactory.jsonArray_oracle();
324328
}
325329
}
326330

@@ -673,6 +677,7 @@ private void extractField(StringBuilder pattern, TemporalUnit unit, TemporalUnit
673677
protected String columnType(int sqlTypeCode) {
674678
switch ( sqlTypeCode ) {
675679
case BOOLEAN:
680+
case BIT:
676681
// still, after all these years...
677682
return "number(1,0)";
678683

@@ -731,9 +736,11 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR
731736
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "MDSYS.SDO_GEOMETRY", this ) );
732737
if ( getVersion().isSameOrAfter( 21 ) ) {
733738
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "json", this ) );
739+
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON_ARRAY, "json", this ) );
734740
}
735741
else if ( getVersion().isSameOrAfter( 12 ) ) {
736742
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "blob", this ) );
743+
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON_ARRAY, "blob", this ) );
737744
}
738745
}
739746

0 commit comments

Comments
 (0)