Fix issues with Oracle 12.1c

This commit is contained in:
Christian Beikov 2021-07-26 17:18:34 +02:00
parent ad49a41cd9
commit e82a45d616
14 changed files with 501 additions and 183 deletions

208
Jenkinsfile vendored
View File

@ -60,109 +60,129 @@ stage('Build') {
environments.each { BuildEnvironment buildEnv -> environments.each { BuildEnvironment buildEnv ->
executions.put(buildEnv.tag, { executions.put(buildEnv.tag, {
runBuildOnNode(buildEnv.node) { runBuildOnNode(buildEnv.node) {
def containerName = null
env.JAVA_HOME="${tool buildEnv.buildJdkTool}" env.JAVA_HOME="${tool buildEnv.buildJdkTool}"
env.PATH="${env.JAVA_HOME}/bin:${env.PATH}" env.PATH="${env.JAVA_HOME}/bin:${env.PATH}"
stage('Checkout') { stage('Checkout') {
checkout scm checkout scm
} }
stage('Start database') { try {
switch (buildEnv.dbName) { stage('Start database') {
case "mysql8": switch (buildEnv.dbName) {
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { case "mysql8":
docker.image('mysql:8.0.21').pull() docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
} docker.image('mysql:8.0.21').pull()
sh "./docker_db.sh mysql_8_0" }
break; sh "./docker_db.sh mysql_8_0"
case "mariadb": containerName = "mysql"
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { break;
docker.image('mariadb:10.5.8').pull() case "mariadb":
} docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
sh "./docker_db.sh mariadb" docker.image('mariadb:10.5.8').pull()
break; }
case "postgresql": sh "./docker_db.sh mariadb"
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { containerName = "mariadb"
docker.image('postgres:9.5').pull() break;
} case "postgresql":
sh "./docker_db.sh postgresql_9_5" docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
break; docker.image('postgres:9.5').pull()
case "oracle": }
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { sh "./docker_db.sh postgresql_9_5"
docker.image('quillbuilduser/oracle-18-xe').pull() containerName = "postgres"
} break;
sh "./docker_db.sh oracle" case "oracle":
break; docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
case "db2": docker.image('quillbuilduser/oracle-18-xe').pull()
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { }
docker.image('ibmcom/db2:11.5.5.0').pull() sh "./docker_db.sh oracle"
} containerName = "oracle"
sh "./docker_db.sh db2" break;
break; case "db2":
case "mssql": docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
docker.image('mcr.microsoft.com/mssql/server:2017-CU13').pull() docker.image('ibmcom/db2:11.5.5.0').pull()
sh "./docker_db.sh mssql" }
break; sh "./docker_db.sh db2"
case "sybase": containerName = "db2"
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') { break;
docker.image('nguoianphu/docker-sybase').pull() case "mssql":
} docker.image('mcr.microsoft.com/mssql/server:2017-CU13').pull()
sh "./docker_db.sh sybase" sh "./docker_db.sh mssql"
break; containerName = "mssql"
case "edb": break;
docker.withRegistry('https://containers.enterprisedb.com', 'hibernateci.containers.enterprisedb.com') { case "sybase":
// withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'hibernateci.containers.enterprisedb.com', docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
// usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD']]) { docker.image('nguoianphu/docker-sybase').pull()
// sh 'docker login -u "$USERNAME" -p "$PASSWORD" https://containers.enterprisedb.com' }
docker.image('containers.enterprisedb.com/edb/edb-as-lite:v11').pull() sh "./docker_db.sh sybase"
} containerName = "sybase"
sh "./docker_db.sh edb" break;
break; case "edb":
} docker.withRegistry('https://containers.enterprisedb.com', 'hibernateci.containers.enterprisedb.com') {
} // withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'hibernateci.containers.enterprisedb.com',
stage('Test') { // usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD']]) {
String goal; // sh 'docker login -u "$USERNAME" -p "$PASSWORD" https://containers.enterprisedb.com'
String lockableResource; docker.image('containers.enterprisedb.com/edb/edb-as-lite:v11').pull()
switch (buildEnv.dbName) { }
case "h2": sh "./docker_db.sh edb"
case "derby": containerName = "edb"
case "hsqldb": break;
goal = "-Pdb=${buildEnv.dbName}"
break;
case "mysql8":
goal = "-Pdb=mysql_ci"
break;
case "postgresql":
goal = "-Pdb=pgsql_ci"
break;
case "oracle":
goal = "-Pdb=oracle_ci -PexcludeTests=**.LockTest.testQueryTimeout*"
break;
case "oracle_ee":
goal = "-Pdb=oracle_jenkins"
lockableResource = 'ORACLE_RDS'
break;
case "hana":
goal = "-Pdb=hana_jenkins"
break;
case "edb":
goal = "-Pdb=edb_ci -DdbHost=localhost:5433"
break;
default:
goal = "-Pdb=${buildEnv.dbName}_ci"
break;
}
String cmd = "./gradlew check ${goal} -Plog-test-progress=true --stacktrace";
try {
if (lockableResource == null) {
sh cmd
} }
else { }
lock(lockableResource) { stage('Test') {
// Clean by default otherwise the PackagedEntityManager tests fail on a node that previously ran a different DB
boolean clean = true;
String goal;
String lockableResource;
switch (buildEnv.dbName) {
case "h2":
case "derby":
case "hsqldb":
goal = "-Pdb=${buildEnv.dbName}"
break;
case "mysql8":
goal = "-Pdb=mysql_ci"
break;
case "postgresql":
goal = "-Pdb=pgsql_ci"
break;
case "oracle":
goal = "-Pdb=oracle_ci -PexcludeTests=**.LockTest.testQueryTimeout*"
break;
case "oracle_ee":
goal = "-Pdb=oracle_jenkins"
lockableResource = 'ORACLE_RDS'
break;
case "hana":
// For HANA we have to also clean because this is a shared VM and the compile cache can become a problem
clean = true;
goal = "-Pdb=hana_jenkins"
break;
case "edb":
goal = "-Pdb=edb_ci -DdbHost=localhost:5433"
break;
default:
goal = "-Pdb=${buildEnv.dbName}_ci"
break;
}
String cmd = "./gradlew" + (clean ? " clean" : "") + " check ${goal} -Plog-test-progress=true --stacktrace";
try {
if (lockableResource == null) {
sh cmd sh cmd
} }
else {
lock(lockableResource) {
sh cmd
}
}
}
finally {
junit '**/target/test-results/test/*.xml'
} }
} }
finally { }
junit '**/target/test-results/test/*.xml' finally {
if ( containerName != null ) {
sh "docker rm -f ${containerName}"
} }
} }
} }
@ -218,7 +238,7 @@ void runBuildOnNode(String label, Closure body) {
node( label ) { node( label ) {
pruneDockerContainers() pruneDockerContainers()
try { try {
timeout( [time: 1, unit: 'HOURS'], body ) timeout( [time: 90, unit: 'MINUTES'], body )
} }
finally { finally {
pruneDockerContainers() pruneDockerContainers()

View File

@ -3,16 +3,72 @@
mysql_5_7() { mysql_5_7() {
docker rm -f mysql || true docker rm -f mysql || true
docker run --name mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci docker run --name mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
# Give the container some time to start
OUTPUT=
n=0
until [ "$n" -ge 5 ]
do
# Need to access STDERR. Thanks for the snippet https://stackoverflow.com/a/56577569/412446
{ OUTPUT="$( { docker logs mysql; } 2>&1 1>&3 3>&- )"; } 3>&1;
if [[ $OUTPUT == *"ready for connections"* ]]; then
break;
fi
n=$((n+1))
echo "Waiting for MySQL to start..."
sleep 3
done
if [ "$n" -ge 5 ]; then
echo "MySQL failed to start and configure after 15 seconds"
else
echo "MySQL successfully started"
fi
} }
mysql_8_0() { mysql_8_0() {
docker rm -f mysql || true docker rm -f mysql || true
docker run --name mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d mysql:8.0.21 --character-set-server=utf8mb4 --collation-server=utf8mb4_0900_ai_ci docker run --name mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d mysql:8.0.21 --character-set-server=utf8mb4 --collation-server=utf8mb4_0900_ai_ci
# Give the container some time to start
OUTPUT=
n=0
until [ "$n" -ge 5 ]
do
# Need to access STDERR. Thanks for the snippet https://stackoverflow.com/a/56577569/412446
{ OUTPUT="$( { docker logs mysql; } 2>&1 1>&3 3>&- )"; } 3>&1;
if [[ $OUTPUT == *"ready for connections"* ]]; then
break;
fi
n=$((n+1))
echo "Waiting for MySQL to start..."
sleep 3
done
if [ "$n" -ge 5 ]; then
echo "MySQL failed to start and configure after 15 seconds"
else
echo "MySQL successfully started"
fi
} }
mariadb() { mariadb() {
docker rm -f mariadb || true docker rm -f mariadb || true
docker run --name mariadb -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d mariadb:10.5.8 --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci docker run --name mariadb -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d mariadb:10.5.8 --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
OUTPUT=
n=0
until [ "$n" -ge 5 ]
do
# Need to access STDERR. Thanks for the snippet https://stackoverflow.com/a/56577569/412446
{ OUTPUT="$( { docker logs mariadb; } 2>&1 1>&3 3>&- )"; } 3>&1;
if [[ $OUTPUT == *"ready for connections"* ]]; then
break;
fi
n=$((n+1))
echo "Waiting for MariaDB to start..."
sleep 3
done
if [ "$n" -ge 5 ]; then
echo "MariaDB failed to start and configure after 15 seconds"
else
echo "MariaDB successfully started"
fi
} }
postgresql_9_5() { postgresql_9_5() {

View File

@ -145,6 +145,13 @@ ext {
'jdbc.pass' : 'hibernate_orm_test', 'jdbc.pass' : 'hibernate_orm_test',
'jdbc.url' : 'jdbc:oracle:thin:@hibernate-testing-oracle-se.ccuzkqo3zqzq.us-east-1.rds.amazonaws.com:1521:ORCL' 'jdbc.url' : 'jdbc:oracle:thin:@hibernate-testing-oracle-se.ccuzkqo3zqzq.us-east-1.rds.amazonaws.com:1521:ORCL'
], ],
oracle_rds : [
'db.dialect' : 'org.hibernate.dialect.OracleDialect',
'jdbc.driver': 'oracle.jdbc.OracleDriver',
'jdbc.user' : 'hibernate_orm_test',
'jdbc.pass' : 'hibernate_orm_test',
'jdbc.url' : 'jdbc:oracle:thin:@localhost:1521:ORCL'
],
// Use ./docker_db.sh oracle_ee to start the database // Use ./docker_db.sh oracle_ee to start the database
oracle_docker : [ oracle_docker : [
'db.dialect' : 'org.hibernate.dialect.OracleDialect', 'db.dialect' : 'org.hibernate.dialect.OracleDialect',

View File

@ -129,15 +129,41 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
Expression fetchExpression, Expression fetchExpression,
FetchClauseType fetchClauseType, FetchClauseType fetchClauseType,
boolean emulateFetchClause) { boolean emulateFetchClause) {
if ( queryPart instanceof QuerySpec && !queryPart.hasSortSpecifications() && offsetExpression == null && fetchClauseType == FetchClauseType.ROWS_ONLY ) { if ( queryPart instanceof QuerySpec && offsetExpression == null && fetchClauseType == FetchClauseType.ROWS_ONLY ) {
// Special case for Oracle to support locking along with simple max results paging // Special case for Oracle to support locking along with simple max results paging
final QuerySpec querySpec = (QuerySpec) queryPart; final QuerySpec querySpec = (QuerySpec) queryPart;
withRowNumbering( withRowNumbering(
querySpec, querySpec,
false,
() -> { () -> {
appendSql( "select * from (" ); final QueryPart currentQueryPart = getQueryPartStack().getCurrent();
final boolean needsParenthesis;
final boolean needsWrapper;
if ( currentQueryPart instanceof QueryGroup ) {
needsParenthesis = false;
// visitQuerySpec will add the select wrapper
needsWrapper = !currentQueryPart.hasOffsetOrFetchClause();
}
else {
needsParenthesis = !querySpec.isRoot();
needsWrapper = true;
}
if ( needsWrapper ) {
if ( needsParenthesis ) {
appendSql( '(' );
}
appendSql( "select * from " );
if ( !needsParenthesis ) {
appendSql( '(' );
}
}
super.visitQuerySpec( querySpec ); super.visitQuerySpec( querySpec );
appendSql( ") where rownum <= " ); if ( needsWrapper ) {
if ( !needsParenthesis ) {
appendSql( ')' );
}
}
appendSql( " where rownum <= " );
final Stack<Clause> clauseStack = getClauseStack(); final Stack<Clause> clauseStack = getClauseStack();
clauseStack.push( Clause.WHERE ); clauseStack.push( Clause.WHERE );
try { try {
@ -151,6 +177,12 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
finally { finally {
clauseStack.pop(); clauseStack.pop();
} }
if ( needsWrapper ) {
if ( needsParenthesis ) {
appendSql( ')' );
}
}
} }
); );
} }
@ -165,6 +197,29 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
} }
} }
@Override
protected void visitOrderBy(List<SortSpecification> sortSpecifications) {
// If we have a query part for row numbering, there is no need to render the order by clause
// as that is part of the row numbering window function already, by which we then order by in the outer query
final QueryPart queryPartForRowNumbering = getQueryPartForRowNumbering();
if ( queryPartForRowNumbering == null ) {
renderOrderBy( true, sortSpecifications );
}
else {
// This logic is tightly coupled to emulateFetchOffsetWithWindowFunctions
// so that this is rendered when we end up in the special case for Oracle that renders a rownum filter
final FetchClauseType fetchClauseType = getFetchClauseTypeForRowNumbering( queryPartForRowNumbering );
if ( fetchClauseType == FetchClauseType.ROWS_ONLY && queryPartForRowNumbering instanceof QuerySpec ) {
final QuerySpec querySpec = (QuerySpec) queryPartForRowNumbering;
if ( querySpec.getOffsetClauseExpression() == null
&& ( !querySpec.isRoot() || getOffsetParameter() == null ) ) {
// When rendering `rownum` for Oracle, we need to render the order by clause still
renderOrderBy( true, sortSpecifications );
}
}
}
}
@Override @Override
protected void visitValuesList(List<Values> valuesList) { protected void visitValuesList(List<Values> valuesList) {
if ( valuesList.size() < 2 ) { if ( valuesList.size() < 2 ) {
@ -223,22 +278,12 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
@Override @Override
protected void renderRowNumber(SelectClause selectClause, QueryPart queryPart) { protected void renderRowNumber(SelectClause selectClause, QueryPart queryPart) {
if ( supportsOffsetFetchClause() || selectClause.isDistinct() ) { if ( !queryPart.hasSortSpecifications() ) {
final List<SortSpecification> sortSpecifications = getSortSpecificationsRowNumbering( selectClause, queryPart ); // Oracle doesn't allow an empty over clause for the row_number() function
if ( selectClause.isDistinct() ) { appendSql( "rownum" );
appendSql( "dense_rank()" );
}
else {
if ( sortSpecifications.isEmpty() ) {
appendSql( "rownum" );
return;
}
appendSql( "row_number()" );
}
visitOverClause( Collections.emptyList(), sortSpecifications );
} }
else { else {
appendSql( "rownum" ); super.renderRowNumber( selectClause, queryPart );
} }
} }

View File

@ -1644,7 +1644,25 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
continue OUTER; continue OUTER;
} }
} }
expressions.add( new SqlSelectionExpression( selection ) ); if ( currentSqmQueryPart instanceof SqmQueryGroup<?> ) {
// Reusing the SqlSelection for query groups would be wrong because the aliases do no exist
// So we have to use a literal expression in a new SqlSelection instance to refer to the position
expressions.add(
new SqlSelectionExpression(
new SqlSelectionImpl(
selection.getJdbcResultSetIndex(),
selection.getValuesArrayPosition(),
new QueryLiteral<>(
selection.getValuesArrayPosition(),
StandardBasicTypes.INTEGER
)
)
)
);
}
else {
expressions.add( new SqlSelectionExpression( selection ) );
}
} }
if ( expressions.size() == 1 ) { if ( expressions.size() == 1 ) {

View File

@ -127,6 +127,11 @@ public class BasicValuedPathInterpretation<T> extends AbstractSqmPathInterpretat
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// DomainResultProducer // DomainResultProducer
@Override
public Expression getSqlExpression() {
return columnReference;
}
@Override @Override
public void accept(SqlAstWalker sqlTreeWalker) { public void accept(SqlAstWalker sqlTreeWalker) {
columnReference.accept( sqlTreeWalker ); columnReference.accept( sqlTreeWalker );

View File

@ -77,6 +77,11 @@ public class DiscriminatedAssociationPathInterpretation<T> extends AbstractSqmPa
this.sqlTuple = sqlTuple; this.sqlTuple = sqlTuple;
} }
@Override
public SqlTuple getSqlExpression() {
return sqlTuple;
}
@Override @Override
public void accept(SqlAstWalker sqlTreeWalker) { public void accept(SqlAstWalker sqlTreeWalker) {
sqlTuple.accept( sqlTreeWalker ); sqlTuple.accept( sqlTreeWalker );

View File

@ -86,6 +86,7 @@ public class EmbeddableValuedPathInterpretation<T> extends AbstractSqmPathInterp
this.sqlExpression = sqlExpression; this.sqlExpression = sqlExpression;
} }
@Override
public SqlTuple getSqlExpression() { public SqlTuple getSqlExpression() {
return sqlExpression; return sqlExpression;
} }

View File

@ -232,6 +232,11 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
this.sqlExpression = sqlExpression; this.sqlExpression = sqlExpression;
} }
@Override
public Expression getSqlExpression() {
return sqlExpression;
}
@Override @Override
public void accept(SqlAstWalker sqlTreeWalker) { public void accept(SqlAstWalker sqlTreeWalker) {
sqlExpression.accept( sqlTreeWalker ); sqlExpression.accept( sqlTreeWalker );

View File

@ -58,6 +58,7 @@ public class NonAggregatedCompositeValuedPathInterpretation<T>
this.sqlExpression = sqlExpression; this.sqlExpression = sqlExpression;
} }
@Override
public SqlTuple getSqlExpression() { public SqlTuple getSqlExpression() {
return sqlExpression; return sqlExpression;
} }

View File

@ -48,6 +48,11 @@ public class PluralValuedSimplePathInterpretation<T> extends AbstractSqmPathInte
this.sqlExpression = sqlExpression; this.sqlExpression = sqlExpression;
} }
@Override
public Expression getSqlExpression() {
return sqlExpression;
}
@Override @Override
public void accept(SqlAstWalker sqlTreeWalker) { public void accept(SqlAstWalker sqlTreeWalker) {
throw new NotYetImplementedFor6Exception( getClass() ); throw new NotYetImplementedFor6Exception( getClass() );

View File

@ -24,4 +24,8 @@ public interface SqmPathInterpretation<T> extends Expression, DomainResultProduc
@Override @Override
ModelPart getExpressionType(); ModelPart getExpressionType();
default Expression getSqlExpression() {
return this;
}
} }

View File

@ -29,6 +29,7 @@ import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Queryable; import org.hibernate.persister.entity.Queryable;
import org.hibernate.query.IllegalQueryOperationException; import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation;
import org.hibernate.sql.ast.tree.cte.CteMaterialization; import org.hibernate.sql.ast.tree.cte.CteMaterialization;
import org.hibernate.sql.ast.tree.cte.CteSearchClauseKind; import org.hibernate.sql.ast.tree.cte.CteSearchClauseKind;
import org.hibernate.query.FetchClauseType; import org.hibernate.query.FetchClauseType;
@ -154,9 +155,11 @@ import org.hibernate.sql.exec.spi.JdbcParameterBinding;
import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.exec.spi.JdbcSelect;
import org.hibernate.sql.exec.spi.JdbcUpdate; import org.hibernate.sql.exec.spi.JdbcUpdate;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.sql.results.jdbc.internal.JdbcValuesMappingProducerStandard; import org.hibernate.sql.results.jdbc.internal.JdbcValuesMappingProducerStandard;
import org.hibernate.type.BasicType; import org.hibernate.type.BasicType;
import org.hibernate.type.IntegerType; import org.hibernate.type.IntegerType;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.StringType; import org.hibernate.type.StringType;
import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
@ -192,7 +195,12 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
private final Set<String> affectedTableNames = new HashSet<>(); private final Set<String> affectedTableNames = new HashSet<>();
private String dmlTargetTableAlias; private String dmlTargetTableAlias;
private boolean needsSelectAliases; private boolean needsSelectAliases;
// We must reset the queryPartForRowNumbering fields to null if a query part is visited that does not
// contribute to the row numbering i.e. if the query part is a sub-query in the where clause.
// To determine whether a query part contributes to row numbering, we remember the clause depth
// and when visiting a query part, compare the current clause depth against the remembered one.
private QueryPart queryPartForRowNumbering; private QueryPart queryPartForRowNumbering;
private int queryPartForRowNumberingClauseDepth = -1;
private int queryPartForRowNumberingAliasCounter; private int queryPartForRowNumberingAliasCounter;
private int queryGroupAliasCounter; private int queryGroupAliasCounter;
private transient AbstractSqmSelfRenderingFunctionDescriptor castFunction; private transient AbstractSqmSelfRenderingFunctionDescriptor castFunction;
@ -1360,22 +1368,51 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
protected void renderQueryGroup(QueryGroup queryGroup, boolean renderOrderByAndOffsetFetchClause) { protected void renderQueryGroup(QueryGroup queryGroup, boolean renderOrderByAndOffsetFetchClause) {
final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering; final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering;
final int queryPartForRowNumberingClauseDepth = this.queryPartForRowNumberingClauseDepth;
final boolean needsSelectAliases = this.needsSelectAliases; final boolean needsSelectAliases = this.needsSelectAliases;
try { try {
String queryGroupAlias = null; String queryGroupAlias = null;
// See the field documentation of queryPartForRowNumbering etc. for an explanation about this
final QueryPart currentQueryPart = queryPartStack.getCurrent(); final QueryPart currentQueryPart = queryPartStack.getCurrent();
if ( currentQueryPart != null && queryPartForRowNumbering != currentQueryPart ) { if ( currentQueryPart != null && queryPartForRowNumberingClauseDepth != clauseStack.depth() ) {
this.queryPartForRowNumbering = null; this.queryPartForRowNumbering = null;
this.queryPartForRowNumberingClauseDepth = -1;
this.needsSelectAliases = false; this.needsSelectAliases = false;
} }
// If we do row counting for this query group, the wrapper select is added by the caller // If we are row numbering the current query group, this means that we can't render the
if ( queryPartForRowNumbering != queryGroup && !queryGroup.isRoot() ) { // order by and offset fetch clause, so we must do row counting on the query group level
if ( queryPartForRowNumbering == queryGroup ) {
this.needsSelectAliases = true; this.needsSelectAliases = true;
queryGroupAlias = "grp_" + queryGroupAliasCounter + "_"; queryGroupAlias = "grp_" + queryGroupAliasCounter + "_";
queryGroupAliasCounter++; queryGroupAliasCounter++;
appendSql( "select " ); appendSql( "select " );
appendSql( queryGroupAlias ); appendSql( queryGroupAlias );
appendSql( ".* from (" ); appendSql( ".* " );
final SelectClause firstSelectClause = queryGroup.getFirstQuerySpec().getSelectClause();
final List<SqlSelection> sqlSelections = firstSelectClause.getSqlSelections();
final int sqlSelectionsSize = sqlSelections.size();
// We need this synthetic select clause to properly render the ORDER BY within the OVER clause
// of the row numbering functions
final SelectClause syntheticSelectClause = new SelectClause( sqlSelectionsSize );
for ( int i = 0; i < sqlSelectionsSize; i++ ) {
syntheticSelectClause.addSqlSelection(
new SqlSelectionImpl(
i + 1,
i,
new ColumnReference(
queryGroupAlias,
"c" + i,
false,
null,
null,
StandardBasicTypes.INTEGER,
null
)
)
);
}
renderRowNumberingSelectItems( syntheticSelectClause, queryPartForRowNumbering );
appendSql( " from (" );
} }
queryPartStack.push( queryGroup ); queryPartStack.push( queryGroup );
final List<QueryPart> queryParts = queryGroup.getQueryParts(); final List<QueryPart> queryParts = queryGroup.getQueryParts();
@ -1399,6 +1436,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
finally { finally {
queryPartStack.pop(); queryPartStack.pop();
this.queryPartForRowNumbering = queryPartForRowNumbering; this.queryPartForRowNumbering = queryPartForRowNumbering;
this.queryPartForRowNumberingClauseDepth = queryPartForRowNumberingClauseDepth;
this.needsSelectAliases = needsSelectAliases; this.needsSelectAliases = needsSelectAliases;
} }
} }
@ -1406,23 +1444,29 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
@Override @Override
public void visitQuerySpec(QuerySpec querySpec) { public void visitQuerySpec(QuerySpec querySpec) {
final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering; final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering;
final int queryPartForRowNumberingClauseDepth = this.queryPartForRowNumberingClauseDepth;
final boolean needsSelectAliases = this.needsSelectAliases; final boolean needsSelectAliases = this.needsSelectAliases;
final ForUpdateClause forUpdate = this.forUpdate; final ForUpdateClause forUpdate = this.forUpdate;
try { try {
this.forUpdate = null; this.forUpdate = null;
// See the field documentation of queryPartForRowNumbering etc. for an explanation about this
// In addition, we also reset the row numbering if the currently row numbered query part is a query group
// which means this query spec is a part of that query group.
// We want the row numbering to happen on the query group level, not on the query spec level, so we reset
final QueryPart currentQueryPart = queryPartStack.getCurrent(); final QueryPart currentQueryPart = queryPartStack.getCurrent();
if ( currentQueryPart != null && queryPartForRowNumbering != currentQueryPart ) { if ( currentQueryPart != null && ( queryPartForRowNumbering instanceof QueryGroup || queryPartForRowNumberingClauseDepth != clauseStack.depth() ) ) {
this.queryPartForRowNumbering = null; this.queryPartForRowNumbering = null;
this.queryPartForRowNumberingClauseDepth = -1;
} }
String queryGroupAlias = ""; String queryGroupAlias = "";
final boolean needsParenthesis; final boolean needsParenthesis;
if ( currentQueryPart instanceof QueryGroup ) { if ( currentQueryPart instanceof QueryGroup ) {
// We always need query wrapping if we are in a query group and the query part has a fetch clause // We always need query wrapping if we are in a query group and this query spec has a fetch clause
// because of order by precedence in SQL
if ( needsParenthesis = querySpec.hasOffsetOrFetchClause() ) { if ( needsParenthesis = querySpec.hasOffsetOrFetchClause() ) {
// If the parent is a query group with a fetch clause, we must use an alias // If the parent is a query group with a fetch clause,
// Some DBMS don't support grouping query expressions and need a select wrapper // or if the database does not support simple query grouping, we must use a select wrapper
if ( !supportsSimpleQueryGrouping() || currentQueryPart.hasOffsetOrFetchClause() ) { if ( !supportsSimpleQueryGrouping() || currentQueryPart.hasOffsetOrFetchClause() ) {
this.needsSelectAliases = true;
queryGroupAlias = " grp_" + queryGroupAliasCounter + "_"; queryGroupAlias = " grp_" + queryGroupAliasCounter + "_";
queryGroupAliasCounter++; queryGroupAliasCounter++;
appendSql( "select" ); appendSql( "select" );
@ -1436,7 +1480,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
} }
queryPartStack.push( querySpec ); queryPartStack.push( querySpec );
if ( needsParenthesis ) { if ( needsParenthesis ) {
appendSql( "(" ); appendSql( '(' );
} }
visitSelectClause( querySpec.getSelectClause() ); visitSelectClause( querySpec.getSelectClause() );
visitFromClause( querySpec.getFromClause() ); visitFromClause( querySpec.getFromClause() );
@ -1451,13 +1495,14 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
} }
if ( needsParenthesis ) { if ( needsParenthesis ) {
appendSql( ")" ); appendSql( ')' );
appendSql( queryGroupAlias ); appendSql( queryGroupAlias );
} }
} }
finally { finally {
this.queryPartStack.pop(); this.queryPartStack.pop();
this.queryPartForRowNumbering = queryPartForRowNumbering; this.queryPartForRowNumbering = queryPartForRowNumbering;
this.queryPartForRowNumberingClauseDepth = queryPartForRowNumberingClauseDepth;
this.needsSelectAliases = needsSelectAliases; this.needsSelectAliases = needsSelectAliases;
if ( queryPartForRowNumbering == null ) { if ( queryPartForRowNumbering == null ) {
this.forUpdate = forUpdate; this.forUpdate = forUpdate;
@ -1485,6 +1530,12 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
} }
protected Expression resolveAliasedExpression(Expression expression) { protected Expression resolveAliasedExpression(Expression expression) {
// This can happen when using window functions for emulating the offset/fetch clause of a query group
// But in that case we always use a SqlSelectionExpression anyway, so this is fine as it doesn't need resolving
if ( queryPartStack.getCurrent() == null ) {
assert expression instanceof SqlSelectionExpression;
return ( (SqlSelectionExpression) expression ).getSelection().getExpression();
}
return resolveAliasedExpression( return resolveAliasedExpression(
queryPartStack.getCurrent().getFirstQuerySpec().getSelectClause().getSqlSelections(), queryPartStack.getCurrent().getFirstQuerySpec().getSelectClause().getSqlSelections(),
expression expression
@ -1501,6 +1552,12 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
else if ( expression instanceof SqlSelectionExpression ) { else if ( expression instanceof SqlSelectionExpression ) {
return ( (SqlSelectionExpression) expression ).getSelection().getExpression(); return ( (SqlSelectionExpression) expression ).getSelection().getExpression();
} }
else if ( expression instanceof SqmPathInterpretation<?> ) {
final Expression sqlExpression = ( (SqmPathInterpretation<?>) expression ).getSqlExpression();
if ( sqlExpression instanceof SqlSelectionExpression ) {
return ( (SqlSelectionExpression) sqlExpression ).getSelection().getExpression();
}
}
return expression; return expression;
} }
@ -1958,7 +2015,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
} }
} }
public void visitSortSpecification(Expression sortExpression, SortOrder sortOrder, NullPrecedence nullPrecedence) { protected void visitSortSpecification(Expression sortExpression, SortOrder sortOrder, NullPrecedence nullPrecedence) {
if ( nullPrecedence == null || nullPrecedence == NullPrecedence.NONE ) { if ( nullPrecedence == null || nullPrecedence == NullPrecedence.NONE ) {
nullPrecedence = sessionFactory.getSessionFactoryOptions().getDefaultNullPrecedence(); nullPrecedence = sessionFactory.getSessionFactoryOptions().getDefaultNullPrecedence();
} }
@ -2624,12 +2681,25 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
FetchClauseType fetchClauseType, FetchClauseType fetchClauseType,
boolean emulateFetchClause) { boolean emulateFetchClause) {
final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering; final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering;
final int queryPartForRowNumberingClauseDepth = this.queryPartForRowNumberingClauseDepth;
final boolean needsSelectAliases = this.needsSelectAliases; final boolean needsSelectAliases = this.needsSelectAliases;
try { try {
this.queryPartForRowNumbering = queryPart; this.queryPartForRowNumbering = queryPart;
this.queryPartForRowNumberingClauseDepth = clauseStack.depth();
this.needsSelectAliases = true; this.needsSelectAliases = true;
final String alias = "r_" + queryPartForRowNumberingAliasCounter + "_"; final String alias = "r_" + queryPartForRowNumberingAliasCounter + "_";
queryPartForRowNumberingAliasCounter++; queryPartForRowNumberingAliasCounter++;
final boolean needsParenthesis;
if ( queryPart instanceof QueryGroup ) {
// We always need query wrapping if we are in a query group and the query part has a fetch clause
needsParenthesis = queryPart.hasOffsetOrFetchClause();
}
else {
needsParenthesis = !queryPart.isRoot();
}
if ( needsParenthesis && !queryPart.isRoot() ) {
appendSql( '(' );
}
appendSql( "select " ); appendSql( "select " );
if ( getClauseStack().isEmpty() ) { if ( getClauseStack().isEmpty() ) {
appendSql( "*" ); appendSql( "*" );
@ -2645,15 +2715,21 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
separator = COMA_SEPARATOR; separator = COMA_SEPARATOR;
} }
} }
appendSql( " from (" ); appendSql( " from " );
if ( !needsParenthesis || queryPart.isRoot() ) {
appendSql( '(' );
}
queryPart.accept( this ); queryPart.accept( this );
appendSql( ") "); if ( !needsParenthesis || queryPart.isRoot() ) {
appendSql( ')' );
}
appendSql( ' ');
appendSql( alias ); appendSql( alias );
appendSql( " where " ); appendSql( " where " );
final Stack<Clause> clauseStack = getClauseStack(); final Stack<Clause> clauseStack = getClauseStack();
clauseStack.push( Clause.WHERE ); clauseStack.push( Clause.WHERE );
try { try {
if ( emulateFetchClause ) { if ( emulateFetchClause && fetchExpression != null ) {
switch ( fetchClauseType ) { switch ( fetchClauseType ) {
case PERCENT_ONLY: case PERCENT_ONLY:
appendSql( alias ); appendSql( alias );
@ -2671,6 +2747,10 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
case ROWS_ONLY: case ROWS_ONLY:
appendSql( alias ); appendSql( alias );
appendSql( ".rn <= " ); appendSql( ".rn <= " );
if ( offsetExpression != null ) {
offsetExpression.accept( this );
appendSql( " + " );
}
fetchExpression.accept( this ); fetchExpression.accept( this );
break; break;
case PERCENT_WITH_TIES: case PERCENT_WITH_TIES:
@ -2689,37 +2769,45 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
case ROWS_WITH_TIES: case ROWS_WITH_TIES:
appendSql( alias ); appendSql( alias );
appendSql( ".rnk <= " ); appendSql( ".rnk <= " );
if ( offsetExpression != null ) {
offsetExpression.accept( this );
appendSql( " + " );
}
fetchExpression.accept( this ); fetchExpression.accept( this );
break; break;
} }
} }
// todo: not sure if databases handle order by row number or the original ordering better.. // todo: not sure if databases handle order by row number or the original ordering better..
if ( offsetExpression == null ) { if ( offsetExpression == null ) {
switch ( fetchClauseType ) { if ( queryPart.isRoot() ) {
case PERCENT_ONLY: switch ( fetchClauseType ) {
case ROWS_ONLY: case PERCENT_ONLY:
appendSql( " order by " ); case ROWS_ONLY:
appendSql( alias ); appendSql( " order by " );
appendSql( ".rn" ); appendSql( alias );
break; appendSql( ".rn" );
case PERCENT_WITH_TIES: break;
case ROWS_WITH_TIES: case PERCENT_WITH_TIES:
appendSql( " order by " ); case ROWS_WITH_TIES:
appendSql( alias ); appendSql( " order by " );
appendSql( ".rnk" ); appendSql( alias );
break; appendSql( ".rnk" );
break;
}
} }
} }
else { else {
if ( emulateFetchClause ) { if ( emulateFetchClause && fetchExpression != null ) {
appendSql( " and " ); appendSql( " and " );
} }
appendSql( alias ); appendSql( alias );
appendSql( ".rn > " ); appendSql( ".rn > " );
offsetExpression.accept( this ); offsetExpression.accept( this );
appendSql( " order by " ); if ( queryPart.isRoot() ) {
appendSql( alias ); appendSql( " order by " );
appendSql( ".rn" ); appendSql( alias );
appendSql( ".rn" );
}
} }
// We render the FOR UPDATE clause in the outer query // We render the FOR UPDATE clause in the outer query
@ -2732,24 +2820,31 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
finally { finally {
clauseStack.pop(); clauseStack.pop();
} }
if ( needsParenthesis && !queryPart.isRoot() ) {
appendSql( ')' );
}
} }
finally { finally {
this.queryPartForRowNumbering = queryPartForRowNumbering; this.queryPartForRowNumbering = queryPartForRowNumbering;
this.queryPartForRowNumberingClauseDepth = queryPartForRowNumberingClauseDepth;
this.needsSelectAliases = needsSelectAliases; this.needsSelectAliases = needsSelectAliases;
} }
} }
protected final void withRowNumbering(QueryPart queryPart, Runnable r) { protected final void withRowNumbering(QueryPart queryPart, boolean needsSelectAliases, Runnable r) {
final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering; final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering;
final boolean needsSelectAliases = this.needsSelectAliases; final int queryPartForRowNumberingClauseDepth = this.queryPartForRowNumberingClauseDepth;
final boolean originalNeedsSelectAliases = this.needsSelectAliases;
try { try {
this.queryPartForRowNumbering = queryPart; this.queryPartForRowNumbering = queryPart;
this.needsSelectAliases = false; this.queryPartForRowNumberingClauseDepth = clauseStack.depth();
this.needsSelectAliases = needsSelectAliases;
r.run(); r.run();
} }
finally { finally {
this.queryPartForRowNumbering = queryPartForRowNumbering; this.queryPartForRowNumbering = queryPartForRowNumbering;
this.needsSelectAliases = needsSelectAliases; this.queryPartForRowNumberingClauseDepth = queryPartForRowNumberingClauseDepth;
this.needsSelectAliases = originalNeedsSelectAliases;
} }
} }
@ -2787,37 +2882,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
separator = COMA_SEPARATOR; separator = COMA_SEPARATOR;
} }
if ( queryPartForRowNumbering != null ) { if ( queryPartForRowNumbering != null ) {
final FetchClauseType fetchClauseType = getFetchClauseTypeForRowNumbering( queryPartForRowNumbering ); renderRowNumberingSelectItems( selectClause, queryPartForRowNumbering );
if ( fetchClauseType != null ) {
appendSql( separator );
switch ( fetchClauseType ) {
case PERCENT_ONLY:
appendSql( "count(*) over () cnt," );
case ROWS_ONLY:
renderRowNumber( selectClause, queryPartForRowNumbering );
appendSql( " rn" );
break;
case PERCENT_WITH_TIES:
appendSql( "count(*) over () cnt," );
case ROWS_WITH_TIES:
if ( queryPartForRowNumbering.getOffsetClauseExpression() != null ) {
renderRowNumber( selectClause, queryPartForRowNumbering );
appendSql( " rn, " );
}
if ( selectClause.isDistinct() ) {
appendSql( "dense_rank()" );
}
else {
appendSql( "rank()" );
}
visitOverClause(
Collections.emptyList(),
getSortSpecificationsRowNumbering( selectClause, queryPartForRowNumbering )
);
appendSql( " rnk" );
break;
}
}
} }
} }
else { else {
@ -2831,6 +2896,40 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
} }
} }
protected void renderRowNumberingSelectItems(SelectClause selectClause, QueryPart queryPart) {
final FetchClauseType fetchClauseType = getFetchClauseTypeForRowNumbering( queryPart );
if ( fetchClauseType != null ) {
appendSql( COMA_SEPARATOR );
switch ( fetchClauseType ) {
case PERCENT_ONLY:
appendSql( "count(*) over () cnt," );
case ROWS_ONLY:
renderRowNumber( selectClause, queryPart );
appendSql( " rn" );
break;
case PERCENT_WITH_TIES:
appendSql( "count(*) over () cnt," );
case ROWS_WITH_TIES:
if ( queryPart.getOffsetClauseExpression() != null ) {
renderRowNumber( selectClause, queryPart );
appendSql( " rn, " );
}
if ( selectClause.isDistinct() ) {
appendSql( "dense_rank()" );
}
else {
appendSql( "rank()" );
}
visitOverClause(
Collections.emptyList(),
getSortSpecificationsRowNumbering( selectClause, queryPart )
);
appendSql( " rnk" );
break;
}
}
}
protected FetchClauseType getFetchClauseTypeForRowNumbering(QueryPart queryPartForRowNumbering) { protected FetchClauseType getFetchClauseTypeForRowNumbering(QueryPart queryPartForRowNumbering) {
if ( queryPartForRowNumbering.isRoot() && hasLimit() ) { if ( queryPartForRowNumbering.isRoot() && hasLimit() ) {
return FetchClauseType.ROWS_ONLY; return FetchClauseType.ROWS_ONLY;
@ -2912,6 +3011,39 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
return sortSpecificationsRowNumbering; return sortSpecificationsRowNumbering;
} }
} }
else if ( queryPart instanceof QueryGroup ) {
// When the sort specifications come from a query group which uses positional references
// we have to resolve to the actual selection expressions
final int specificationsSize = sortSpecifications.size();
final List<SortSpecification> sortSpecificationsRowNumbering = new ArrayList<>( specificationsSize );
final List<SqlSelection> sqlSelections = selectClause.getSqlSelections();
for ( int i = 0; i < specificationsSize; i++ ) {
final SortSpecification sortSpecification = sortSpecifications.get( i );
final int position;
if ( sortSpecification.getSortExpression() instanceof SqlSelectionExpression ) {
position = ( (SqlSelectionExpression) sortSpecification.getSortExpression() )
.getSelection()
.getValuesArrayPosition();
}
else {
assert sortSpecification.getSortExpression() instanceof QueryLiteral;
final QueryLiteral<?> queryLiteral = (QueryLiteral<?>) sortSpecification.getSortExpression();
assert queryLiteral.getLiteralValue() instanceof Integer;
position = (Integer) queryLiteral.getLiteralValue();
}
sortSpecificationsRowNumbering.add(
new SortSpecification(
new SqlSelectionExpression(
sqlSelections.get( position )
),
null,
sortSpecification.getSortOrder(),
sortSpecification.getNullPrecedence()
)
);
}
return sortSpecificationsRowNumbering;
}
else { else {
return sortSpecifications; return sortSpecifications;
} }
@ -4187,9 +4319,11 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
} }
final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering; final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering;
final int queryPartForRowNumberingClauseDepth = this.queryPartForRowNumberingClauseDepth;
final boolean needsSelectAliases = this.needsSelectAliases; final boolean needsSelectAliases = this.needsSelectAliases;
try { try {
this.queryPartForRowNumbering = null; this.queryPartForRowNumbering = null;
this.queryPartForRowNumberingClauseDepth = -1;
this.needsSelectAliases = false; this.needsSelectAliases = false;
queryPartStack.push( subQuery ); queryPartStack.push( subQuery );
appendSql( "exists (select 1" ); appendSql( "exists (select 1" );
@ -4242,11 +4376,12 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
} }
} }
appendSql( ")" ); appendSql( ')' );
} }
finally { finally {
queryPartStack.pop(); queryPartStack.pop();
this.queryPartForRowNumbering = queryPartForRowNumbering; this.queryPartForRowNumbering = queryPartForRowNumbering;
this.queryPartForRowNumberingClauseDepth = queryPartForRowNumberingClauseDepth;
this.needsSelectAliases = needsSelectAliases; this.needsSelectAliases = needsSelectAliases;
} }
} }
@ -4279,12 +4414,14 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
appendSql( " " ); appendSql( " " );
final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering; final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering;
final int queryPartForRowNumberingClauseDepth = this.queryPartForRowNumberingClauseDepth;
final boolean needsSelectAliases = this.needsSelectAliases; final boolean needsSelectAliases = this.needsSelectAliases;
try { try {
this.queryPartForRowNumbering = null; this.queryPartForRowNumbering = null;
this.queryPartForRowNumberingClauseDepth = -1;
this.needsSelectAliases = false; this.needsSelectAliases = false;
queryPartStack.push( subQuery ); queryPartStack.push( subQuery );
appendSql( "(" ); appendSql( '(' );
visitSelectClause( subQuery.getSelectClause() ); visitSelectClause( subQuery.getSelectClause() );
visitFromClause( subQuery.getFromClause() ); visitFromClause( subQuery.getFromClause() );
visitWhereClause( subQuery ); visitWhereClause( subQuery );
@ -4309,11 +4446,12 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
appendSql( order ); appendSql( order );
} }
renderFetch( ONE_LITERAL, null, FetchClauseType.ROWS_ONLY ); renderFetch( ONE_LITERAL, null, FetchClauseType.ROWS_ONLY );
appendSql( ")" ); appendSql( ')' );
} }
finally { finally {
queryPartStack.pop(); queryPartStack.pop();
this.queryPartForRowNumbering = queryPartForRowNumbering; this.queryPartForRowNumbering = queryPartForRowNumbering;
this.queryPartForRowNumberingClauseDepth = queryPartForRowNumberingClauseDepth;
this.needsSelectAliases = needsSelectAliases; this.needsSelectAliases = needsSelectAliases;
} }
} }

View File

@ -23,7 +23,15 @@ import org.hibernate.query.sqm.sql.internal.DomainResultProducer;
public class SelectClause implements SqlAstNode { public class SelectClause implements SqlAstNode {
private boolean distinct; private boolean distinct;
private final List<SqlSelection> sqlSelections = new ArrayList<>(); private final List<SqlSelection> sqlSelections;
public SelectClause() {
this.sqlSelections = new ArrayList<>();
}
public SelectClause(int estimateSelectionSize) {
this.sqlSelections = new ArrayList<>( estimateSelectionSize );
}
public void makeDistinct(boolean distinct) { public void makeDistinct(boolean distinct) {
this.distinct = distinct; this.distinct = distinct;