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:
parent
d9446e7c77
commit
3b730ac376
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -911,6 +911,11 @@ public class OracleDialect extends Dialect {
|
|||
*/
|
||||
@Override
|
||||
public boolean supportsRowValueConstructorSyntaxInInList() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRowValueConstructorSyntaxInInSubquery() {
|
||||
return getVersion() >= 9;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -221,6 +221,11 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter {
|
|||
sqlQuerySpec,
|
||||
rootProcessingState,
|
||||
this,
|
||||
r -> new SqlSelectionForSqmSelectionCollector(
|
||||
r,
|
||||
sqmSelectClause.getSelectionItems()
|
||||
.size()
|
||||
),
|
||||
getCurrentClauseStack()::getCurrent
|
||||
) {
|
||||
@Override
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -241,6 +241,7 @@ public class StandardSqmSelectTranslator
|
|||
|
||||
@Override
|
||||
public Void visitSelection(SqmSelection sqmSelection) {
|
||||
currentSqlSelectionCollector().next();
|
||||
final DomainResultProducer resultProducer = resolveDomainResultProducer( sqmSelection );
|
||||
|
||||
if ( getProcessingStateStack().depth() > 1 ) {
|
||||
|
|
|
@ -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) > (1, 2)</code> can be emulated through it logical definition: <code>a > 1 or a = 1 and b > 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) > (1, 2)</code> we add <code>a >= 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) > (1, 2, 3)</code> we use the broad predicate <code>a >= 1</code> and then want to remove rows where <code>a = 1 and (b, c) <= (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() );
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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) );
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue