Fix issues with Oracle 12.1c
This commit is contained in:
parent
ad49a41cd9
commit
e82a45d616
|
@ -60,11 +60,13 @@ stage('Build') {
|
|||
environments.each { BuildEnvironment buildEnv ->
|
||||
executions.put(buildEnv.tag, {
|
||||
runBuildOnNode(buildEnv.node) {
|
||||
def containerName = null
|
||||
env.JAVA_HOME="${tool buildEnv.buildJdkTool}"
|
||||
env.PATH="${env.JAVA_HOME}/bin:${env.PATH}"
|
||||
stage('Checkout') {
|
||||
checkout scm
|
||||
}
|
||||
try {
|
||||
stage('Start database') {
|
||||
switch (buildEnv.dbName) {
|
||||
case "mysql8":
|
||||
|
@ -72,53 +74,63 @@ stage('Build') {
|
|||
docker.image('mysql:8.0.21').pull()
|
||||
}
|
||||
sh "./docker_db.sh mysql_8_0"
|
||||
containerName = "mysql"
|
||||
break;
|
||||
case "mariadb":
|
||||
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
|
||||
docker.image('mariadb:10.5.8').pull()
|
||||
}
|
||||
sh "./docker_db.sh mariadb"
|
||||
containerName = "mariadb"
|
||||
break;
|
||||
case "postgresql":
|
||||
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
|
||||
docker.image('postgres:9.5').pull()
|
||||
}
|
||||
sh "./docker_db.sh postgresql_9_5"
|
||||
containerName = "postgres"
|
||||
break;
|
||||
case "oracle":
|
||||
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
|
||||
docker.image('quillbuilduser/oracle-18-xe').pull()
|
||||
}
|
||||
sh "./docker_db.sh oracle"
|
||||
containerName = "oracle"
|
||||
break;
|
||||
case "db2":
|
||||
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
|
||||
docker.image('ibmcom/db2:11.5.5.0').pull()
|
||||
}
|
||||
sh "./docker_db.sh db2"
|
||||
containerName = "db2"
|
||||
break;
|
||||
case "mssql":
|
||||
docker.image('mcr.microsoft.com/mssql/server:2017-CU13').pull()
|
||||
sh "./docker_db.sh mssql"
|
||||
containerName = "mssql"
|
||||
break;
|
||||
case "sybase":
|
||||
docker.withRegistry('https://index.docker.io/v1/', 'hibernateci.hub.docker.com') {
|
||||
docker.image('nguoianphu/docker-sybase').pull()
|
||||
}
|
||||
sh "./docker_db.sh sybase"
|
||||
containerName = "sybase"
|
||||
break;
|
||||
case "edb":
|
||||
docker.withRegistry('https://containers.enterprisedb.com', 'hibernateci.containers.enterprisedb.com') {
|
||||
// withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'hibernateci.containers.enterprisedb.com',
|
||||
// usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD']]) {
|
||||
// sh 'docker login -u "$USERNAME" -p "$PASSWORD" https://containers.enterprisedb.com'
|
||||
// withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'hibernateci.containers.enterprisedb.com',
|
||||
// usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD']]) {
|
||||
// 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 edb"
|
||||
containerName = "edb"
|
||||
break;
|
||||
}
|
||||
}
|
||||
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) {
|
||||
|
@ -141,6 +153,8 @@ stage('Build') {
|
|||
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":
|
||||
|
@ -150,7 +164,7 @@ stage('Build') {
|
|||
goal = "-Pdb=${buildEnv.dbName}_ci"
|
||||
break;
|
||||
}
|
||||
String cmd = "./gradlew check ${goal} -Plog-test-progress=true --stacktrace";
|
||||
String cmd = "./gradlew" + (clean ? " clean" : "") + " check ${goal} -Plog-test-progress=true --stacktrace";
|
||||
try {
|
||||
if (lockableResource == null) {
|
||||
sh cmd
|
||||
|
@ -166,6 +180,12 @@ stage('Build') {
|
|||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if ( containerName != null ) {
|
||||
sh "docker rm -f ${containerName}"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
parallel(executions)
|
||||
|
@ -218,7 +238,7 @@ void runBuildOnNode(String label, Closure body) {
|
|||
node( label ) {
|
||||
pruneDockerContainers()
|
||||
try {
|
||||
timeout( [time: 1, unit: 'HOURS'], body )
|
||||
timeout( [time: 90, unit: 'MINUTES'], body )
|
||||
}
|
||||
finally {
|
||||
pruneDockerContainers()
|
||||
|
|
56
docker_db.sh
56
docker_db.sh
|
@ -3,16 +3,72 @@
|
|||
mysql_5_7() {
|
||||
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
|
||||
# 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() {
|
||||
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
|
||||
# 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() {
|
||||
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
|
||||
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() {
|
||||
|
|
|
@ -145,6 +145,13 @@ ext {
|
|||
'jdbc.pass' : 'hibernate_orm_test',
|
||||
'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
|
||||
oracle_docker : [
|
||||
'db.dialect' : 'org.hibernate.dialect.OracleDialect',
|
||||
|
|
|
@ -129,15 +129,41 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
|
|||
Expression fetchExpression,
|
||||
FetchClauseType fetchClauseType,
|
||||
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
|
||||
final QuerySpec querySpec = (QuerySpec) queryPart;
|
||||
withRowNumbering(
|
||||
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 );
|
||||
appendSql( ") where rownum <= " );
|
||||
if ( needsWrapper ) {
|
||||
if ( !needsParenthesis ) {
|
||||
appendSql( ')' );
|
||||
}
|
||||
}
|
||||
appendSql( " where rownum <= " );
|
||||
final Stack<Clause> clauseStack = getClauseStack();
|
||||
clauseStack.push( Clause.WHERE );
|
||||
try {
|
||||
|
@ -151,6 +177,12 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
|
|||
finally {
|
||||
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
|
||||
protected void visitValuesList(List<Values> valuesList) {
|
||||
if ( valuesList.size() < 2 ) {
|
||||
|
@ -223,22 +278,12 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
|
|||
|
||||
@Override
|
||||
protected void renderRowNumber(SelectClause selectClause, QueryPart queryPart) {
|
||||
if ( supportsOffsetFetchClause() || selectClause.isDistinct() ) {
|
||||
final List<SortSpecification> sortSpecifications = getSortSpecificationsRowNumbering( selectClause, queryPart );
|
||||
if ( selectClause.isDistinct() ) {
|
||||
appendSql( "dense_rank()" );
|
||||
if ( !queryPart.hasSortSpecifications() ) {
|
||||
// Oracle doesn't allow an empty over clause for the row_number() function
|
||||
appendSql( "rownum" );
|
||||
}
|
||||
else {
|
||||
if ( sortSpecifications.isEmpty() ) {
|
||||
appendSql( "rownum" );
|
||||
return;
|
||||
}
|
||||
appendSql( "row_number()" );
|
||||
}
|
||||
visitOverClause( Collections.emptyList(), sortSpecifications );
|
||||
}
|
||||
else {
|
||||
appendSql( "rownum" );
|
||||
super.renderRowNumber( selectClause, queryPart );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1644,8 +1644,26 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
continue OUTER;
|
||||
}
|
||||
}
|
||||
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 ) {
|
||||
return expressions.get( 0 );
|
||||
|
|
|
@ -127,6 +127,11 @@ public class BasicValuedPathInterpretation<T> extends AbstractSqmPathInterpretat
|
|||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// DomainResultProducer
|
||||
|
||||
@Override
|
||||
public Expression getSqlExpression() {
|
||||
return columnReference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(SqlAstWalker sqlTreeWalker) {
|
||||
columnReference.accept( sqlTreeWalker );
|
||||
|
|
|
@ -77,6 +77,11 @@ public class DiscriminatedAssociationPathInterpretation<T> extends AbstractSqmPa
|
|||
this.sqlTuple = sqlTuple;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlTuple getSqlExpression() {
|
||||
return sqlTuple;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(SqlAstWalker sqlTreeWalker) {
|
||||
sqlTuple.accept( sqlTreeWalker );
|
||||
|
|
|
@ -86,6 +86,7 @@ public class EmbeddableValuedPathInterpretation<T> extends AbstractSqmPathInterp
|
|||
this.sqlExpression = sqlExpression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlTuple getSqlExpression() {
|
||||
return sqlExpression;
|
||||
}
|
||||
|
|
|
@ -232,6 +232,11 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
|
|||
this.sqlExpression = sqlExpression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression getSqlExpression() {
|
||||
return sqlExpression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(SqlAstWalker sqlTreeWalker) {
|
||||
sqlExpression.accept( sqlTreeWalker );
|
||||
|
|
|
@ -58,6 +58,7 @@ public class NonAggregatedCompositeValuedPathInterpretation<T>
|
|||
this.sqlExpression = sqlExpression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlTuple getSqlExpression() {
|
||||
return sqlExpression;
|
||||
}
|
||||
|
|
|
@ -48,6 +48,11 @@ public class PluralValuedSimplePathInterpretation<T> extends AbstractSqmPathInte
|
|||
this.sqlExpression = sqlExpression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression getSqlExpression() {
|
||||
return sqlExpression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(SqlAstWalker sqlTreeWalker) {
|
||||
throw new NotYetImplementedFor6Exception( getClass() );
|
||||
|
|
|
@ -24,4 +24,8 @@ public interface SqmPathInterpretation<T> extends Expression, DomainResultProduc
|
|||
|
||||
@Override
|
||||
ModelPart getExpressionType();
|
||||
|
||||
default Expression getSqlExpression() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.hibernate.metamodel.mapping.ModelPart;
|
|||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.persister.entity.Queryable;
|
||||
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.CteSearchClauseKind;
|
||||
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.JdbcSelect;
|
||||
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.type.BasicType;
|
||||
import org.hibernate.type.IntegerType;
|
||||
import org.hibernate.type.StandardBasicTypes;
|
||||
import org.hibernate.type.StringType;
|
||||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
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 String dmlTargetTableAlias;
|
||||
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 int queryPartForRowNumberingClauseDepth = -1;
|
||||
private int queryPartForRowNumberingAliasCounter;
|
||||
private int queryGroupAliasCounter;
|
||||
private transient AbstractSqmSelfRenderingFunctionDescriptor castFunction;
|
||||
|
@ -1360,22 +1368,51 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
|
||||
protected void renderQueryGroup(QueryGroup queryGroup, boolean renderOrderByAndOffsetFetchClause) {
|
||||
final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering;
|
||||
final int queryPartForRowNumberingClauseDepth = this.queryPartForRowNumberingClauseDepth;
|
||||
final boolean needsSelectAliases = this.needsSelectAliases;
|
||||
try {
|
||||
String queryGroupAlias = null;
|
||||
// See the field documentation of queryPartForRowNumbering etc. for an explanation about this
|
||||
final QueryPart currentQueryPart = queryPartStack.getCurrent();
|
||||
if ( currentQueryPart != null && queryPartForRowNumbering != currentQueryPart ) {
|
||||
if ( currentQueryPart != null && queryPartForRowNumberingClauseDepth != clauseStack.depth() ) {
|
||||
this.queryPartForRowNumbering = null;
|
||||
this.queryPartForRowNumberingClauseDepth = -1;
|
||||
this.needsSelectAliases = false;
|
||||
}
|
||||
// If we do row counting for this query group, the wrapper select is added by the caller
|
||||
if ( queryPartForRowNumbering != queryGroup && !queryGroup.isRoot() ) {
|
||||
// If we are row numbering the current query group, this means that we can't render the
|
||||
// order by and offset fetch clause, so we must do row counting on the query group level
|
||||
if ( queryPartForRowNumbering == queryGroup ) {
|
||||
this.needsSelectAliases = true;
|
||||
queryGroupAlias = "grp_" + queryGroupAliasCounter + "_";
|
||||
queryGroupAliasCounter++;
|
||||
appendSql( "select " );
|
||||
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 );
|
||||
final List<QueryPart> queryParts = queryGroup.getQueryParts();
|
||||
|
@ -1399,6 +1436,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
finally {
|
||||
queryPartStack.pop();
|
||||
this.queryPartForRowNumbering = queryPartForRowNumbering;
|
||||
this.queryPartForRowNumberingClauseDepth = queryPartForRowNumberingClauseDepth;
|
||||
this.needsSelectAliases = needsSelectAliases;
|
||||
}
|
||||
}
|
||||
|
@ -1406,23 +1444,29 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
@Override
|
||||
public void visitQuerySpec(QuerySpec querySpec) {
|
||||
final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering;
|
||||
final int queryPartForRowNumberingClauseDepth = this.queryPartForRowNumberingClauseDepth;
|
||||
final boolean needsSelectAliases = this.needsSelectAliases;
|
||||
final ForUpdateClause forUpdate = this.forUpdate;
|
||||
try {
|
||||
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();
|
||||
if ( currentQueryPart != null && queryPartForRowNumbering != currentQueryPart ) {
|
||||
if ( currentQueryPart != null && ( queryPartForRowNumbering instanceof QueryGroup || queryPartForRowNumberingClauseDepth != clauseStack.depth() ) ) {
|
||||
this.queryPartForRowNumbering = null;
|
||||
this.queryPartForRowNumberingClauseDepth = -1;
|
||||
}
|
||||
String queryGroupAlias = "";
|
||||
final boolean needsParenthesis;
|
||||
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 the parent is a query group with a fetch clause, we must use an alias
|
||||
// Some DBMS don't support grouping query expressions and need a select wrapper
|
||||
// If the parent is a query group with a fetch clause,
|
||||
// or if the database does not support simple query grouping, we must use a select wrapper
|
||||
if ( !supportsSimpleQueryGrouping() || currentQueryPart.hasOffsetOrFetchClause() ) {
|
||||
this.needsSelectAliases = true;
|
||||
queryGroupAlias = " grp_" + queryGroupAliasCounter + "_";
|
||||
queryGroupAliasCounter++;
|
||||
appendSql( "select" );
|
||||
|
@ -1436,7 +1480,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
}
|
||||
queryPartStack.push( querySpec );
|
||||
if ( needsParenthesis ) {
|
||||
appendSql( "(" );
|
||||
appendSql( '(' );
|
||||
}
|
||||
visitSelectClause( querySpec.getSelectClause() );
|
||||
visitFromClause( querySpec.getFromClause() );
|
||||
|
@ -1451,13 +1495,14 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
}
|
||||
|
||||
if ( needsParenthesis ) {
|
||||
appendSql( ")" );
|
||||
appendSql( ')' );
|
||||
appendSql( queryGroupAlias );
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.queryPartStack.pop();
|
||||
this.queryPartForRowNumbering = queryPartForRowNumbering;
|
||||
this.queryPartForRowNumberingClauseDepth = queryPartForRowNumberingClauseDepth;
|
||||
this.needsSelectAliases = needsSelectAliases;
|
||||
if ( queryPartForRowNumbering == null ) {
|
||||
this.forUpdate = forUpdate;
|
||||
|
@ -1485,6 +1530,12 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
}
|
||||
|
||||
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(
|
||||
queryPartStack.getCurrent().getFirstQuerySpec().getSelectClause().getSqlSelections(),
|
||||
expression
|
||||
|
@ -1501,6 +1552,12 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
else if ( expression instanceof SqlSelectionExpression ) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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 ) {
|
||||
nullPrecedence = sessionFactory.getSessionFactoryOptions().getDefaultNullPrecedence();
|
||||
}
|
||||
|
@ -2624,12 +2681,25 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
FetchClauseType fetchClauseType,
|
||||
boolean emulateFetchClause) {
|
||||
final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering;
|
||||
final int queryPartForRowNumberingClauseDepth = this.queryPartForRowNumberingClauseDepth;
|
||||
final boolean needsSelectAliases = this.needsSelectAliases;
|
||||
try {
|
||||
this.queryPartForRowNumbering = queryPart;
|
||||
this.queryPartForRowNumberingClauseDepth = clauseStack.depth();
|
||||
this.needsSelectAliases = true;
|
||||
final String alias = "r_" + 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 " );
|
||||
if ( getClauseStack().isEmpty() ) {
|
||||
appendSql( "*" );
|
||||
|
@ -2645,15 +2715,21 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
separator = COMA_SEPARATOR;
|
||||
}
|
||||
}
|
||||
appendSql( " from (" );
|
||||
appendSql( " from " );
|
||||
if ( !needsParenthesis || queryPart.isRoot() ) {
|
||||
appendSql( '(' );
|
||||
}
|
||||
queryPart.accept( this );
|
||||
appendSql( ") ");
|
||||
if ( !needsParenthesis || queryPart.isRoot() ) {
|
||||
appendSql( ')' );
|
||||
}
|
||||
appendSql( ' ');
|
||||
appendSql( alias );
|
||||
appendSql( " where " );
|
||||
final Stack<Clause> clauseStack = getClauseStack();
|
||||
clauseStack.push( Clause.WHERE );
|
||||
try {
|
||||
if ( emulateFetchClause ) {
|
||||
if ( emulateFetchClause && fetchExpression != null ) {
|
||||
switch ( fetchClauseType ) {
|
||||
case PERCENT_ONLY:
|
||||
appendSql( alias );
|
||||
|
@ -2671,6 +2747,10 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
case ROWS_ONLY:
|
||||
appendSql( alias );
|
||||
appendSql( ".rn <= " );
|
||||
if ( offsetExpression != null ) {
|
||||
offsetExpression.accept( this );
|
||||
appendSql( " + " );
|
||||
}
|
||||
fetchExpression.accept( this );
|
||||
break;
|
||||
case PERCENT_WITH_TIES:
|
||||
|
@ -2689,12 +2769,17 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
case ROWS_WITH_TIES:
|
||||
appendSql( alias );
|
||||
appendSql( ".rnk <= " );
|
||||
if ( offsetExpression != null ) {
|
||||
offsetExpression.accept( this );
|
||||
appendSql( " + " );
|
||||
}
|
||||
fetchExpression.accept( this );
|
||||
break;
|
||||
}
|
||||
}
|
||||
// todo: not sure if databases handle order by row number or the original ordering better..
|
||||
if ( offsetExpression == null ) {
|
||||
if ( queryPart.isRoot() ) {
|
||||
switch ( fetchClauseType ) {
|
||||
case PERCENT_ONLY:
|
||||
case ROWS_ONLY:
|
||||
|
@ -2710,17 +2795,20 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ( emulateFetchClause ) {
|
||||
if ( emulateFetchClause && fetchExpression != null ) {
|
||||
appendSql( " and " );
|
||||
}
|
||||
appendSql( alias );
|
||||
appendSql( ".rn > " );
|
||||
offsetExpression.accept( this );
|
||||
if ( queryPart.isRoot() ) {
|
||||
appendSql( " order by " );
|
||||
appendSql( alias );
|
||||
appendSql( ".rn" );
|
||||
}
|
||||
}
|
||||
|
||||
// We render the FOR UPDATE clause in the outer query
|
||||
if ( queryPart instanceof QuerySpec ) {
|
||||
|
@ -2732,24 +2820,31 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
finally {
|
||||
clauseStack.pop();
|
||||
}
|
||||
if ( needsParenthesis && !queryPart.isRoot() ) {
|
||||
appendSql( ')' );
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.queryPartForRowNumbering = queryPartForRowNumbering;
|
||||
this.queryPartForRowNumberingClauseDepth = queryPartForRowNumberingClauseDepth;
|
||||
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 boolean needsSelectAliases = this.needsSelectAliases;
|
||||
final int queryPartForRowNumberingClauseDepth = this.queryPartForRowNumberingClauseDepth;
|
||||
final boolean originalNeedsSelectAliases = this.needsSelectAliases;
|
||||
try {
|
||||
this.queryPartForRowNumbering = queryPart;
|
||||
this.needsSelectAliases = false;
|
||||
this.queryPartForRowNumberingClauseDepth = clauseStack.depth();
|
||||
this.needsSelectAliases = needsSelectAliases;
|
||||
r.run();
|
||||
}
|
||||
finally {
|
||||
this.queryPartForRowNumbering = queryPartForRowNumbering;
|
||||
this.needsSelectAliases = needsSelectAliases;
|
||||
this.queryPartForRowNumberingClauseDepth = queryPartForRowNumberingClauseDepth;
|
||||
this.needsSelectAliases = originalNeedsSelectAliases;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2787,21 +2882,36 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
separator = COMA_SEPARATOR;
|
||||
}
|
||||
if ( queryPartForRowNumbering != null ) {
|
||||
final FetchClauseType fetchClauseType = getFetchClauseTypeForRowNumbering( queryPartForRowNumbering );
|
||||
if ( fetchClauseType != null ) {
|
||||
renderRowNumberingSelectItems( selectClause, queryPartForRowNumbering );
|
||||
}
|
||||
}
|
||||
else {
|
||||
String separator = NO_SEPARATOR;
|
||||
for ( int i = 0; i < size; i++ ) {
|
||||
final SqlSelection sqlSelection = sqlSelections.get( i );
|
||||
appendSql( separator );
|
||||
visitSqlSelection( sqlSelection );
|
||||
separator = COMA_SEPARATOR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, queryPartForRowNumbering );
|
||||
renderRowNumber( selectClause, queryPart );
|
||||
appendSql( " rn" );
|
||||
break;
|
||||
case PERCENT_WITH_TIES:
|
||||
appendSql( "count(*) over () cnt," );
|
||||
case ROWS_WITH_TIES:
|
||||
if ( queryPartForRowNumbering.getOffsetClauseExpression() != null ) {
|
||||
renderRowNumber( selectClause, queryPartForRowNumbering );
|
||||
if ( queryPart.getOffsetClauseExpression() != null ) {
|
||||
renderRowNumber( selectClause, queryPart );
|
||||
appendSql( " rn, " );
|
||||
}
|
||||
if ( selectClause.isDistinct() ) {
|
||||
|
@ -2812,24 +2922,13 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
}
|
||||
visitOverClause(
|
||||
Collections.emptyList(),
|
||||
getSortSpecificationsRowNumbering( selectClause, queryPartForRowNumbering )
|
||||
getSortSpecificationsRowNumbering( selectClause, queryPart )
|
||||
);
|
||||
appendSql( " rnk" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
String separator = NO_SEPARATOR;
|
||||
for ( int i = 0; i < size; i++ ) {
|
||||
final SqlSelection sqlSelection = sqlSelections.get( i );
|
||||
appendSql( separator );
|
||||
visitSqlSelection( sqlSelection );
|
||||
separator = COMA_SEPARATOR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected FetchClauseType getFetchClauseTypeForRowNumbering(QueryPart queryPartForRowNumbering) {
|
||||
if ( queryPartForRowNumbering.isRoot() && hasLimit() ) {
|
||||
|
@ -2912,6 +3011,39 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
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 {
|
||||
return sortSpecifications;
|
||||
}
|
||||
|
@ -4187,9 +4319,11 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
}
|
||||
|
||||
final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering;
|
||||
final int queryPartForRowNumberingClauseDepth = this.queryPartForRowNumberingClauseDepth;
|
||||
final boolean needsSelectAliases = this.needsSelectAliases;
|
||||
try {
|
||||
this.queryPartForRowNumbering = null;
|
||||
this.queryPartForRowNumberingClauseDepth = -1;
|
||||
this.needsSelectAliases = false;
|
||||
queryPartStack.push( subQuery );
|
||||
appendSql( "exists (select 1" );
|
||||
|
@ -4242,11 +4376,12 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
}
|
||||
}
|
||||
|
||||
appendSql( ")" );
|
||||
appendSql( ')' );
|
||||
}
|
||||
finally {
|
||||
queryPartStack.pop();
|
||||
this.queryPartForRowNumbering = queryPartForRowNumbering;
|
||||
this.queryPartForRowNumberingClauseDepth = queryPartForRowNumberingClauseDepth;
|
||||
this.needsSelectAliases = needsSelectAliases;
|
||||
}
|
||||
}
|
||||
|
@ -4279,12 +4414,14 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
appendSql( " " );
|
||||
|
||||
final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering;
|
||||
final int queryPartForRowNumberingClauseDepth = this.queryPartForRowNumberingClauseDepth;
|
||||
final boolean needsSelectAliases = this.needsSelectAliases;
|
||||
try {
|
||||
this.queryPartForRowNumbering = null;
|
||||
this.queryPartForRowNumberingClauseDepth = -1;
|
||||
this.needsSelectAliases = false;
|
||||
queryPartStack.push( subQuery );
|
||||
appendSql( "(" );
|
||||
appendSql( '(' );
|
||||
visitSelectClause( subQuery.getSelectClause() );
|
||||
visitFromClause( subQuery.getFromClause() );
|
||||
visitWhereClause( subQuery );
|
||||
|
@ -4309,11 +4446,12 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
appendSql( order );
|
||||
}
|
||||
renderFetch( ONE_LITERAL, null, FetchClauseType.ROWS_ONLY );
|
||||
appendSql( ")" );
|
||||
appendSql( ')' );
|
||||
}
|
||||
finally {
|
||||
queryPartStack.pop();
|
||||
this.queryPartForRowNumbering = queryPartForRowNumbering;
|
||||
this.queryPartForRowNumberingClauseDepth = queryPartForRowNumberingClauseDepth;
|
||||
this.needsSelectAliases = needsSelectAliases;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,15 @@ import org.hibernate.query.sqm.sql.internal.DomainResultProducer;
|
|||
public class SelectClause implements SqlAstNode {
|
||||
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) {
|
||||
this.distinct = distinct;
|
||||
|
|
Loading…
Reference in New Issue