HHH-17375 Shorthand bracket syntax for array slicing

This commit is contained in:
Christian Beikov 2024-05-07 08:15:25 +02:00
parent 67d04577be
commit b74992198c
4 changed files with 75 additions and 10 deletions

View File

@ -1458,14 +1458,26 @@ include::{array-example-dir-hql}/ArrayRemoveIndexTest.java[tags=hql-array-remove
[[hql-array-slice-functions]] [[hql-array-slice-functions]]
===== `array_slice()` ===== `array_slice()`
Returns the sub-array as specified by the given start and end index. Returns `null` if any of the arguments is `null` Returns the sub-array as specified by the given 1-based inclusive start and end index. Returns `null` if any of the arguments is `null`
and also if the index is out of bounds. and also if the index is out of bounds.
[[hql-array-remove-index-example]] [[hql-array-slice-example]]
==== ====
[source, JAVA, indent=0] [source, JAVA, indent=0]
---- ----
include::{array-example-dir-hql}/ArrayRemoveIndexTest.java[tags=hql-array-remove-index-example] include::{array-example-dir-hql}/ArraySliceTest.java[tags=hql-array-slice-example]
----
====
Alternatively, it's also possible to slice an array by specifying the lower and upper bound,
separated by a colon, as index in the bracket array index syntax `array[lowerIndex:upperIndex]`.
This is syntax sugar that translates to the `array_slice` function.
[[hql-array-slice-hql-example]]
====
[source, JAVA, indent=0]
----
include::{array-example-dir-hql}/ArraySliceTest.java[tags=hql-array-slice-hql-example]
---- ----
==== ====

View File

@ -438,9 +438,11 @@ syntacticDomainPath
| collectionValueNavigablePath | collectionValueNavigablePath
| mapKeyNavigablePath | mapKeyNavigablePath
| simplePath indexedPathAccessFragment | simplePath indexedPathAccessFragment
| simplePath slicedPathAccessFragment
| toOneFkReference | toOneFkReference
| function pathContinuation | function pathContinuation
| function indexedPathAccessFragment pathContinuation? | function indexedPathAccessFragment pathContinuation?
| function slicedPathAccessFragment
; ;
/** /**
@ -463,6 +465,13 @@ indexedPathAccessFragment
: LEFT_BRACKET expression RIGHT_BRACKET (DOT generalPathFragment)? : LEFT_BRACKET expression RIGHT_BRACKET (DOT generalPathFragment)?
; ;
/**
* The slice operator to obtain elements between the lower and upper bound.
*/
slicedPathAccessFragment
: LEFT_BRACKET expression COLON expression RIGHT_BRACKET
;
/** /**
* A 'treat()' function that "breaks" a path expression * A 'treat()' function that "breaks" a path expression
*/ */

View File

@ -5205,6 +5205,20 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
return visitToOneFkReference( ctx.toOneFkReference() ); return visitToOneFkReference( ctx.toOneFkReference() );
} }
else if ( ctx.function() != null ) { else if ( ctx.function() != null ) {
final HqlParser.SlicedPathAccessFragmentContext slicedFragmentsCtx = ctx.slicedPathAccessFragment();
if ( slicedFragmentsCtx != null ) {
final List<HqlParser.ExpressionContext> slicedFragments = slicedFragmentsCtx.expression();
return getFunctionDescriptor( "array_slice" ).generateSqmExpression(
List.of(
(SqmTypedNode<?>) visitFunction( ctx.function() ),
(SqmTypedNode<?>) slicedFragments.get( 0 ).accept( this ),
(SqmTypedNode<?>) slicedFragments.get( 1 ).accept( this )
),
null,
creationContext.getQueryEngine()
);
}
else {
return visitPathContinuation( return visitPathContinuation(
visitIndexedPathAccessFragment( visitIndexedPathAccessFragment(
(SemanticPathPart) visitFunction( ctx.function() ), (SemanticPathPart) visitFunction( ctx.function() ),
@ -5213,9 +5227,22 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
ctx.pathContinuation() ctx.pathContinuation()
); );
} }
}
else if ( ctx.simplePath() != null && ctx.indexedPathAccessFragment() != null ) { else if ( ctx.simplePath() != null && ctx.indexedPathAccessFragment() != null ) {
return visitIndexedPathAccessFragment( visitSimplePath( ctx.simplePath() ), ctx.indexedPathAccessFragment() ); return visitIndexedPathAccessFragment( visitSimplePath( ctx.simplePath() ), ctx.indexedPathAccessFragment() );
} }
else if ( ctx.simplePath() != null && ctx.slicedPathAccessFragment() != null ) {
final List<HqlParser.ExpressionContext> slicedFragments = ctx.slicedPathAccessFragment().expression();
return getFunctionDescriptor( "array_slice" ).generateSqmExpression(
List.of(
(SqmTypedNode<?>) visitSimplePath( ctx.simplePath() ),
(SqmTypedNode<?>) slicedFragments.get( 0 ).accept( this ),
(SqmTypedNode<?>) slicedFragments.get( 1 ).accept( this )
),
null,
creationContext.getQueryEngine()
);
}
else { else {
throw new ParsingException( "Illegal domain path '" + ctx.getText() + "'" ); throw new ParsingException( "Illegal domain path '" + ctx.getText() + "'" );
} }

View File

@ -139,4 +139,21 @@ public class ArraySliceTest {
} ); } );
} }
@Test
public void testSliceSyntax(SessionFactoryScope scope) {
scope.inSession( em -> {
//tag::hql-array-slice-hql-example[]
List<Tuple> results = em.createQuery( "select e.id, e.theArray[1:1] from EntityWithArrays e order by e.id", Tuple.class )
.getResultList();
//end::hql-array-slice-hql-example[]
assertEquals( 3, results.size() );
assertEquals( 1L, results.get( 0 ).get( 0 ) );
assertArrayEquals( new String[0], results.get( 0 ).get( 1, String[].class ) );
assertEquals( 2L, results.get( 1 ).get( 0 ) );
assertArrayEquals( new String[] { "abc" }, results.get( 1 ).get( 1, String[].class ) );
assertEquals( 3L, results.get( 2 ).get( 0 ) );
assertNull( results.get( 2 ).get( 1, String[].class ) );
} );
}
} }