Fix a few more tests, simplify collation handling, HQL parsing support for tuples, revert wrong dialect changes, alias support for group by and order by, implement index optimized tuple comparison emulation

This commit is contained in:
Christian Beikov 2020-11-27 18:11:23 +01:00
parent d9446e7c77
commit 3b730ac376
18 changed files with 897 additions and 332 deletions

View File

@ -277,9 +277,14 @@ mapKeyNavigablePath
// GROUP BY clause
groupByClause
: GROUP BY expression ( COMMA expression )*
: GROUP BY groupByExpression ( COMMA groupByExpression )*
;
groupByExpression
: identifier collationSpecification?
| INTEGER_LITERAL collationSpecification?
| expression
;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//HAVING clause
@ -396,23 +401,28 @@ likeEscape
expression
//highest to lowest precedence
: LEFT_PAREN expression RIGHT_PAREN # GroupedExpression
| LEFT_PAREN subQuery RIGHT_PAREN # SubQueryExpression
| caseList collationSpecification? # CaseExpression
| literal collationSpecification? # LiteralExpression
| parameter collationSpecification? # ParameterExpression
: LEFT_PAREN expression RIGHT_PAREN # GroupedExpression
| LEFT_PAREN expression (COMMA expression)* RIGHT_PAREN # TupleExpression
| LEFT_PAREN subQuery RIGHT_PAREN # SubQueryExpression
| primaryExpression collationSpecification? # CollateExpression
| signOperator expression # UnaryExpression
| expression datetimeField # ToDurationExpression
| expression BY datetimeField # FromDurationExpression
| expression multiplicativeOperator expression # MultiplicationExpression
| expression additiveOperator expression # AdditionExpression
| expression DOUBLE_PIPE expression # ConcatenationExpression
;
primaryExpression
: caseList # CaseExpression
| literal # LiteralExpression
| parameter # ParameterExpression
| entityTypeReference # EntityTypeExpression
| entityIdReference collationSpecification? # EntityIdExpression
| entityVersionReference collationSpecification? # EntityVersionExpression
| entityNaturalIdReference collationSpecification? # EntityNaturalIdExpression
| path collationSpecification? # PathExpression
| function collationSpecification? # FunctionExpression
| signOperator expression # UnaryExpression
| expression datetimeField # ToDurationExpression
| expression BY datetimeField # FromDurationExpression
| expression multiplicativeOperator expression # MultiplicationExpression
| expression additiveOperator expression # AdditionExpression
| expression DOUBLE_PIPE expression # ConcatenationExpression
| entityIdReference # EntityIdExpression
| entityVersionReference # EntityVersionExpression
| entityNaturalIdReference # EntityNaturalIdExpression
| path # PathExpression
| function # FunctionExpression
;
multiplicativeOperator

View File

@ -96,6 +96,11 @@ public class DB2Dialect extends Dialect {
registerColumnType( Types.BINARY, "varchar($l) for bit data" ); //should use 'binary' since version 11
registerColumnType( Types.BINARY, 254, "char($l) for bit data" ); //should use 'binary' since version 11
registerColumnType( Types.VARBINARY, "varchar($l) for bit data" ); //should use 'varbinary' since version 11
//prior to DB2 11, the 'boolean' type existed,
//but was not allowed as a column type
registerColumnType( Types.BOOLEAN, "smallint" );
registerColumnType( Types.BIT, 1, "smallint" );
}
registerColumnType( Types.BLOB, "blob($l)" );
@ -560,7 +565,10 @@ public class DB2Dialect extends Dialect {
@Override
protected SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) {
if ( getVersion() < 970 ) {
if ( getVersion() < 1100 && sqlCode == Types.BOOLEAN ) {
return SmallIntTypeDescriptor.INSTANCE;
}
else if ( getVersion() < 970 ) {
return sqlCode == Types.NUMERIC
? DecimalTypeDescriptor.INSTANCE
: super.getSqlTypeDescriptorOverride(sqlCode);
@ -572,8 +580,6 @@ public class DB2Dialect extends Dialect {
// Therefore here we overwrite the sql type descriptors to
// use the non-N variants which are supported.
switch ( sqlCode ) {
case Types.BOOLEAN:
return SmallIntTypeDescriptor.INSTANCE;
case Types.NCHAR:
return CharTypeDescriptor.INSTANCE;
case Types.NCLOB:

View File

@ -88,17 +88,20 @@ public class DerbyDialect extends Dialect {
super();
this.version = version;
registerColumnType( Types.BIT, 1, "boolean" ); //no bit
registerColumnType( Types.BIT, "smallint" ); //no bit
if ( getVersion() < 1070) {
registerColumnType( Types.BOOLEAN, "smallint" ); //no boolean before 10.7
registerColumnType( Types.BIT, 1, "smallint" ); //no bit
}
else {
registerColumnType( Types.BIT, 1, "boolean" ); //no bit
}
registerColumnType( Types.TINYINT, "smallint" ); //no tinyint
registerColumnType( Types.CHAR, "char(1)" );
registerColumnType( Types.CHAR, 254, "char($l)" );
//HHH-12827: map them both to the same type to
// avoid problems with schema update
// registerColumnType( Types.DECIMAL, "decimal($p,$s)" );
registerColumnType( Types.NUMERIC, "decimal($p,$s)" );
registerColumnType( Types.FLOAT, "float" );
registerColumnType( Types.DOUBLE, "double" );
registerColumnType( Types.BINARY, "varchar($l) for bit data" );
registerColumnType( Types.BINARY, 254, "char($l) for bit data" );
@ -453,9 +456,10 @@ public class DerbyDialect extends Dialect {
}
protected SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) {
if ( getVersion() < 1070 && sqlCode == Types.BOOLEAN) {
return SmallIntTypeDescriptor.INSTANCE;
}
switch ( sqlCode ) {
case Types.BOOLEAN:
return SmallIntTypeDescriptor.INSTANCE;
case Types.NUMERIC:
return DecimalTypeDescriptor.INSTANCE;
case Types.TIMESTAMP_WITH_TIMEZONE:

View File

@ -1258,7 +1258,8 @@ public abstract class Dialect implements ConversionContext {
* @return the SQL equivalent to Oracle's {@code from dual}.
*/
public String getFromDual() {
return "";
// The standard SQL solution to get a dual table is to use the VALUES clause
return "from (values (0)) as dual";
}
// limit/offset support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -2621,6 +2622,10 @@ public abstract class Dialect implements ConversionContext {
return false;
}
public boolean supportsRowValueConstructorSyntaxInInSubquery() {
return supportsRowValueConstructorSyntaxInInList();
}
/**
* Is this dialect known to support ROLLUP functions in the GROUP BY clause.
*

View File

@ -911,6 +911,11 @@ public class OracleDialect extends Dialect {
*/
@Override
public boolean supportsRowValueConstructorSyntaxInInList() {
return false;
}
@Override
public boolean supportsRowValueConstructorSyntaxInInSubquery() {
return getVersion() >= 9;
}

View File

@ -22,6 +22,16 @@ public enum ComparisonOperator {
return EQUAL;
}
@Override
public ComparisonOperator broader() {
return EQUAL;
}
@Override
public ComparisonOperator sharper() {
return EQUAL;
}
@Override
public String sqlText() {
return "=";
@ -39,6 +49,16 @@ public enum ComparisonOperator {
return NOT_EQUAL;
}
@Override
public ComparisonOperator broader() {
return NOT_EQUAL;
}
@Override
public ComparisonOperator sharper() {
return NOT_EQUAL;
}
@Override
public String sqlText() {
return "!=";
@ -56,6 +76,16 @@ public enum ComparisonOperator {
return GREATER_THAN;
}
@Override
public ComparisonOperator broader() {
return LESS_THAN_OR_EQUAL;
}
@Override
public ComparisonOperator sharper() {
return LESS_THAN;
}
@Override
public String sqlText() {
return "<";
@ -73,6 +103,16 @@ public enum ComparisonOperator {
return GREATER_THAN_OR_EQUAL;
}
@Override
public ComparisonOperator broader() {
return LESS_THAN_OR_EQUAL;
}
@Override
public ComparisonOperator sharper() {
return LESS_THAN;
}
@Override
public String sqlText() {
return "<=";
@ -90,6 +130,16 @@ public enum ComparisonOperator {
return LESS_THAN;
}
@Override
public ComparisonOperator broader() {
return GREATER_THAN_OR_EQUAL;
}
@Override
public ComparisonOperator sharper() {
return GREATER_THAN;
}
@Override
public String sqlText() {
return ">";
@ -107,6 +157,16 @@ public enum ComparisonOperator {
return LESS_THAN_OR_EQUAL;
}
@Override
public ComparisonOperator broader() {
return GREATER_THAN_OR_EQUAL;
}
@Override
public ComparisonOperator sharper() {
return GREATER_THAN;
}
@Override
public String sqlText() {
return ">=";
@ -115,5 +175,7 @@ public enum ComparisonOperator {
public abstract ComparisonOperator negated();
public abstract ComparisonOperator invert();
public abstract ComparisonOperator broader();
public abstract ComparisonOperator sharper();
public abstract String sqlText();
}

View File

@ -120,6 +120,7 @@ import org.hibernate.query.sqm.tree.expression.SqmPositionalParameter;
import org.hibernate.query.sqm.tree.expression.SqmStar;
import org.hibernate.query.sqm.tree.expression.SqmToDuration;
import org.hibernate.query.sqm.tree.expression.SqmTrimSpecification;
import org.hibernate.query.sqm.tree.expression.SqmTuple;
import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation;
import org.hibernate.query.sqm.tree.from.DowncastLocation;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
@ -166,6 +167,7 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.jboss.logging.Logger;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
@ -221,6 +223,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
private final Stack<ParameterDeclarationContext> parameterDeclarationContextStack = new StandardStack<>();
private final Stack<SqmCreationProcessingState> processingStateStack = new StandardStack<>();
private final Stack<SqmQuerySpec<?>> querySpecStack = new StandardStack<>();
private ParameterCollector parameterCollector;
@ -474,79 +477,86 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
@Override
public SqmQuerySpec<?> visitQuerySpec(HqlParser.QuerySpecContext ctx) {
final SqmQuerySpec<?> sqmQuerySpec = new SqmQuerySpec<>( creationContext.getNodeBuilder() );
querySpecStack.push( sqmQuerySpec );
// visit from-clause first!!!
treatHandlerStack.push( new TreatHandlerFromClause() );
try {
sqmQuerySpec.setFromClause( visitFromClause( ctx.fromClause() ) );
}
finally {
treatHandlerStack.pop();
}
final SqmSelectClause selectClause;
if ( ctx.selectClause() != null ) {
selectClause = visitSelectClause( ctx.selectClause() );
}
else {
log.debugf( "Encountered implicit select clause : %s", ctx.getText() );
selectClause = buildInferredSelectClause( sqmQuerySpec.getFromClause() );
}
sqmQuerySpec.setSelectClause( selectClause );
final SqmWhereClause whereClause = new SqmWhereClause( creationContext.getNodeBuilder() );
if ( ctx.whereClause() != null ) {
treatHandlerStack.push( new TreatHandlerNormal( DowncastLocation.WHERE ) );
// visit from-clause first!!!
treatHandlerStack.push( new TreatHandlerFromClause() );
try {
whereClause.setPredicate( (SqmPredicate) ctx.whereClause().accept( this ) );
sqmQuerySpec.setFromClause( visitFromClause( ctx.fromClause() ) );
}
finally {
treatHandlerStack.pop();
}
}
sqmQuerySpec.setWhereClause( whereClause );
final HqlParser.GroupByClauseContext groupByClauseContext = ctx.groupByClause();
if ( groupByClauseContext != null ) {
sqmQuerySpec.setGroupByClauseExpressions( visitGroupByClause( groupByClauseContext ) );
}
final HqlParser.HavingClauseContext havingClauseContext = ctx.havingClause();
if ( havingClauseContext != null ) {
sqmQuerySpec.setHavingClausePredicate( visitHavingClause( havingClauseContext ) );
}
final SqmSelectClause selectClause;
if ( ctx.selectClause() != null ) {
selectClause = visitSelectClause( ctx.selectClause() );
}
else {
log.debugf( "Encountered implicit select clause : %s", ctx.getText() );
selectClause = buildInferredSelectClause( sqmQuerySpec.getFromClause() );
}
sqmQuerySpec.setSelectClause( selectClause );
final SqmOrderByClause orderByClause;
final HqlParser.OrderByClauseContext orderByClauseContext = ctx.orderByClause();
if ( orderByClauseContext != null ) {
if ( creationOptions.useStrictJpaCompliance() && processingStateStack.depth() > 1 ) {
throw new StrictJpaComplianceViolation(
StrictJpaComplianceViolation.Type.SUBQUERY_ORDER_BY
);
final SqmWhereClause whereClause = new SqmWhereClause( creationContext.getNodeBuilder() );
if ( ctx.whereClause() != null ) {
treatHandlerStack.push( new TreatHandlerNormal( DowncastLocation.WHERE ) );
try {
whereClause.setPredicate( (SqmPredicate) ctx.whereClause().accept( this ) );
}
finally {
treatHandlerStack.pop();
}
}
sqmQuerySpec.setWhereClause( whereClause );
final HqlParser.GroupByClauseContext groupByClauseContext = ctx.groupByClause();
if ( groupByClauseContext != null ) {
sqmQuerySpec.setGroupByClauseExpressions( visitGroupByClause( groupByClauseContext ) );
}
final HqlParser.HavingClauseContext havingClauseContext = ctx.havingClause();
if ( havingClauseContext != null ) {
sqmQuerySpec.setHavingClausePredicate( visitHavingClause( havingClauseContext ) );
}
orderByClause = visitOrderByClause( orderByClauseContext );
}
else {
orderByClause = new SqmOrderByClause();
}
sqmQuerySpec.setOrderByClause( orderByClause );
final SqmOrderByClause orderByClause;
final HqlParser.OrderByClauseContext orderByClauseContext = ctx.orderByClause();
if ( orderByClauseContext != null ) {
if ( creationOptions.useStrictJpaCompliance() && processingStateStack.depth() > 1 ) {
throw new StrictJpaComplianceViolation(
StrictJpaComplianceViolation.Type.SUBQUERY_ORDER_BY
);
}
if ( ctx.limitClause() != null || ctx.offsetClause() != null ) {
if ( getCreationOptions().useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation(
StrictJpaComplianceViolation.Type.LIMIT_OFFSET_CLAUSE
);
orderByClause = visitOrderByClause( orderByClauseContext );
}
if ( processingStateStack.depth() > 1 && orderByClause == null ) {
throw new SemanticException( "limit and offset clause require an order-by clause when used in sub-query" );
else {
orderByClause = new SqmOrderByClause();
}
sqmQuerySpec.setOrderByClause( orderByClause );
//noinspection unchecked
sqmQuerySpec.setOffsetExpression( visitOffsetClause( ctx.offsetClause() ) );
//noinspection unchecked
sqmQuerySpec.setLimitExpression( visitLimitClause( ctx.limitClause() ) );
if ( ctx.limitClause() != null || ctx.offsetClause() != null ) {
if ( getCreationOptions().useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation(
StrictJpaComplianceViolation.Type.LIMIT_OFFSET_CLAUSE
);
}
if ( processingStateStack.depth() > 1 && orderByClause == null ) {
throw new SemanticException(
"limit and offset clause require an order-by clause when used in sub-query" );
}
//noinspection unchecked
sqmQuerySpec.setOffsetExpression( visitOffsetClause( ctx.offsetClause() ) );
//noinspection unchecked
sqmQuerySpec.setLimitExpression( visitLimitClause( ctx.limitClause() ) );
}
}
finally {
querySpecStack.pop();
}
return sqmQuerySpec;
@ -756,12 +766,59 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
@Override
public List<SqmExpression<?>> visitGroupByClause(HqlParser.GroupByClauseContext ctx) {
final List<HqlParser.ExpressionContext> expressionContexts = ctx.expression();
final List<SqmExpression<?>> expressions = new ArrayList<>( expressionContexts.size() );
for ( HqlParser.ExpressionContext expressionContext : expressionContexts ) {
expressions.add( (SqmExpression<?>) expressionContext.accept( this ) );
return visitExpressions( ctx.groupByExpression() );
}
@Override
public SqmExpression<?> visitGroupByExpression(HqlParser.GroupByExpressionContext ctx) {
if ( ctx.INTEGER_LITERAL() != null ) {
// This is syntactically disallowed
if ( ctx.collationSpecification() != null ) {
throw new ParsingException( "COLLATE is not allowed for position based group by items!" );
}
final int position = Integer.parseInt( ctx.INTEGER_LITERAL().getText() );
final SqmSelection<?> selection = getCurrentProcessingState().getPathRegistry().findSelectionByPosition( position );
if ( selection == null ) {
throw new ParsingException( "Invalid select item position " + position + " used for order by item!" );
}
return new SqmLiteral<>(
position,
resolveExpressableTypeBasic( Integer.class ),
creationContext.getNodeBuilder()
);
}
return expressions;
if ( ctx.identifier() != null ) {
final HqlParser.CollationSpecificationContext collationSpecificationContext = ctx.collationSpecification();
final SqmSelection<?> selection = getCurrentProcessingState().getPathRegistry().findSelectionByAlias( ctx.identifier().getText() );
if ( selection != null ) {
// This is syntactically disallowed
if ( collationSpecificationContext != null ) {
throw new ParsingException( "COLLATE is not allowed for alias based group by items!" );
}
return new SqmLiteral<>(
getSelectionPosition( selection ),
resolveExpressableTypeBasic( Integer.class ),
creationContext.getNodeBuilder()
);
}
final SqmFrom<?, ?> sqmFrom = getCurrentProcessingState().getPathRegistry().findFromByAlias( ctx.identifier().getText() );
if ( sqmFrom != null ) {
// This is syntactically disallowed
if ( collationSpecificationContext != null ) {
throw new ParsingException( "COLLATE is not allowed for alias based group by items!" );
}
return sqmFrom;
}
final DotIdentifierConsumer dotIdentifierConsumer = dotIdentifierConsumerStack.getCurrent();
dotIdentifierConsumer.consumeIdentifier( ctx.getText(), true, true );
return (SqmExpression<?>) dotIdentifierConsumer.getConsumedPart();
}
return (SqmExpression<?>) ctx.expression().accept( this );
}
@Override
@ -824,53 +881,60 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
@Override
public SqmExpression<?> visitSortExpression(HqlParser.SortExpressionContext ctx) {
if ( ctx.INTEGER_LITERAL() != null ) {
final HqlParser.CollationSpecificationContext collationSpecificationContext = ctx.collationSpecification();
// This is syntactically disallowed
if ( ctx.collationSpecification() != null ) {
throw new ParsingException( "COLLATE is not allowed for position based order by items!" );
}
final int position = Integer.parseInt( ctx.INTEGER_LITERAL().getText() );
final SqmSelection selection = getCurrentProcessingState().getPathRegistry().findSelectionByPosition( position );
if ( selection != null ) {
final SqmSelectableNode selectableNode = selection.getSelectableNode();
if ( selectableNode instanceof SqmExpression ) {
return wrapCollate( (SqmExpression) selectableNode, collationSpecificationContext );
}
final SqmSelection<?> selection = getCurrentProcessingState().getPathRegistry().findSelectionByPosition( position );
if ( selection == null ) {
throw new ParsingException( "Invalid select item position " + position + " used for order by item!" );
}
return wrapCollate(
new SqmLiteral<>(
position,
resolveExpressableTypeBasic( Integer.class ),
creationContext.getNodeBuilder()
),
collationSpecificationContext
return new SqmLiteral<>(
position,
resolveExpressableTypeBasic( Integer.class ),
creationContext.getNodeBuilder()
);
}
if ( ctx.identifier() != null ) {
final HqlParser.CollationSpecificationContext collationSpecificationContext = ctx.collationSpecification();
final SqmSelection selection = getCurrentProcessingState().getPathRegistry().findSelectionByAlias( ctx.identifier().getText() );
final SqmSelection<?> selection = getCurrentProcessingState().getPathRegistry().findSelectionByAlias( ctx.identifier().getText() );
if ( selection != null ) {
final SqmSelectableNode selectableNode = selection.getSelectableNode();
if ( selectableNode instanceof SqmExpression ) {
return wrapCollate( (SqmExpression) selectableNode, collationSpecificationContext );
// This is syntactically disallowed
if ( collationSpecificationContext != null ) {
throw new ParsingException( "COLLATE is not allowed for alias based order by items!" );
}
return new SqmLiteral<>(
getSelectionPosition( selection ),
resolveExpressableTypeBasic( Integer.class ),
creationContext.getNodeBuilder()
);
}
final SqmFrom sqmFrom = getCurrentProcessingState().getPathRegistry().findFromByAlias( ctx.identifier().getText() );
final SqmFrom<?, ?> sqmFrom = getCurrentProcessingState().getPathRegistry().findFromByAlias( ctx.identifier().getText() );
if ( sqmFrom != null ) {
assert collationSpecificationContext == null;
// This is syntactically disallowed
if ( collationSpecificationContext != null ) {
throw new ParsingException( "COLLATE is not allowed for alias based order by items!" );
}
return sqmFrom;
}
final DotIdentifierConsumer dotIdentifierConsumer = dotIdentifierConsumerStack.getCurrent();
dotIdentifierConsumer.consumeIdentifier( ctx.getText(), true, true );
return wrapCollate(
(SqmExpression<?>) dotIdentifierConsumer.getConsumedPart(),
collationSpecificationContext
);
return (SqmExpression<?>) dotIdentifierConsumer.getConsumedPart();
}
return (SqmExpression<?>) ctx.expression().accept( this );
}
private int getSelectionPosition(SqmSelection<?> selection) {
final SqmQuerySpec<?> querySpec = querySpecStack.getCurrent();
return querySpec.getSelectClause().getSelections().indexOf( selection ) + 1;
}
@Override
public SqmExpression<?> visitLimitClause(HqlParser.LimitClauseContext ctx) {
if ( ctx == null ) {
@ -891,18 +955,12 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
@Override
public SqmExpression<?> visitPathExpression(HqlParser.PathExpressionContext ctx) {
return wrapCollate(
(SqmExpression<?>) ctx.path().accept( this ),
ctx.collationSpecification()
);
return (SqmExpression<?>) ctx.path().accept( this );
}
@Override
public SqmExpression<?> visitFunctionExpression(HqlParser.FunctionExpressionContext ctx) {
return wrapCollate(
(SqmExpression<?>) ctx.function().accept( this ),
ctx.collationSpecification()
);
return (SqmExpression<?>) ctx.function().accept( this );
}
@Override
@ -1382,8 +1440,10 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
private Map<Class<?>, Enum<?>> getPossibleEnumValues(HqlParser.ExpressionContext expressionContext) {
ParseTree ctx;
// Traverse the expression structure according to the grammar
if ( expressionContext instanceof HqlParser.PathExpressionContext
if ( expressionContext instanceof HqlParser.CollateExpressionContext
&& expressionContext.getChildCount() == 1
&& ( ctx = expressionContext.getChild( 0 ) ) instanceof HqlParser.PrimaryExpressionContext
&& ctx.getChildCount() == 1
&& ( ctx = expressionContext.getChild( 0 ) ) instanceof HqlParser.PathContext
&& ctx.getChildCount() == 1
&& ( ctx = ctx.getChild( 0 ) ) instanceof HqlParser.GeneralPathFragmentContext
@ -1542,10 +1602,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
@Override
public SqmExpression<?> visitEntityIdExpression(HqlParser.EntityIdExpressionContext ctx) {
return wrapCollate(
visitEntityIdReference( ctx.entityIdReference() ),
ctx.collationSpecification()
);
return visitEntityIdReference( ctx.entityIdReference() );
}
@Override
@ -1572,10 +1629,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
@Override
public SqmExpression<?> visitEntityVersionExpression(HqlParser.EntityVersionExpressionContext ctx) {
return wrapCollate(
visitEntityVersionReference( ctx.entityVersionReference() ),
ctx.collationSpecification()
);
return visitEntityVersionReference( ctx.entityVersionReference() );
}
@Override
@ -1763,13 +1817,28 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
}
@Override
public Object visitCaseExpression(HqlParser.CaseExpressionContext ctx) {
final SqmExpression<?> expression = (SqmExpression<?>) ctx.caseList().accept( this );
final HqlParser.CollationSpecificationContext collationSpecificationContext = ctx.collationSpecification();
if ( collationSpecificationContext == null ) {
return expression;
public Object visitCollateExpression(HqlParser.CollateExpressionContext ctx) {
return wrapCollate( (SqmExpression<?>) ctx.primaryExpression().accept( this ), ctx.collationSpecification() );
}
@Override
public Object visitTupleExpression(HqlParser.TupleExpressionContext ctx) {
final List<SqmExpression<?>> expressions = visitExpressions( ctx.expression() );
return new SqmTuple<>( expressions, creationContext.getNodeBuilder() );
}
private List<SqmExpression<?>> visitExpressions(List<? extends ParserRuleContext> expressionContexts) {
final int size = expressionContexts.size();
final List<SqmExpression<?>> expressions = new ArrayList<>( size );
for ( int i = 0; i < size; i++ ) {
expressions.add( (SqmExpression<?>) expressionContexts.get( i ).accept( this ) );
}
return new SqmCollate<>( expression, collationSpecificationContext.collateName().getText() );
return expressions;
}
@Override
public Object visitCaseExpression(HqlParser.CaseExpressionContext ctx) {
return ctx.caseList().accept( this );
}
@Override
@ -1990,10 +2059,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
@Override
public SqmExpression<?> visitLiteralExpression(HqlParser.LiteralExpressionContext ctx) {
return wrapCollate(
(SqmExpression<?>) ctx.literal().accept( this ),
ctx.collationSpecification()
);
return (SqmExpression<?>) ctx.literal().accept( this );
}
@Override
@ -2491,7 +2557,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
@Override
public Object visitParameterExpression(HqlParser.ParameterExpressionContext ctx) {
return wrapCollate( (SqmExpression<?>) ctx.parameter().accept( this ), ctx.collationSpecification() );
return ctx.parameter().accept( this );
}
@Override

View File

@ -221,6 +221,11 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter {
sqlQuerySpec,
rootProcessingState,
this,
r -> new SqlSelectionForSqmSelectionCollector(
r,
sqmSelectClause.getSelectionItems()
.size()
),
getCurrentClauseStack()::getCurrent
) {
@Override

View File

@ -12,6 +12,7 @@ import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.hibernate.AssertionFailure;
@ -137,6 +138,7 @@ import org.hibernate.query.sqm.tree.select.SqmOrderByClause;
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
import org.hibernate.query.sqm.tree.select.SqmSelectClause;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.query.sqm.tree.select.SqmSelection;
import org.hibernate.query.sqm.tree.select.SqmSortSpecification;
import org.hibernate.query.sqm.tree.select.SqmSubQuery;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
@ -151,6 +153,7 @@ import org.hibernate.sql.ast.spi.SqlAstProcessingState;
import org.hibernate.sql.ast.spi.SqlAstQuerySpecProcessingState;
import org.hibernate.sql.ast.spi.SqlAstTreeHelper;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.cte.CteColumn;
import org.hibernate.sql.ast.tree.cte.CteConsumer;
import org.hibernate.sql.ast.tree.cte.CteStatement;
@ -200,7 +203,9 @@ import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.type.IntegerType;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.spi.TypeConfiguration;
import org.jboss.logging.Logger;
@ -411,6 +416,7 @@ public abstract class BaseSqmToSqlAstConverter
@Override
public QuerySpec visitQuerySpec(SqmQuerySpec<?> sqmQuerySpec) {
final QuerySpec sqlQuerySpec = new QuerySpec( processingStateStack.isEmpty(), sqmQuerySpec.getFromClause().getNumberOfRoots() );
final SqmSelectClause selectClause = sqmQuerySpec.getSelectClause();
additionalRestrictions = null;
@ -419,6 +425,11 @@ public abstract class BaseSqmToSqlAstConverter
sqlQuerySpec,
processingStateStack.getCurrent(),
this,
r -> new SqlSelectionForSqmSelectionCollector(
r,
selectClause.getSelectionItems()
.size()
),
currentClauseStack::getCurrent
)
);
@ -429,10 +440,7 @@ public abstract class BaseSqmToSqlAstConverter
// we want to visit the from-clause first
visitFromClause( sqmQuerySpec.getFromClause() );
final SqmSelectClause selectClause = sqmQuerySpec.getSelectClause();
if ( selectClause != null ) {
visitSelectClause( selectClause );
}
visitSelectClause( selectClause );
final SqmWhereClause whereClause = sqmQuerySpec.getWhereClause();
if ( whereClause != null && whereClause.getPredicate() != null ) {
@ -491,8 +499,9 @@ public abstract class BaseSqmToSqlAstConverter
try {
super.visitSelectClause( selectClause );
currentQuerySpec().getSelectClause().makeDistinct( selectClause.isDistinct() );
return currentQuerySpec().getSelectClause();
final SelectClause sqlSelectClause = currentQuerySpec().getSelectClause();
sqlSelectClause.makeDistinct( selectClause.isDistinct() );
return sqlSelectClause;
}
finally {
shallownessStack.pop();
@ -500,14 +509,38 @@ public abstract class BaseSqmToSqlAstConverter
}
}
@Override
public Object visitSelection(SqmSelection selection) {
currentSqlSelectionCollector().next();
selection.getSelectableNode().accept( this );
return selection;
}
protected Expression resolveGroupOrOrderByExpression(SqmExpression<?> groupByClauseExpression) {
if ( groupByClauseExpression instanceof SqmLiteral<?> ) {
Object literal = ( (SqmLiteral<?>) groupByClauseExpression ).getLiteralValue();
if ( literal instanceof Integer ) {
// Integer literals have a special meaning in the GROUP BY and ORDER BY clause i.e. they refer to a select item
final int sqmPosition = (Integer) literal;
final List<SqlSelection> selections = currentSqlSelectionCollector().getSelections( sqmPosition );
final List<Expression> expressions = new ArrayList<>( selections.size() );
for ( SqlSelection selection : selections ) {
expressions.add( new JdbcLiteral<>( selection.getJdbcResultSetIndex(), IntegerType.INSTANCE ) );
}
return new SqlTuple( expressions, null );
}
}
return (Expression) groupByClauseExpression.accept( this );
}
@Override
public List<Expression> visitGroupByClause(List<SqmExpression<?>> groupByClauseExpressions) {
if ( !groupByClauseExpressions.isEmpty() ) {
currentClauseStack.push( Clause.GROUP );
try {
List<Expression> expressions = new ArrayList<>( groupByClauseExpressions.size() );
final List<Expression> expressions = new ArrayList<>( groupByClauseExpressions.size() );
for ( SqmExpression<?> groupByClauseExpression : groupByClauseExpressions ) {
expressions.add( (Expression) groupByClauseExpression.accept( this ) );
expressions.add( resolveGroupOrOrderByExpression( groupByClauseExpression ) );
}
return expressions;
}
@ -541,7 +574,7 @@ public abstract class BaseSqmToSqlAstConverter
@Override
public SortSpecification visitSortSpecification(SqmSortSpecification sortSpecification) {
return new SortSpecification(
(Expression) sortSpecification.getSortExpression().accept( this ),
resolveGroupOrOrderByExpression( sortSpecification.getSortExpression() ),
null,
sortSpecification.getSortOrder(),
sortSpecification.getNullPrecedence()
@ -920,6 +953,10 @@ public abstract class BaseSqmToSqlAstConverter
return processingState.getInflightQuerySpec();
}
protected SqlSelectionForSqmSelectionCollector currentSqlSelectionCollector() {
return (SqlSelectionForSqmSelectionCollector) getProcessingStateStack().getCurrent().getSqlExpressionResolver();
}
@Override
public TableGroup visitCrossJoin(SqmCrossJoin<?> sqmJoin) {
// todo (6.0) : have this resolve to TableGroup instead?
@ -2675,4 +2712,40 @@ public abstract class BaseSqmToSqlAstConverter
public List<Fetch> visitFetches(FetchParent fetchParent) {
return Collections.emptyList();
}
protected static class SqlSelectionForSqmSelectionCollector implements SqlExpressionResolver {
private final SqlExpressionResolver delegate;
private final List<SqlSelection>[] sqlSelectionsForSqmSelection;
private int index = -1;
public SqlSelectionForSqmSelectionCollector(SqlExpressionResolver delegate, int sqmSelectionCount) {
this.delegate = delegate;
sqlSelectionsForSqmSelection = new List[sqmSelectionCount];
}
public void next() {
index++;
}
public List<SqlSelection> getSelections(int position) {
return sqlSelectionsForSqmSelection[position - 1];
}
@Override
public Expression resolveSqlExpression(String key, Function<SqlAstProcessingState, Expression> creator) {
return delegate.resolveSqlExpression( key, creator );
}
@Override
public SqlSelection resolveSqlSelection(Expression expression, JavaTypeDescriptor javaTypeDescriptor, TypeConfiguration typeConfiguration) {
SqlSelection selection = delegate.resolveSqlSelection( expression, javaTypeDescriptor, typeConfiguration );
List<SqlSelection> sqlSelectionList = sqlSelectionsForSqmSelection[index];
if ( sqlSelectionList == null ) {
sqlSelectionsForSqmSelection[index] = sqlSelectionList = new ArrayList<>();
}
sqlSelectionList.add( selection );
return selection;
}
}
}

View File

@ -32,6 +32,7 @@ import org.hibernate.type.spi.TypeConfiguration;
public class SqlAstProcessingStateImpl implements SqlAstProcessingState, SqlExpressionResolver {
private final SqlAstProcessingState parentState;
private final SqlAstCreationState creationState;
private final SqlExpressionResolver expressionResolver;
private final Supplier<Clause> currentClauseAccess;
private final Map<String,Expression> expressionMap = new HashMap<>();
@ -42,6 +43,18 @@ public class SqlAstProcessingStateImpl implements SqlAstProcessingState, SqlExpr
Supplier<Clause> currentClauseAccess) {
this.parentState = parentState;
this.creationState = creationState;
this.expressionResolver = this;
this.currentClauseAccess = currentClauseAccess;
}
public SqlAstProcessingStateImpl(
SqlAstProcessingState parentState,
SqlAstCreationState creationState,
Function<SqlExpressionResolver, SqlExpressionResolver> expressionResolverDecorator,
Supplier<Clause> currentClauseAccess) {
this.parentState = parentState;
this.creationState = creationState;
this.expressionResolver = expressionResolverDecorator.apply( this );
this.currentClauseAccess = currentClauseAccess;
}
@ -56,7 +69,7 @@ public class SqlAstProcessingStateImpl implements SqlAstProcessingState, SqlExpr
@Override
public SqlExpressionResolver getSqlExpressionResolver() {
return this;
return expressionResolver;
}
@Override

View File

@ -8,12 +8,14 @@ package org.hibernate.query.sqm.sql.internal;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
import org.hibernate.sql.ast.spi.SqlAstQuerySpecProcessingState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.select.QuerySpec;
@ -38,6 +40,16 @@ public class SqlAstQuerySpecProcessingStateImpl
this.querySpec = querySpec;
}
public SqlAstQuerySpecProcessingStateImpl(
QuerySpec querySpec,
SqlAstProcessingState parent,
SqlAstCreationState creationState,
Function<SqlExpressionResolver, SqlExpressionResolver> expressionResolverDecorator,
Supplier<Clause> currentClauseAccess) {
super( parent, creationState, expressionResolverDecorator, currentClauseAccess );
this.querySpec = querySpec;
}
@Override
public QuerySpec getInflightQuerySpec() {
return querySpec;

View File

@ -26,6 +26,7 @@ import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
import org.hibernate.query.sqm.tree.insert.SqmInsertStatement;
import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement;
import org.hibernate.query.sqm.tree.insert.SqmValues;
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.query.sqm.tree.select.SqmSelection;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
@ -81,10 +82,17 @@ public class StandardSqmInsertTranslator
final EntityPersister entityDescriptor = getCreationContext().getDomainModel().getEntityDescriptor( entityName );
assert entityDescriptor != null;
SqmQuerySpec selectQuerySpec = sqmStatement.getSelectQuerySpec();
getProcessingStateStack().push(
new SqlAstProcessingStateImpl(
null,
this,
r -> new SqlSelectionForSqmSelectionCollector(
r,
selectQuerySpec.getSelectClause()
.getSelectionItems()
.size()
),
getCurrentClauseStack()::getCurrent
)
);
@ -118,7 +126,7 @@ public class StandardSqmInsertTranslator
}
insertStatement.setSourceSelectStatement(
visitQuerySpec( sqmStatement.getSelectQuerySpec() )
visitQuerySpec( selectQuerySpec )
);
return insertStatement;
@ -190,6 +198,7 @@ public class StandardSqmInsertTranslator
@Override
public Void visitSelection(SqmSelection sqmSelection) {
currentSqlSelectionCollector().next();
final DomainResultProducer resultProducer = resolveDomainResultProducer( sqmSelection );
// if ( getProcessingStateStack().depth() > 1 ) {

View File

@ -241,6 +241,7 @@ public class StandardSqmSelectTranslator
@Override
public Void visitSelection(SqmSelection sqmSelection) {
currentSqlSelectionCollector().next();
final DomainResultProducer resultProducer = resolveDomainResultProducer( sqmSelection );
if ( getProcessingStateStack().depth() > 1 ) {

View File

@ -6,9 +6,7 @@
*/
package org.hibernate.sql.ast.spi;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@ -122,6 +120,7 @@ public abstract class AbstractSqlAstWalker
private final Set<FilterJdbcParameter> filterJdbcParameters = new HashSet<>();
private final Stack<Clause> clauseStack = new StandardStack<>();
protected final Stack<QuerySpec> querySpecStack = new StandardStack<>();
private final Dialect dialect;
private transient AbstractSqmSelfRenderingFunctionDescriptor castFunction;
@ -257,19 +256,24 @@ public abstract class AbstractSqlAstWalker
@Override
public void visitQuerySpec(QuerySpec querySpec) {
if ( !querySpec.isRoot() ) {
appendSql( " (" );
}
visitSelectClause( querySpec.getSelectClause() );
visitFromClause( querySpec.getFromClause() );
visitWhereClause( querySpec );
visitGroupByClause( querySpec );
visitHavingClause( querySpec );
visitOrderBy( querySpec );
visitLimitOffsetClause( querySpec );
try {
querySpecStack.push( querySpec );
if ( !querySpec.isRoot() ) {
appendSql( " (" );
}
visitSelectClause( querySpec.getSelectClause() );
visitFromClause( querySpec.getFromClause() );
visitWhereClause( querySpec );
visitGroupByClause( querySpec, dialect.supportsSelectAliasInGroupByClause() );
visitHavingClause( querySpec );
visitOrderBy( querySpec );
visitLimitOffsetClause( querySpec );
if ( !querySpec.isRoot() ) {
appendSql( ")" );
if ( !querySpec.isRoot() ) {
appendSql( ")" );
}
} finally {
querySpecStack.push( querySpec );
}
}
@ -288,18 +292,58 @@ public abstract class AbstractSqlAstWalker
}
}
protected final void visitGroupByClause(QuerySpec querySpec) {
protected Expression resolveAliasedExpression(Expression expression) {
if ( expression instanceof Literal ) {
Object literalValue = ( (Literal) expression ).getLiteralValue();
if ( literalValue instanceof Integer ) {
return querySpecStack.getCurrent()
.getSelectClause()
.getSqlSelections()
.get( (Integer) literalValue )
.getExpression();
}
}
return expression;
}
protected final void visitGroupByClause(QuerySpec querySpec, boolean supportsSelectAliases) {
List<Expression> groupByClauseExpressions = querySpec.getGroupByClauseExpressions();
if ( !groupByClauseExpressions.isEmpty() ) {
appendSql( " group by " );
clauseStack.push( Clause.GROUP );
String separator = NO_SEPARATOR;
String separator = " group by ";
try {
for ( Expression groupByClauseExpression : groupByClauseExpressions ) {
appendSql( separator );
groupByClauseExpression.accept( this );
separator = COMA_SEPARATOR;
if ( supportsSelectAliases ) {
for ( Expression groupByClauseExpression : groupByClauseExpressions ) {
if ( groupByClauseExpression instanceof SqlTuple ) {
for ( Expression expression : ( (SqlTuple) groupByClauseExpression ).getExpressions() ) {
appendSql( separator );
renderGroupByItem( expression );
separator = COMA_SEPARATOR;
}
}
else {
appendSql( separator );
renderGroupByItem( groupByClauseExpression );
}
separator = COMA_SEPARATOR;
}
}
else {
for ( Expression groupByClauseExpression : groupByClauseExpressions ) {
if ( groupByClauseExpression instanceof SqlTuple ) {
for ( Expression expression : ( (SqlTuple) groupByClauseExpression ).getExpressions() ) {
appendSql( separator );
renderGroupByItem( resolveAliasedExpression( expression ) );
separator = COMA_SEPARATOR;
}
}
else {
appendSql( separator );
renderGroupByItem( resolveAliasedExpression( groupByClauseExpression ) );
}
separator = COMA_SEPARATOR;
}
}
}
finally {
@ -308,6 +352,17 @@ public abstract class AbstractSqlAstWalker
}
}
protected void renderGroupByItem(Expression expression) {
// We render an empty group instead of literals as some DBs don't support grouping by literals
// Note that integer literals, which refer to select item positions, are handled in #visitGroupByClause
if ( expression instanceof Literal ) {
appendSql( "()" );
}
else {
expression.accept( this );
}
}
protected final void visitHavingClause(QuerySpec querySpec) {
final Predicate havingClauseRestrictions = querySpec.getHavingClauseRestrictions();
if ( havingClauseRestrictions != null && !havingClauseRestrictions.isEmpty() ) {
@ -343,21 +398,33 @@ public abstract class AbstractSqlAstWalker
}
}
protected void emulateTupleComparison(final List<? extends Expression> lhsExpressions, final List<? extends Expression> rhsExpressions, ComparisonOperator operator) {
String separator = NO_SEPARATOR;
/**
* A tuple comparison like <code>(a, b) &gt; (1, 2)</code> can be emulated through it logical definition: <code>a &gt; 1 or a = 1 and b &gt; 2</code>.
* The normal tuple comparison emulation is not very index friendly though because of the top level OR predicate.
* Index optimized emulation of tuple comparisons puts an AND predicate on the top level.
* The effect of that is, that the database can do an index seek to efficiently find a superset of matching rows.
* Generally, it is sufficient to just add a broader predicate like for <code>(a, b) &gt; (1, 2)</code> we add <code>a &gt;= 1 and (..)</code>.
* But we can further optimize this if we just remove the non-matching parts from this too broad predicate.
* For <code>(a, b, c) &gt; (1, 2, 3)</code> we use the broad predicate <code>a &gt;= 1</code> and then want to remove rows where <code>a = 1 and (b, c) &lt;= (2, 3)</code>
*/
protected void emulateTupleComparison(
final List<? extends Expression> lhsExpressions,
final List<? extends Expression> rhsExpressions,
ComparisonOperator operator,
boolean indexOptimized) {
final boolean isCurrentWhereClause = clauseStack.getCurrent() == Clause.WHERE;
if ( isCurrentWhereClause ) {
appendSql( OPEN_PARENTHESIS );
}
final int size = lhsExpressions.size();
final String operatorText = operator.sqlText();
assert size == rhsExpressions.size();
switch ( operator ) {
case EQUAL:
case NOT_EQUAL:
case NOT_EQUAL: {
final String operatorText = operator.sqlText();
String separator = NO_SEPARATOR;
for ( int i = 0; i < size; i++ ) {
appendSql( separator );
lhsExpressions.get( i ).accept( this );
@ -366,43 +433,44 @@ public abstract class AbstractSqlAstWalker
separator = " and ";
}
break;
}
case LESS_THAN_OR_EQUAL:
// Optimized (a, b) <= (1, 2) as: a <= 1 and not (a = 1 and b > 2)
// Normal (a, b) <= (1, 2) as: a < 1 or a = 1 and (b <= 2)
case GREATER_THAN_OR_EQUAL:
// Render (a, b) <= (1, 2) as: (a = 1 and b = 2) or (a < 1 or a = 1 and b < 2)
appendSql( OPEN_PARENTHESIS );
for ( int i = 0; i < size; i++ ) {
appendSql( separator );
lhsExpressions.get( i ).accept( this );
appendSql( operatorText );
rhsExpressions.get( i ).accept( this );
separator = " and ";
}
appendSql( CLOSE_PARENTHESIS );
appendSql( " or " );
separator = NO_SEPARATOR;
// Optimized (a, b) >= (1, 2) as: a >= 1 and not (a = 1 and b < 2)
// Normal (a, b) >= (1, 2) as: a > 1 or a = 1 and (b >= 2)
case LESS_THAN:
case GREATER_THAN:
// Render (a, b) < (1, 2) as: (a < 1 or a = 1 and b < 2)
appendSql( OPEN_PARENTHESIS );
for ( int i = 0; i < size; i++ ) {
int j = 0;
// Render the equals parts
for ( ; j < i; j++ ) {
appendSql( separator );
lhsExpressions.get( i ).accept( this );
appendSql( '=' );
rhsExpressions.get( i ).accept( this );
separator = " and ";
}
// Render the actual operator part for the current component
appendSql( separator );
lhsExpressions.get( i ).accept( this );
appendSql( operatorText );
rhsExpressions.get( i ).accept( this );
separator = " or ";
// Optimized (a, b) < (1, 2) as: a <= 1 and not (a = 1 and b >= 2)
// Normal (a, b) < (1, 2) as: a < 1 or a = 1 and (b < 2)
case GREATER_THAN: {
// Optimized (a, b) > (1, 2) as: a >= 1 and not (a = 1 and b <= 2)
// Normal (a, b) > (1, 2) as: a > 1 or a = 1 and (b > 2)
if ( indexOptimized ) {
lhsExpressions.get( 0 ).accept( this );
appendSql( operator.broader().sqlText() );
rhsExpressions.get( 0 ).accept( this );
appendSql( " and not " );
final String negatedOperatorText = operator.negated().sqlText();
emulateTupleComparisonSimple(
lhsExpressions,
rhsExpressions,
negatedOperatorText,
negatedOperatorText,
true
);
}
else {
emulateTupleComparisonSimple(
lhsExpressions,
rhsExpressions,
operator.sharper().sqlText(),
operator.sqlText(),
false
);
}
appendSql( CLOSE_PARENTHESIS );
break;
}
}
if ( isCurrentWhereClause ) {
@ -410,6 +478,88 @@ public abstract class AbstractSqlAstWalker
}
}
private void renderExpressionsAsSubquery(final List<? extends Expression> expressions) {
clauseStack.push( Clause.SELECT );
try {
appendSql( "select " );
String separator = NO_SEPARATOR;
for ( Expression expression : expressions ) {
appendSql( separator );
expression.accept( this );
separator = COMA_SEPARATOR;
}
String fromDual = dialect.getFromDual();
if ( !fromDual.isEmpty() ) {
appendSql( " " );
appendSql( fromDual );
}
}
finally {
clauseStack.pop();
}
}
private void emulateTupleComparisonSimple(
final List<? extends Expression> lhsExpressions,
final List<? extends Expression> rhsExpressions,
final String operatorText,
final String finalOperatorText,
final boolean optimized) {
// Render (a, b) OP (1, 2) as: (a OP 1 or a = 1 and b FINAL_OP 2)
final int size = lhsExpressions.size();
final int lastIndex = size - 1;
appendSql( OPEN_PARENTHESIS );
String separator = NO_SEPARATOR;
int i;
if ( optimized ) {
i = 1;
}
else {
lhsExpressions.get( 0 ).accept( this );
appendSql( operatorText );
rhsExpressions.get( 0 ).accept( this );
separator = " or ";
i = 1;
}
for ( ; i < lastIndex; i++ ) {
// Render the equals parts
appendSql( separator );
lhsExpressions.get( i - 1 ).accept( this );
appendSql( '=' );
rhsExpressions.get( i - 1 ).accept( this );
// Render the actual operator part for the current component
appendSql( " and (" );
lhsExpressions.get( i ).accept( this );
appendSql( operatorText );
rhsExpressions.get( i ).accept( this );
separator = " or ";
}
// Render the equals parts
appendSql( separator );
lhsExpressions.get( lastIndex - 1 ).accept( this );
appendSql( '=' );
rhsExpressions.get( lastIndex - 1 ).accept( this );
// Render the actual operator part for the current component
appendSql( " and " );
lhsExpressions.get( lastIndex ).accept( this );
appendSql( finalOperatorText );
rhsExpressions.get( lastIndex ).accept( this );
// Close all opened parenthesis
for ( i = optimized ? 1 : 0; i < lastIndex; i++ ) {
appendSql( CLOSE_PARENTHESIS );
}
}
protected void renderSelectTupleComparison(final List<SqlSelection> lhsExpressions, SqlTuple tuple, ComparisonOperator operator) {
if ( dialect.supportsRowValueConstructorSyntax() ) {
appendSql( OPEN_PARENTHESIS );
@ -431,7 +581,7 @@ public abstract class AbstractSqlAstWalker
lhs.add( lhsExpression.getExpression() );
}
emulateTupleComparison( lhs, tuple.getExpressions(), operator );
emulateTupleComparison( lhs, tuple.getExpressions(), operator, true );
}
}
@ -441,11 +591,28 @@ public abstract class AbstractSqlAstWalker
@Override
public void visitSortSpecification(SortSpecification sortSpecification) {
NullPrecedence nullPrecedence = sortSpecification.getNullPrecedence();
final Expression sortExpression = sortSpecification.getSortExpression();
final NullPrecedence nullPrecedence = sortSpecification.getNullPrecedence();
final SortOrder sortOrder = sortSpecification.getSortOrder();
if ( sortExpression instanceof SqlTuple ) {
String separator = NO_SEPARATOR;
for ( Expression expression : ( (SqlTuple) sortExpression ).getExpressions() ) {
appendSql( separator );
visitSortSpecification( expression, sortOrder, nullPrecedence );
separator = COMA_SEPARATOR;
}
}
else {
visitSortSpecification( sortExpression, sortOrder, nullPrecedence );
}
}
public void visitSortSpecification(Expression sortExpression, SortOrder sortOrder, NullPrecedence nullPrecedence) {
final boolean hasNullPrecedence = nullPrecedence != null && nullPrecedence != NullPrecedence.NONE;
if ( hasNullPrecedence && !dialect.supportsNullPrecedence() ) {
// TODO: generate "virtual" select items and use them here positionally
appendSql( "case when (" );
sortSpecification.getSortExpression().accept( this );
resolveAliasedExpression( sortExpression ).accept( this );
appendSql( ") is null then " );
if ( nullPrecedence == NullPrecedence.FIRST ) {
appendSql( "0 else 1" );
@ -457,9 +624,8 @@ public abstract class AbstractSqlAstWalker
appendSql( COMA_SEPARATOR );
}
sortSpecification.getSortExpression().accept( this );
sortExpression.accept( this );
final SortOrder sortOrder = sortSpecification.getSortOrder();
if ( sortOrder == SortOrder.ASCENDING ) {
appendSql( " asc" );
}
@ -1274,14 +1440,75 @@ public abstract class AbstractSqlAstWalker
@Override
public void visitInListPredicate(InListPredicate inListPredicate) {
if ( inListPredicate.getListExpressions().isEmpty() ) {
appendSql( "false" );
return;
}
final SqlTuple lhsTuple;
if ( ( lhsTuple = getTuple( inListPredicate.getTestExpression() ) ) != null && !dialect.supportsRowValueConstructorSyntaxInInList() ) {
final ComparisonOperator comparisonOperator = inListPredicate.isNegated() ? ComparisonOperator.NOT_EQUAL : ComparisonOperator.EQUAL;
String separator = NO_SEPARATOR;
for ( Expression expression : inListPredicate.getListExpressions() ) {
appendSql( separator );
emulateTupleComparison( lhsTuple.getExpressions(), getTuple( expression ).getExpressions(), comparisonOperator );
separator = " or ";
if ( ( lhsTuple = getTuple( inListPredicate.getTestExpression() ) ) != null ) {
if ( lhsTuple.getExpressions().size() == 1 ) {
// Special case for tuples with arity 1 as any DBMS supports scalar IN predicates
lhsTuple.getExpressions().get( 0 ).accept( this );
if ( inListPredicate.isNegated() ) {
appendSql( " not" );
}
appendSql( " in (" );
String separator = NO_SEPARATOR;
for ( Expression expression : inListPredicate.getListExpressions() ) {
appendSql( separator );
getTuple( expression ).getExpressions().get( 0 ).accept( this );
separator = COMA_SEPARATOR;
}
appendSql( CLOSE_PARENTHESIS );
}
else if ( !dialect.supportsRowValueConstructorSyntaxInInList() ) {
final ComparisonOperator comparisonOperator = inListPredicate.isNegated() ?
ComparisonOperator.NOT_EQUAL :
ComparisonOperator.EQUAL;
// Some DBs like Oracle support tuples only for the IN subquery predicate
if ( dialect.supportsRowValueConstructorSyntaxInInSubquery() && dialect.supportsUnionAll() ) {
inListPredicate.getTestExpression().accept( this );
if ( inListPredicate.isNegated() ) {
appendSql( " not" );
}
appendSql( " in (" );
String separator = NO_SEPARATOR;
for ( Expression expression : inListPredicate.getListExpressions() ) {
appendSql( separator );
renderExpressionsAsSubquery(
getTuple( expression ).getExpressions()
);
separator = " union all ";
}
appendSql( CLOSE_PARENTHESIS );
}
else {
String separator = NO_SEPARATOR;
for ( Expression expression : inListPredicate.getListExpressions() ) {
appendSql( separator );
emulateTupleComparison(
lhsTuple.getExpressions(),
getTuple( expression ).getExpressions(),
comparisonOperator,
true
);
separator = " or ";
}
}
}
else {
inListPredicate.getTestExpression().accept( this );
if ( inListPredicate.isNegated() ) {
appendSql( " not" );
}
appendSql( " in (" );
String separator = NO_SEPARATOR;
for ( Expression expression : inListPredicate.getListExpressions() ) {
appendSql( separator );
expression.accept( this );
separator = COMA_SEPARATOR;
}
appendSql( CLOSE_PARENTHESIS );
}
}
else {
@ -1290,16 +1517,11 @@ public abstract class AbstractSqlAstWalker
appendSql( " not" );
}
appendSql( " in (" );
if ( inListPredicate.getListExpressions().isEmpty() ) {
appendSql( NULL_KEYWORD );
}
else {
String separator = NO_SEPARATOR;
for ( Expression expression : inListPredicate.getListExpressions() ) {
appendSql( separator );
expression.accept( this );
separator = COMA_SEPARATOR;
}
String separator = NO_SEPARATOR;
for ( Expression expression : inListPredicate.getListExpressions() ) {
appendSql( separator );
expression.accept( this );
separator = COMA_SEPARATOR;
}
appendSql( CLOSE_PARENTHESIS );
}
@ -1327,14 +1549,33 @@ public abstract class AbstractSqlAstWalker
@Override
public void visitInSubQueryPredicate(InSubQueryPredicate inSubQueryPredicate) {
final SqlTuple lhsTuple;
if ( ( lhsTuple = getTuple( inSubQueryPredicate.getTestExpression() ) ) != null && !dialect.supportsRowValueConstructorSyntaxInInList() ) {
emulateTupleSubQueryPredicate(
inSubQueryPredicate,
inSubQueryPredicate.isNegated(),
inSubQueryPredicate.getSubQuery(),
lhsTuple,
ComparisonOperator.EQUAL
);
if ( ( lhsTuple = getTuple( inSubQueryPredicate.getTestExpression() ) ) != null ) {
if ( lhsTuple.getExpressions().size() == 1 ) {
// Special case for tuples with arity 1 as any DBMS supports scalar IN predicates
lhsTuple.getExpressions().get( 0 ).accept( this );
if ( inSubQueryPredicate.isNegated() ) {
appendSql( " not" );
}
appendSql( " in " );
visitQuerySpec( inSubQueryPredicate.getSubQuery() );
}
else if ( !dialect.supportsRowValueConstructorSyntaxInInSubquery() ) {
emulateTupleSubQueryPredicate(
inSubQueryPredicate,
inSubQueryPredicate.isNegated(),
inSubQueryPredicate.getSubQuery(),
lhsTuple,
ComparisonOperator.EQUAL
);
}
else {
inSubQueryPredicate.getTestExpression().accept( this );
if ( inSubQueryPredicate.isNegated() ) {
appendSql( " not" );
}
appendSql( " in " );
visitQuerySpec( inSubQueryPredicate.getSubQuery() );
}
}
else {
inSubQueryPredicate.getTestExpression().accept( this );
@ -1357,31 +1598,64 @@ public abstract class AbstractSqlAstWalker
if ( negated ) {
appendSql( "not " );
}
appendSql( "exists (select 1" );
visitFromClause( subQuery.getFromClause() );
appendSql( " where " );
// TODO: use HAVING clause if it has a group by
clauseStack.push( Clause.WHERE );
try {
renderSelectTupleComparison(
subQuery.getSelectClause().getSqlSelections(),
lhsTuple,
tupleComparisonOperator
);
appendSql( " and (" );
final Predicate whereClauseRestrictions = subQuery.getWhereClauseRestrictions();
if ( whereClauseRestrictions != null ) {
whereClauseRestrictions.accept( this );
querySpecStack.push( subQuery );
appendSql( "exists (select 1" );
visitFromClause( subQuery.getFromClause() );
if ( !subQuery.getGroupByClauseExpressions()
.isEmpty() || subQuery.getHavingClauseRestrictions() != null ) {
// If we have a group by or having clause, we have to move the tuple comparison emulation to the HAVING clause
visitWhereClause( subQuery );
visitGroupByClause( subQuery, false );
appendSql( " having " );
clauseStack.push( Clause.HAVING );
try {
renderSelectTupleComparison(
subQuery.getSelectClause().getSqlSelections(),
lhsTuple,
tupleComparisonOperator
);
final Predicate havingClauseRestrictions = subQuery.getHavingClauseRestrictions();
if ( havingClauseRestrictions != null ) {
appendSql( " and (" );
havingClauseRestrictions.accept( this );
appendSql( ')' );
}
}
finally {
clauseStack.pop();
}
}
appendSql( ')' );
else {
// If we have no group by or having clause, we can move the tuple comparison emulation to the WHERE clause
appendSql( " where " );
clauseStack.push( Clause.WHERE );
try {
renderSelectTupleComparison(
subQuery.getSelectClause().getSqlSelections(),
lhsTuple,
tupleComparisonOperator
);
final Predicate whereClauseRestrictions = subQuery.getWhereClauseRestrictions();
if ( whereClauseRestrictions != null ) {
appendSql( " and (" );
whereClauseRestrictions.accept( this );
appendSql( ')' );
}
}
finally {
clauseStack.pop();
}
}
appendSql( ")" );
}
finally {
clauseStack.pop();
querySpecStack.pop();
}
appendSql( ")" );
}
else {
// TODO: We could use nested queries and use row numbers to emulate this
@ -1405,31 +1679,38 @@ public abstract class AbstractSqlAstWalker
appendSql( tupleComparisonOperator.sqlText() );
appendSql( " " );
appendSql( "(" );
visitSelectClause( subQuery.getSelectClause() );
visitFromClause( subQuery.getFromClause() );
visitWhereClause( subQuery );
try {
querySpecStack.push( subQuery );
appendSql( "(" );
visitSelectClause( subQuery.getSelectClause() );
visitFromClause( subQuery.getFromClause() );
visitWhereClause( subQuery );
visitGroupByClause( subQuery, dialect.supportsSelectAliasInGroupByClause() );
visitHavingClause( subQuery );
appendSql( " order by " );
boolean asc = tupleComparisonOperator == ComparisonOperator.LESS_THAN || tupleComparisonOperator == ComparisonOperator.LESS_THAN_OR_EQUAL;
final List<SqlSelection> sqlSelections = subQuery.getSelectClause().getSqlSelections();
final String order;
if ( tupleComparisonOperator == ComparisonOperator.LESS_THAN || tupleComparisonOperator == ComparisonOperator.LESS_THAN_OR_EQUAL ) {
// Default order is asc so we don't need to specify the order explicitly
order = "";
}
else {
order = " desc";
}
appendSql( "1" );
appendSql( order );
for ( int i = 1; i < sqlSelections.size(); i++ ) {
appendSql( COMA_SEPARATOR );
appendSql( Integer.toString( i + 1 ) );
appendSql( " order by " );
final List<SqlSelection> sqlSelections = subQuery.getSelectClause().getSqlSelections();
final String order;
if ( tupleComparisonOperator == ComparisonOperator.LESS_THAN || tupleComparisonOperator == ComparisonOperator.LESS_THAN_OR_EQUAL ) {
// Default order is asc so we don't need to specify the order explicitly
order = "";
}
else {
order = " desc";
}
appendSql( "1" );
appendSql( order );
for ( int i = 1; i < sqlSelections.size(); i++ ) {
appendSql( COMA_SEPARATOR );
appendSql( Integer.toString( i + 1 ) );
appendSql( order );
}
renderLimit( ONE_LITERAL );
appendSql( ")" );
}
finally {
querySpecStack.pop();
}
renderLimit( ONE_LITERAL );
appendSql( ")" );
}
else {
// TODO: We could use nested queries and use row numbers to emulate this
@ -1496,33 +1777,14 @@ public abstract class AbstractSqlAstWalker
else {
predicateValue = " is null";
}
if ( expression instanceof EmbeddableValuedPathInterpretation ) {
final EmbeddableValuedPathInterpretation embeddableValuedPathInterpretation = (EmbeddableValuedPathInterpretation) expression;
final Expression sqlExpression = embeddableValuedPathInterpretation.getSqlExpression();
final SqlTuple tuple;
if ( ( tuple = getTuple( sqlExpression ) ) != null ) {
String separator = NO_SEPARATOR;
boolean isCurrentWhereClause = clauseStack.getCurrent() == Clause.WHERE;
if ( isCurrentWhereClause ) {
appendSql( OPEN_PARENTHESIS );
}
for ( Expression exp : tuple.getExpressions() ) {
appendSql( separator );
exp.accept( this );
appendSql( predicateValue );
separator = " and ";
}
if ( isCurrentWhereClause ) {
appendSql( CLOSE_PARENTHESIS );
}
}
else {
expression.accept( this );
final SqlTuple tuple;
if ( ( tuple = getTuple( expression ) ) != null ) {
String separator = NO_SEPARATOR;
for ( Expression exp : tuple.getExpressions() ) {
appendSql( separator );
exp.accept( this );
appendSql( predicateValue );
separator = " and ";
}
}
else {
@ -1573,7 +1835,20 @@ public abstract class AbstractSqlAstWalker
}
final ComparisonOperator operator = comparisonPredicate.getOperator();
if ( subquery != null && !dialect.supportsRowValueConstructorSyntaxInQuantifiedPredicates() ) {
if ( lhsTuple.getExpressions().size() == 1 ) {
// Special case for tuples with arity 1 as any DBMS supports scalar IN predicates
lhsTuple.getExpressions().get( 0 ).accept( this );
appendSql( " " );
appendSql( operator.sqlText() );
appendSql( " " );
if ( subquery == null ) {
getTuple( comparisonPredicate.getRightHandExpression() ).getExpressions().get( 0 ).accept( this );
}
else {
rhsExpression.accept( this );
}
}
else if ( subquery != null && !dialect.supportsRowValueConstructorSyntaxInQuantifiedPredicates() ) {
// For quantified relational comparisons, we can do an optimized emulation
if ( all && operator != ComparisonOperator.EQUAL && operator != ComparisonOperator.NOT_EQUAL && dialect.supportsRowValueConstructorSyntax() ) {
emulateQuantifiedTupleSubQueryPredicate(
@ -1596,11 +1871,24 @@ public abstract class AbstractSqlAstWalker
else if ( !dialect.supportsRowValueConstructorSyntax() ) {
rhsTuple = getTuple( rhsExpression );
assert rhsTuple != null;
emulateTupleComparison(
lhsTuple.getExpressions(),
rhsTuple.getExpressions(),
operator
);
// Some DBs like Oracle support tuples only for the IN subquery predicate
if ( ( operator == ComparisonOperator.EQUAL || operator == ComparisonOperator.NOT_EQUAL ) && dialect.supportsRowValueConstructorSyntaxInInSubquery() ) {
comparisonPredicate.getLeftHandExpression().accept( this );
if ( operator == ComparisonOperator.NOT_EQUAL ) {
appendSql( " not" );
}
appendSql( " in (" );
renderExpressionsAsSubquery( rhsTuple.getExpressions() );
appendSql( CLOSE_PARENTHESIS );
}
else {
emulateTupleComparison(
lhsTuple.getExpressions(),
rhsTuple.getExpressions(),
operator,
true
);
}
}
else {
comparisonPredicate.getLeftHandExpression().accept( this );
@ -1616,7 +1904,15 @@ public abstract class AbstractSqlAstWalker
if ( lhsExpression instanceof QuerySpec ) {
final QuerySpec subquery = (QuerySpec) lhsExpression;
if ( dialect.supportsRowValueConstructorSyntax() ) {
if ( rhsTuple.getExpressions().size() == 1 ) {
// Special case for tuples with arity 1 as any DBMS supports scalar IN predicates
lhsExpression.accept( this );
appendSql( " " );
appendSql( comparisonPredicate.getOperator().sqlText() );
appendSql( " " );
rhsTuple.getExpressions().get( 0 ).accept( this );
}
else if ( dialect.supportsRowValueConstructorSyntax() ) {
lhsExpression.accept( this );
appendSql( " " );
appendSql( comparisonPredicate.getOperator().sqlText() );

View File

@ -55,7 +55,6 @@ public class CriteriaLiteralWithSingleQuoteTest extends EntityManagerFactoryBase
}
@Test
@SkipForDialect(dialectClass = PostgreSQLDialect.class, matchSubTypes = true, reason = "PostgreSQL does not support literals in group by statement")
public void testLiteralProjectionAndGroupBy() {
inTransaction(
entityManager -> {
@ -89,7 +88,7 @@ public class CriteriaLiteralWithSingleQuoteTest extends EntityManagerFactoryBase
public void cleanupData() {
inTransaction(
entityManager -> {
entityManager.createQuery( "delete from Student" );
entityManager.createQuery( "delete from Student" ).executeUpdate();
}
);
}
@ -111,7 +110,6 @@ public class CriteriaLiteralWithSingleQuoteTest extends EntityManagerFactoryBase
public void setId(Long id) {
this.id = id;
this.id = id;
}
public String getAValue() {

View File

@ -111,7 +111,7 @@ public class FunctionTests extends SessionFactoryBasedFunctionalTest {
public void testCoalesceFunction(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery("select coalesce(null, e.gender, e.convertedGender, e.ordinalGender) from EntityOfBasics e")
session.createQuery("select coalesce(null, e.gender, e.convertedGender) from EntityOfBasics e")
.list();
session.createQuery("select ifnull(e.gender, e.convertedGender) from EntityOfBasics e")
.list();
@ -157,13 +157,13 @@ public class FunctionTests extends SessionFactoryBasedFunctionalTest {
.list();
session.createQuery("select abs(e.theDouble), sign(e.theDouble), sqrt(e.theDouble) from EntityOfBasics e")
.list();
session.createQuery("select exp(e.theDouble), ln(e.theDouble) from EntityOfBasics e")
session.createQuery("select exp(e.theDouble), ln(e.theDouble + 1) from EntityOfBasics e")
.list();
session.createQuery("select power(e.theDouble, 2.5) from EntityOfBasics e")
.list();
session.createQuery("select ceiling(e.theDouble), floor(e.theDouble) from EntityOfBasics e")
.list();
session.createQuery("select round(e.theDouble, 3) from EntityOfBasics e")
session.createQuery("select round(cast(e.theDouble as BigDecimal), 3) from EntityOfBasics e")
.list();
assertThat( session.createQuery("select abs(-2)").getSingleResult(), is(2) );
assertThat( session.createQuery("select sign(-2)").getSingleResult(), is(-1) );

View File

@ -124,11 +124,11 @@ public class StandardFunctionTests {
session.createQuery( "select local_time from EntityOfBasics" ).list();
session.createQuery( "select local_time() from EntityOfBasics" ).list();
session.createQuery( "select e from EntityOfBasics e where e.theTimestamp = local_time" ).list();
session.createQuery( "select e from EntityOfBasics e where e.theTimestamp = local_time()()" ).list();
session.createQuery( "select e from EntityOfBasics e where e.theLocalTime = local_time" ).list();
session.createQuery( "select e from EntityOfBasics e where e.theLocalTime = local_time()()" ).list();
session.createQuery( "select e from EntityOfBasics e where local_time() between e.theTimestamp and e.theTimestamp" ).list();
session.createQuery( "select e from EntityOfBasics e where local_time()() between e.theTimestamp and e.theTimestamp" ).list();
session.createQuery( "select e from EntityOfBasics e where local_time() between e.theLocalTime and e.theLocalTime" ).list();
session.createQuery( "select e from EntityOfBasics e where local_time()() between e.theLocalTime and e.theLocalTime" ).list();
assertThat(
session.createQuery( "select local_time" ).getSingleResult(),
@ -152,7 +152,7 @@ public class StandardFunctionTests {
public void testCoalesceFunction(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery("select coalesce(null, e.gender, e.convertedGender, e.ordinalGender) from EntityOfBasics e")
session.createQuery("select coalesce(null, e.gender, e.convertedGender) from EntityOfBasics e")
.list();
session.createQuery("select ifnull(e.gender, e.convertedGender) from EntityOfBasics e")
.list();
@ -195,7 +195,7 @@ public class StandardFunctionTests {
.list();
session.createQuery("select ceiling(e.theDouble), floor(e.theDouble) from EntityOfBasics e")
.list();
session.createQuery("select round(e.theDouble, 3) from EntityOfBasics e")
session.createQuery("select round(cast(e.theDouble as BigDecimal), 3) from EntityOfBasics e")
.list();
}
);

View File

@ -57,11 +57,11 @@ public class SubqueryOperatorsTest extends SessionFactoryBasedFunctionalTest {
inTransaction(
session -> {
List res0 = session.createQuery(
"select (select 1) as one, (select 'foo') as foo order by one, foo, (select 2)" )
"select (select cast(1 as Integer)) as one, (select cast('foo' as String)) as foo order by one, foo, (select 2)" )
.list();
assertThat( res0.size(), is( 1 ) );
List res1 = session.createQuery(
"select (select 1) as one, (select 'foo') as foo from SimpleEntity o order by one, foo, (select 2)" )
"select (select cast(1 as Integer)) as one, (select cast('foo' as String)) as foo from SimpleEntity o order by one, foo, (select 2)" )
.list();
assertThat( res1.size(), is( 2 ) );
List res2 = session.createQuery(
@ -69,7 +69,7 @@ public class SubqueryOperatorsTest extends SessionFactoryBasedFunctionalTest {
.list();
assertThat( res2.size(), is( 2 ) );
List res3 = session.createQuery(
"from SimpleEntity o where o.someString = (select 'aaa') and o.id >= (select 0)" )
"from SimpleEntity o where o.someString = (select cast('aaa' as String)) and o.id >= (select cast(0 as Integer))" )
.list();
assertThat( res3.size(), is( 1 ) );
List res4 = session.createQuery(