HHH-17506 Support ON CONFLICT clause for HQL/Criteria inserts

This commit is contained in:
Christian Beikov 2023-12-11 20:05:22 +01:00
parent c931c86896
commit bb4ed4b000
146 changed files with 5981 additions and 1580 deletions

View File

@ -5,7 +5,7 @@
# See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
#
hibernate.dialect org.hibernate.dialect.HANAColumnStoreDialect
hibernate.dialect org.hibernate.dialect.HANADialect
hibernate.connection.driver_class com.sap.db.jdbc.Driver
hibernate.connection.url jdbc:sap://localhost:39015/
hibernate.connection.username HIBERNATE_TEST

View File

@ -161,7 +161,7 @@ hibernate.connection.url @DB_URL@
## HANA
#hibernate.dialect org.hibernate.dialect.HANAColumnStoreDialect
#hibernate.dialect org.hibernate.dialect.HANADialect
#hibernate.connection.driver_class com.sap.db.jdbc.Driver
#hibernate.connection.url jdbc:sap://localhost:30015
#hibernate.connection.username HIBERNATE_TEST

View File

@ -254,7 +254,7 @@ ext {
'connection.init_sql' : ''
],
hana_cloud : [
'db.dialect' : 'org.hibernate.dialect.HANAColumnStoreDialect',
'db.dialect' : 'org.hibernate.dialect.HANADialect',
'jdbc.driver': 'com.sap.db.jdbc.Driver',
'jdbc.user' : 'HIBERNATE_TEST',
'jdbc.pass' : 'H1bernate_test',
@ -263,7 +263,7 @@ ext {
'connection.init_sql' : ''
],
hana_ci : [
'db.dialect' : 'org.hibernate.dialect.HANAColumnStoreDialect',
'db.dialect' : 'org.hibernate.dialect.HANADialect',
'jdbc.driver': 'com.sap.db.jdbc.Driver',
'jdbc.user' : 'SYSTEM',
'jdbc.pass' : 'H1bernate_test',

View File

@ -146,13 +146,14 @@ public class AltibaseSqlAstTranslator<T extends JdbcOperation> extends AbstractS
emulateQueryPartTableReferenceColumnAliasing( tableReference );
}
protected String getFromDual() {
return " from dual";
@Override
protected String getDual() {
return "dual";
}
@Override
protected String getFromDualForSelectOnly() {
return getFromDual();
return " from " + getDual();
}
@Override

View File

@ -82,14 +82,14 @@ public class CUBRIDSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
}
@Override
protected String getFromDual() {
protected String getDual() {
//TODO: is this really needed?
//TODO: would "from table({0})" be better?
return " from db_root";
return "db_root";
}
@Override
protected String getFromDualForSelectOnly() {
return getFromDual();
return " from " + getDual();
}
}

View File

@ -570,6 +570,10 @@ public class CockroachLegacyDialect extends Dialect {
public boolean supportsRecursiveCTE() {
return getVersion().isSameOrAfter( 20, 1 );
}
@Override
public boolean supportsConflictClauseForInsertCTE() {
return true;
}
@Override
public String getNoColumnsInsertString() {
@ -1165,4 +1169,14 @@ public class CockroachLegacyDialect extends Dialect {
// RuntimeModelCreationContext runtimeModelCreationContext) {
// return new CteInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext );
// }
@Override
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return DmlTargetColumnQualifierSupport.TABLE_ALIAS;
}
@Override
public boolean supportsFromClauseInUpdate() {
return true;
}
}

View File

@ -7,20 +7,27 @@
package org.hibernate.community.dialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.cte.CteMaterialization;
import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.InArrayPredicate;
import org.hibernate.sql.ast.tree.predicate.LikePredicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.internal.JdbcOperationQueryInsertImpl;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert;
/**
* A SQL AST translator for Cockroach.
@ -33,6 +40,56 @@ public class CockroachLegacySqlAstTranslator<T extends JdbcOperation> extends Ab
super( sessionFactory, statement );
}
@Override
protected JdbcOperationQueryInsert translateInsert(InsertSelectStatement sqlAst) {
visitInsertStatement( sqlAst );
return new JdbcOperationQueryInsertImpl(
getSql(),
getParameterBinders(),
getAffectedTableNames(),
null
);
}
@Override
protected void renderTableReferenceIdentificationVariable(TableReference tableReference) {
final String identificationVariable = tableReference.getIdentificationVariable();
if ( identificationVariable != null ) {
final Clause currentClause = getClauseStack().getCurrent();
if ( currentClause == Clause.INSERT ) {
// PostgreSQL requires the "as" keyword for inserts
appendSql( " as " );
}
else {
append( WHITESPACE );
}
append( tableReference.getIdentificationVariable() );
}
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
final Statement currentStatement = getStatementStack().getCurrent();
if ( !( currentStatement instanceof UpdateStatement )
|| !hasNonTrivialFromClause( ( (UpdateStatement) currentStatement ).getFromClause() ) ) {
// For UPDATE statements we render a full FROM clause and a join condition to match target table rows,
// but for that to work, we have to omit the alias for the target table reference here
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected void renderFromClauseAfterUpdateSet(UpdateStatement statement) {
renderFromClauseJoiningDmlTargetReference( statement );
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
visitStandardConflictClause( conflictClause );
}
@Override
protected void renderExpressionAsClauseItem(Expression expression) {
expression.accept( this );

View File

@ -24,6 +24,7 @@ import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.DB2StructJdbcType;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.aggregate.AggregateSupport;
import org.hibernate.dialect.aggregate.DB2AggregateSupport;
@ -49,8 +50,11 @@ import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.LockTimeoutException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor;
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.mapping.Column;
import org.hibernate.metamodel.mapping.EntityMappingType;
@ -94,6 +98,7 @@ import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.TemporalType;
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
import static org.hibernate.type.SqlTypes.BINARY;
import static org.hibernate.type.SqlTypes.BLOB;
import static org.hibernate.type.SqlTypes.BOOLEAN;
@ -889,6 +894,20 @@ public class DB2LegacyDialect extends Dialect {
appender.appendSql( '\'' );
}
@Override
public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
return new TemplatedViolatedConstraintNameExtractor(
sqle -> {
switch ( JdbcExceptionHelper.extractErrorCode( sqle ) ) {
case -803:
return extractUsingTemplate( "SQLERRMC=1;", ",", sqle.getMessage() );
default:
return null;
}
}
);
}
@Override
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
return (sqlException, message, sql) -> {
@ -896,6 +915,14 @@ public class DB2LegacyDialect extends Dialect {
switch ( errorCode ) {
case -952:
return new LockTimeoutException( message, sqlException, sql );
case -803:
return new ConstraintViolationException(
message,
sqlException,
sql,
ConstraintViolationException.ConstraintKind.UNIQUE,
getViolatedConstraintNameExtractor().extractConstraintName( sqlException )
);
}
return null;
};
@ -1079,4 +1106,14 @@ public class DB2LegacyDialect extends Dialect {
public int rowIdSqlType() {
return VARBINARY;
}
@Override
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return DmlTargetColumnQualifierSupport.TABLE_ALIAS;
}
@Override
public boolean supportsFromClauseInUpdate() {
return getDB2Version().isSameOrAfter( 11 );
}
}

View File

@ -13,8 +13,10 @@ import org.hibernate.LockMode;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.query.sqm.FetchClauseType;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlSelection;
@ -28,10 +30,12 @@ import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableReferenceJoin;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
@ -344,12 +348,40 @@ public class DB2LegacySqlAstTranslator<T extends JdbcOperation> extends Abstract
@Override
protected void visitInsertStatementOnly(InsertSelectStatement statement) {
final boolean closeWrapper = renderReturningClause( statement );
if ( statement.getConflictClause() == null || statement.getConflictClause().isDoNothing() ) {
// Render plain insert statement and possibly run into unique constraint violation
super.visitInsertStatementOnly( statement );
}
else {
visitInsertStatementEmulateMerge( statement );
}
if ( closeWrapper ) {
appendSql( ')' );
}
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
if ( conflictClause != null ) {
if ( conflictClause.isDoUpdate() && conflictClause.getConstraintName() != null ) {
throw new IllegalQueryOperationException( "Insert conflict do update clause with constraint name is not supported" );
}
}
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
if ( getClauseStack().getCurrent() != Clause.INSERT ) {
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected void renderFromClauseAfterUpdateSet(UpdateStatement statement) {
renderFromClauseExcludingDmlTargetReference( statement );
}
protected boolean renderReturningClause(MutationStatement statement) {
final List<ColumnReference> returningColumns = statement.getReturningColumns();
final int size = returningColumns.size();
@ -497,13 +529,13 @@ public class DB2LegacySqlAstTranslator<T extends JdbcOperation> extends Abstract
}
@Override
protected String getFromDual() {
return " from sysibm.dual";
protected String getDual() {
return "sysibm.dual";
}
@Override
protected String getFromDualForSelectOnly() {
return getFromDual();
return " from " + getDual();
}
@Override

View File

@ -39,8 +39,11 @@ import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.LockTimeoutException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor;
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
@ -706,14 +709,42 @@ public class DerbyLegacyDialect extends Dialect {
return false;
}
@Override
public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
return new TemplatedViolatedConstraintNameExtractor( sqle -> {
final String sqlState = JdbcExceptionHelper.extractSqlState( sqle );
if ( sqlState != null ) {
switch ( sqlState ) {
case "23505":
return TemplatedViolatedConstraintNameExtractor.extractUsingTemplate(
"'", "'",
sqle.getMessage()
);
}
}
return null;
} );
}
@Override
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
return (sqlException, message, sql) -> {
final String sqlState = JdbcExceptionHelper.extractSqlState( sqlException );
// final int errorCode = JdbcExceptionHelper.extractErrorCode( sqlException );
final String constraintName;
if ( sqlState != null ) {
switch ( sqlState ) {
case "23505":
// Unique constraint violation
constraintName = getViolatedConstraintNameExtractor().extractConstraintName(sqlException);
return new ConstraintViolationException(
message,
sqlException,
sql,
ConstraintViolationException.ConstraintKind.UNIQUE,
constraintName
);
case "40XL1":
case "40XL2":
return new LockTimeoutException( message, sqlException, sql );

View File

@ -235,13 +235,13 @@ public class DerbyLegacySqlAstTranslator<T extends JdbcOperation> extends Abstra
}
@Override
protected String getFromDual() {
return " from (values 0) dual";
protected String getDual() {
return "(values 0)";
}
@Override
protected String getFromDualForSelectOnly() {
return getFromDual();
return " from " + getDual() + " dual";
}
@Override

View File

@ -263,13 +263,13 @@ public class FirebirdSqlAstTranslator<T extends JdbcOperation> extends AbstractS
}
@Override
protected String getFromDual() {
return " from rdb$database";
protected String getDual() {
return "rdb$database";
}
@Override
protected String getFromDualForSelectOnly() {
return getFromDual();
return " from " + getDual();
}
private boolean supportsOffsetFetchClause() {

View File

@ -776,8 +776,19 @@ public class H2LegacyDialect extends Dialect {
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
return (sqlException, message, sql) -> {
final int errorCode = JdbcExceptionHelper.extractErrorCode( sqlException );
final String constraintName;
switch (errorCode) {
case 23505:
// Unique constraint violation
constraintName = getViolatedConstraintNameExtractor().extractConstraintName(sqlException);
return new ConstraintViolationException(
message,
sqlException,
sql,
ConstraintViolationException.ConstraintKind.UNIQUE,
constraintName
);
case 40001:
// DEADLOCK DETECTED
return new LockAcquisitionException(message, sqlException, sql);
@ -786,7 +797,7 @@ public class H2LegacyDialect extends Dialect {
return new PessimisticLockException(message, sqlException, sql);
case 90006:
// NULL not allowed for column [90006-145]
final String constraintName = getViolatedConstraintNameExtractor().extractConstraintName(sqlException);
constraintName = getViolatedConstraintNameExtractor().extractConstraintName(sqlException);
return new ConstraintViolationException(message, sqlException, sql, constraintName);
case 57014:
return new QueryTimeoutException( message, sqlException, sql );
@ -940,4 +951,9 @@ public class H2LegacyDialect extends Dialect {
public int rowIdSqlType() {
return BIGINT;
}
@Override
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return DmlTargetColumnQualifierSupport.TABLE_ALIAS;
}
}

View File

@ -12,6 +12,7 @@ import org.hibernate.LockMode;
import org.hibernate.dialect.identity.H2IdentityColumnSupport;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
@ -27,14 +28,18 @@ import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.SqlTupleContainer;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
import org.hibernate.sql.ast.tree.predicate.LikePredicate;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.SelectClause;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.model.internal.TableInsertStandard;
@ -79,6 +84,44 @@ public class H2LegacySqlAstTranslator<T extends JdbcOperation> extends AbstractS
);
}
@Override
protected void visitInsertStatementOnly(InsertSelectStatement statement) {
if ( statement.getConflictClause() == null || statement.getConflictClause().isDoNothing() ) {
// Render plain insert statement and possibly run into unique constraint violation
super.visitInsertStatementOnly( statement );
}
else {
visitInsertStatementEmulateMerge( statement );
}
}
@Override
protected void visitUpdateStatementOnly(UpdateStatement statement) {
if ( hasNonTrivialFromClause( statement.getFromClause() ) ) {
visitUpdateStatementEmulateMerge( statement );
}
else {
super.visitUpdateStatementOnly( statement );
}
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
if ( getClauseStack().getCurrent() != Clause.INSERT ) {
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
if ( conflictClause != null ) {
if ( conflictClause.isDoUpdate() && conflictClause.getConstraintName() != null ) {
throw new IllegalQueryOperationException( "Insert conflict do update clause with constraint name is not supported" );
}
}
}
@Override
protected void visitReturningColumns(List<ColumnReference> returningColumns) {
// do nothing - this is handled via `#visitReturningInsertStatement`
@ -289,8 +332,8 @@ public class H2LegacySqlAstTranslator<T extends JdbcOperation> extends AbstractS
}
@Override
protected String getFromDual() {
return " from dual";
protected String getDual() {
return "dual";
}
private boolean supportsOffsetFetchClause() {

View File

@ -41,6 +41,8 @@ import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.spi.EventSource;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor;
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.CoreMessageLogger;
@ -497,6 +499,29 @@ public class HSQLLegacyDialect extends Dialect {
return null;
} );
@Override
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
return (sqlException, message, sql) -> {
final int errorCode = JdbcExceptionHelper.extractErrorCode( sqlException );
final String constraintName;
switch ( errorCode ) {
case -104:
// Unique constraint violation
constraintName = getViolatedConstraintNameExtractor().extractConstraintName(sqlException);
return new ConstraintViolationException(
message,
sqlException,
sql,
ConstraintViolationException.ConstraintKind.UNIQUE,
constraintName
);
}
return null;
};
}
/**
* HSQLDB 2.0 messages have changed
* messages may be localized - therefore use the common, non-locale element " table: "
@ -856,4 +881,9 @@ public class HSQLLegacyDialect extends Dialect {
public UniqueDelegate getUniqueDelegate() {
return uniqueDelegate;
}
@Override
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return DmlTargetColumnQualifierSupport.TABLE_ALIAS;
}
}

View File

@ -11,23 +11,32 @@ import java.util.function.Consumer;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.MutationStatement;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
@ -42,6 +51,44 @@ public class HSQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstrac
super( sessionFactory, statement );
}
@Override
protected void visitInsertStatementOnly(InsertSelectStatement statement) {
if ( statement.getConflictClause() == null || statement.getConflictClause().isDoNothing() ) {
// Render plain insert statement and possibly run into unique constraint violation
super.visitInsertStatementOnly( statement );
}
else {
visitInsertStatementEmulateMerge( statement );
}
}
@Override
protected void visitUpdateStatementOnly(UpdateStatement statement) {
if ( hasNonTrivialFromClause( statement.getFromClause() ) ) {
visitUpdateStatementEmulateMerge( statement );
}
else {
super.visitUpdateStatementOnly( statement );
}
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
if ( getClauseStack().getCurrent() != Clause.INSERT ) {
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
if ( conflictClause != null ) {
if ( conflictClause.isDoUpdate() && conflictClause.getConstraintName() != null ) {
throw new IllegalQueryOperationException( "Insert conflict do update clause with constraint name is not supported" );
}
}
}
@Override
protected void renderExpressionAsClauseItem(Expression expression) {
expression.accept( this );
@ -295,14 +342,9 @@ public class HSQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstrac
return false;
}
@Override
protected String getFromDual() {
return " from (values(0))";
}
@Override
protected String getFromDualForSelectOnly() {
return getFromDual();
return " from " + getDual();
}
private boolean supportsOffsetFetchClause() {

View File

@ -134,13 +134,13 @@ public class InformixSqlAstTranslator<T extends JdbcOperation> extends AbstractS
}
@Override
protected String getFromDual() {
return " from (select 0 from systables where tabid=1) dual";
protected String getDual() {
return "(select 0 from systables where tabid=1)";
}
@Override
protected String getFromDualForSelectOnly() {
return getFromDual();
return " from " + getDual() + " dual";
}
private boolean supportsParameterOffsetFetchExpression() {

View File

@ -139,14 +139,14 @@ public class IngresSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
}
@Override
protected String getFromDual() {
//this is only necessary if the query has a where clause
return " from (select 0) dual";
protected String getDual() {
return "(select 0)";
}
@Override
protected String getFromDualForSelectOnly() {
return getFromDual();
//this is only necessary if the query has a where clause
return " from " + getDual() + " dual";
}
@Override

View File

@ -6,22 +6,38 @@
*/
package org.hibernate.community.dialect;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
import org.hibernate.dialect.MySQLSqlAstTranslator;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.tree.MutationStatement;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.LikePredicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.internal.JdbcOperationQueryInsertImpl;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert;
/**
* A SQL AST translator for MariaDB.
@ -37,6 +53,135 @@ public class MariaDBLegacySqlAstTranslator<T extends JdbcOperation> extends Abst
this.dialect = (MariaDBLegacyDialect)super.getDialect();
}
@Override
protected void visitInsertSource(InsertSelectStatement statement) {
if ( statement.getSourceSelectStatement() != null ) {
if ( statement.getConflictClause() != null ) {
final List<ColumnReference> targetColumnReferences = statement.getTargetColumns();
final List<String> columnNames = new ArrayList<>( targetColumnReferences.size() );
for ( ColumnReference targetColumnReference : targetColumnReferences ) {
columnNames.add( targetColumnReference.getColumnExpression() );
}
appendSql( "select * from " );
emulateQueryPartTableReferenceColumnAliasing(
new QueryPartTableReference(
new SelectStatement( statement.getSourceSelectStatement() ),
"excluded",
columnNames,
false,
getSessionFactory()
)
);
}
else {
statement.getSourceSelectStatement().accept( this );
}
}
else {
visitValuesList( statement.getValuesList() );
}
}
@Override
public void visitColumnReference(ColumnReference columnReference) {
final Statement currentStatement;
if ( "excluded".equals( columnReference.getQualifier() )
&& ( currentStatement = getStatementStack().getCurrent() ) instanceof InsertSelectStatement
&& ( (InsertSelectStatement) currentStatement ).getSourceSelectStatement() == null ) {
// Accessing the excluded row for an insert-values statement in the conflict clause requires the values qualifier
appendSql( "values(" );
columnReference.appendReadExpression( this, null );
append( ')' );
}
else {
super.visitColumnReference( columnReference );
}
}
@Override
protected void renderDeleteClause(DeleteStatement statement) {
appendSql( "delete" );
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.DELETE );
renderTableReferenceIdentificationVariable( statement.getTargetTable() );
if ( statement.getFromClause().getRoots().isEmpty() ) {
appendSql( " from " );
renderDmlTargetTableExpression( statement.getTargetTable() );
}
else {
visitFromClause( statement.getFromClause() );
}
}
finally {
clauseStack.pop();
}
}
@Override
protected void renderUpdateClause(UpdateStatement updateStatement) {
if ( updateStatement.getFromClause().getRoots().isEmpty() ) {
super.renderUpdateClause( updateStatement );
}
else {
appendSql( "update " );
renderFromClauseSpaces( updateStatement.getFromClause() );
}
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
if ( getClauseStack().getCurrent() != Clause.INSERT ) {
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected boolean supportsJoinsInDelete() {
return true;
}
@Override
protected JdbcOperationQueryInsert translateInsert(InsertSelectStatement sqlAst) {
visitInsertStatement( sqlAst );
return new JdbcOperationQueryInsertImpl(
getSql(),
getParameterBinders(),
getAffectedTableNames(),
null
);
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
visitOnDuplicateKeyConflictClause( conflictClause );
}
@Override
protected String determineColumnReferenceQualifier(ColumnReference columnReference) {
final DmlTargetColumnQualifierSupport qualifierSupport = getDialect().getDmlTargetColumnQualifierSupport();
final MutationStatement currentDmlStatement;
final String dmlAlias;
// Since MariaDB does not support aliasing the insert target table,
// we must detect column reference that are used in the conflict clause
// and use the table expression as qualifier instead
if ( getClauseStack().getCurrent() != Clause.SET
|| !( ( currentDmlStatement = getCurrentDmlStatement() ) instanceof InsertSelectStatement )
|| ( dmlAlias = currentDmlStatement.getTargetTable().getIdentificationVariable() ) == null
|| !dmlAlias.equals( columnReference.getQualifier() ) ) {
return columnReference.getQualifier();
}
// Qualify the column reference with the table expression also when in subqueries
else if ( qualifierSupport != DmlTargetColumnQualifierSupport.NONE || !getQueryPartStack().isEmpty() ) {
return getCurrentDmlStatement().getTargetTable().getTableExpression();
}
else {
return null;
}
}
@Override
protected boolean supportsWithClause() {
return dialect.getVersion().isSameOrAfter( 10, 2 );
@ -222,13 +367,13 @@ public class MariaDBLegacySqlAstTranslator<T extends JdbcOperation> extends Abst
}
@Override
protected String getFromDual() {
return " from dual";
protected String getDual() {
return "dual";
}
@Override
protected String getFromDualForSelectOnly() {
return getDialect().getVersion().isBefore( 10, 4 ) ? getFromDual() : "";
return getDialect().getVersion().isBefore( 10, 4 ) ? ( " from " + getDual() ) : "";
}
@Override

View File

@ -94,12 +94,12 @@ public class MaxDBSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlA
}
@Override
public String getFromDual() {
return " from dual";
protected String getDual() {
return "dual";
}
@Override
protected String getFromDualForSelectOnly() {
return getFromDual();
return " from " + getDual();
}
}

View File

@ -82,13 +82,8 @@ public class MimerSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractS
return false;
}
@Override
protected String getFromDual() {
return " from (values(0))";
}
@Override
protected String getFromDualForSelectOnly() {
return getFromDual();
return " from " + getDual();
}
}

View File

@ -1395,4 +1395,14 @@ public class MySQLLegacyDialect extends Dialect {
return "set foreign_key_checks = 1";
}
@Override
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return DmlTargetColumnQualifierSupport.TABLE_ALIAS;
}
@Override
public boolean supportsFromClauseInUpdate() {
return true;
}
}

View File

@ -6,24 +6,40 @@
*/
package org.hibernate.community.dialect;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.dialect.DialectDelegateWrapper;
import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
import org.hibernate.dialect.MySQLSqlAstTranslator;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.tree.MutationStatement;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.LikePredicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.internal.JdbcOperationQueryInsertImpl;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert;
/**
* A SQL AST translator for MySQL.
@ -36,6 +52,146 @@ public class MySQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstra
super( sessionFactory, statement );
}
@Override
protected void visitInsertSource(InsertSelectStatement statement) {
if ( statement.getSourceSelectStatement() != null ) {
if ( statement.getConflictClause() != null ) {
final List<ColumnReference> targetColumnReferences = statement.getTargetColumns();
final List<String> columnNames = new ArrayList<>( targetColumnReferences.size() );
for ( ColumnReference targetColumnReference : targetColumnReferences ) {
columnNames.add( targetColumnReference.getColumnExpression() );
}
appendSql( "select * from " );
emulateQueryPartTableReferenceColumnAliasing(
new QueryPartTableReference(
new SelectStatement( statement.getSourceSelectStatement() ),
"excluded",
columnNames,
false,
getSessionFactory()
)
);
}
else {
statement.getSourceSelectStatement().accept( this );
}
}
else {
visitValuesList( statement.getValuesList() );
if ( statement.getConflictClause() != null && getDialect().getMySQLVersion().isSameOrAfter( 8, 0, 19 ) ) {
appendSql( " as excluded" );
char separator = '(';
for ( ColumnReference targetColumn : statement.getTargetColumns() ) {
appendSql( separator );
appendSql( targetColumn.getColumnExpression() );
separator = ',';
}
appendSql( ')' );
}
}
}
@Override
public void visitColumnReference(ColumnReference columnReference) {
final Statement currentStatement;
if ( getDialect().getMySQLVersion().isBefore( 8, 0, 19 )
&& "excluded".equals( columnReference.getQualifier() )
&& ( currentStatement = getStatementStack().getCurrent() ) instanceof InsertSelectStatement
&& ( (InsertSelectStatement) currentStatement ).getSourceSelectStatement() == null ) {
// Accessing the excluded row for an insert-values statement in the conflict clause requires the values qualifier
appendSql( "values(" );
columnReference.appendReadExpression( this, null );
append( ')' );
}
else {
super.visitColumnReference( columnReference );
}
}
@Override
protected void renderDeleteClause(DeleteStatement statement) {
appendSql( "delete" );
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.DELETE );
renderTableReferenceIdentificationVariable( statement.getTargetTable() );
if ( statement.getFromClause().getRoots().isEmpty() ) {
appendSql( " from " );
renderDmlTargetTableExpression( statement.getTargetTable() );
}
else {
visitFromClause( statement.getFromClause() );
}
}
finally {
clauseStack.pop();
}
}
@Override
protected void renderUpdateClause(UpdateStatement updateStatement) {
if ( updateStatement.getFromClause().getRoots().isEmpty() ) {
super.renderUpdateClause( updateStatement );
}
else {
appendSql( "update " );
renderFromClauseSpaces( updateStatement.getFromClause() );
}
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
if ( getClauseStack().getCurrent() != Clause.INSERT ) {
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected boolean supportsJoinsInDelete() {
return true;
}
@Override
protected JdbcOperationQueryInsert translateInsert(InsertSelectStatement sqlAst) {
visitInsertStatement( sqlAst );
return new JdbcOperationQueryInsertImpl(
getSql(),
getParameterBinders(),
getAffectedTableNames(),
null
);
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
visitOnDuplicateKeyConflictClause( conflictClause );
}
@Override
protected String determineColumnReferenceQualifier(ColumnReference columnReference) {
final DmlTargetColumnQualifierSupport qualifierSupport = getDialect().getDmlTargetColumnQualifierSupport();
final MutationStatement currentDmlStatement;
final String dmlAlias;
// Since MySQL does not support aliasing the insert target table,
// we must detect column reference that are used in the conflict clause
// and use the table expression as qualifier instead
if ( getClauseStack().getCurrent() != Clause.SET
|| !( ( currentDmlStatement = getCurrentDmlStatement() ) instanceof InsertSelectStatement )
|| ( dmlAlias = currentDmlStatement.getTargetTable().getIdentificationVariable() ) == null
|| !dmlAlias.equals( columnReference.getQualifier() ) ) {
return columnReference.getQualifier();
}
// Qualify the column reference with the table expression also when in subqueries
else if ( qualifierSupport != DmlTargetColumnQualifierSupport.NONE || !getQueryPartStack().isEmpty() ) {
return getCurrentDmlStatement().getTargetTable().getTableExpression();
}
else {
return null;
}
}
@Override
protected void renderExpressionAsClauseItem(Expression expression) {
expression.accept( this );
@ -234,13 +390,13 @@ public class MySQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstra
}
@Override
protected String getFromDual() {
return " from dual";
protected String getDual() {
return "dual";
}
@Override
protected String getFromDualForSelectOnly() {
return getDialect().getVersion().isSameOrAfter( 8 ) ? "" : getFromDual();
return getDialect().getVersion().isSameOrAfter( 8 ) ? "" : ( " from " + getDual() );
}
@Override

View File

@ -1008,6 +1008,7 @@ public class OracleLegacyDialect extends Dialect {
@Override
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
return (sqlException, message, sql) -> {
final String constraintName;
// interpreting Oracle exceptions is much much more precise based on their specific vendor codes.
switch ( JdbcExceptionHelper.extractErrorCode( sqlException ) ) {
@ -1040,9 +1041,19 @@ public class OracleLegacyDialect extends Dialect {
// data integrity violation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
case 1:
// ORA-00001: unique constraint violated
constraintName = getViolatedConstraintNameExtractor().extractConstraintName( sqlException );
return new ConstraintViolationException(
message,
sqlException,
sql,
ConstraintViolationException.ConstraintKind.UNIQUE,
constraintName
);
case 1407:
// ORA-01407: cannot update column to NULL
final String constraintName = getViolatedConstraintNameExtractor().extractConstraintName( sqlException );
constraintName = getViolatedConstraintNameExtractor().extractConstraintName( sqlException );
return new ConstraintViolationException( message, sqlException, sql, constraintName );
default:
@ -1520,4 +1531,9 @@ public class OracleLegacyDialect extends Dialect {
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return DmlTargetColumnQualifierSupport.TABLE_ALIAS;
}
@Override
public boolean supportsFromClauseInUpdate() {
return true;
}
}

View File

@ -37,10 +37,12 @@ import org.hibernate.sql.ast.tree.expression.SqlTupleContainer;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.FromClause;
import org.hibernate.sql.ast.tree.from.FunctionTableReference;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.UnionTableGroup;
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.insert.Values;
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
@ -51,6 +53,7 @@ import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectClause;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.sql.ast.tree.update.Assignment;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.type.SqlTypes;
@ -67,6 +70,54 @@ public class OracleLegacySqlAstTranslator<T extends JdbcOperation> extends Abstr
super( sessionFactory, statement );
}
@Override
protected void visitInsertStatementOnly(InsertSelectStatement statement) {
if ( statement.getConflictClause() == null || statement.getConflictClause().isDoNothing() ) {
// Render plain insert statement and possibly run into unique constraint violation
super.visitInsertStatementOnly( statement );
}
else {
visitInsertStatementEmulateMerge( statement );
}
}
@Override
protected void visitUpdateStatementOnly(UpdateStatement statement) {
if ( hasNonTrivialFromClause( statement.getFromClause() ) ) {
visitUpdateStatementEmulateInlineView( statement );
}
else {
renderUpdateClause( statement );
renderSetClause( statement.getAssignments() );
visitWhereClause( statement.getRestriction() );
visitReturningColumns( statement.getReturningColumns() );
}
}
@Override
protected void renderMergeUpdateClause(List<Assignment> assignments, Predicate wherePredicate) {
appendSql( " then update" );
renderSetClause( assignments );
visitWhereClause( wherePredicate );
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
if ( getClauseStack().getCurrent() != Clause.INSERT ) {
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
if ( conflictClause != null ) {
if ( conflictClause.isDoUpdate() && conflictClause.getConstraintName() != null ) {
throw new IllegalQueryOperationException( "Insert conflict do update clause with constraint name is not supported" );
}
}
}
@Override
protected boolean needsRecursiveKeywordInWithClause() {
return false;
@ -227,8 +278,15 @@ public class OracleLegacySqlAstTranslator<T extends JdbcOperation> extends Abstr
@Override
protected void visitValuesList(List<Values> valuesList) {
if ( valuesList.size() < 2 ) {
visitValuesListStandard( valuesList );
}
else {
// Oracle doesn't support a multi-values insert
// So we render a select union emulation instead
visitValuesListEmulateSelectUnion( valuesList );
}
}
@Override
public void visitValuesTableReference(ValuesTableReference tableReference) {
@ -617,13 +675,13 @@ public class OracleLegacySqlAstTranslator<T extends JdbcOperation> extends Abstr
}
@Override
protected String getFromDual() {
return " from dual";
protected String getDual() {
return "dual";
}
@Override
protected String getFromDualForSelectOnly() {
return getFromDual();
return " from " + getDual();
}
private boolean supportsOffsetFetchClause() {

View File

@ -732,6 +732,11 @@ public class PostgreSQLLegacyDialect extends Dialect {
return getVersion().isSameOrAfter( 9, 1 );
}
@Override
public boolean supportsConflictClauseForInsertCTE() {
return getVersion().isSameOrAfter( 9, 5 );
}
@Override
public SequenceSupport getSequenceSupport() {
return getVersion().isBefore( 8, 2 )
@ -1467,4 +1472,14 @@ public class PostgreSQLLegacyDialect extends Dialect {
// The maximum scale for `interval second` is 6 unfortunately
return 6;
}
@Override
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return DmlTargetColumnQualifierSupport.TABLE_ALIAS;
}
@Override
public boolean supportsFromClauseInUpdate() {
return true;
}
}

View File

@ -10,6 +10,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.query.sqm.FetchClauseType;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.cte.CteMaterialization;
@ -18,13 +19,20 @@ import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.LikePredicate;
import org.hibernate.sql.ast.tree.predicate.NullnessPredicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.internal.JdbcOperationQueryInsertImpl;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert;
import org.hibernate.sql.model.internal.TableInsertStandard;
import org.hibernate.type.SqlTypes;
@ -45,6 +53,56 @@ public class PostgreSQLLegacySqlAstTranslator<T extends JdbcOperation> extends A
appendSql( "default values" );
}
@Override
protected JdbcOperationQueryInsert translateInsert(InsertSelectStatement sqlAst) {
visitInsertStatement( sqlAst );
return new JdbcOperationQueryInsertImpl(
getSql(),
getParameterBinders(),
getAffectedTableNames(),
null
);
}
@Override
protected void renderTableReferenceIdentificationVariable(TableReference tableReference) {
final String identificationVariable = tableReference.getIdentificationVariable();
if ( identificationVariable != null ) {
final Clause currentClause = getClauseStack().getCurrent();
if ( currentClause == Clause.INSERT ) {
// PostgreSQL requires the "as" keyword for inserts
appendSql( " as " );
}
else {
append( WHITESPACE );
}
append( tableReference.getIdentificationVariable() );
}
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
final Statement currentStatement = getStatementStack().getCurrent();
if ( !( currentStatement instanceof UpdateStatement )
|| !hasNonTrivialFromClause( ( (UpdateStatement) currentStatement ).getFromClause() ) ) {
// For UPDATE statements we render a full FROM clause and a join condition to match target table rows,
// but for that to work, we have to omit the alias for the target table reference here
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected void renderFromClauseAfterUpdateSet(UpdateStatement statement) {
renderFromClauseJoiningDmlTargetReference( statement );
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
visitStandardConflictClause( conflictClause );
}
@Override
protected void renderExpressionAsClauseItem(Expression expression) {
expression.accept( this );
@ -218,9 +276,7 @@ public class PostgreSQLLegacySqlAstTranslator<T extends JdbcOperation> extends A
appendSql( "()" );
}
else {
appendSql( "(select 1" );
appendSql( getFromDualForSelectOnly() );
appendSql( ')' );
appendSql( "(select 1)" );
}
}
else if ( expression instanceof Summarization ) {

View File

@ -126,12 +126,12 @@ public class RDMSOS2200SqlAstTranslator<T extends JdbcOperation> extends Abstrac
}
@Override
protected String getFromDual() {
return " from rdms.rdms_dummy where key_col=1";
protected String getDual() {
return "rdms.rdms_dummy";
}
@Override
protected String getFromDualForSelectOnly() {
return getFromDual();
return " from " + getDual() + " where key_col=1";
}
}

View File

@ -16,6 +16,7 @@ import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.dialect.AbstractTransactSQLDialect;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
import org.hibernate.dialect.Replacer;
import org.hibernate.dialect.TimeZoneSupport;
import org.hibernate.dialect.function.CommonFunctionFactory;
@ -41,8 +42,11 @@ import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.LockTimeoutException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor;
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.mapping.Column;
import org.hibernate.query.sqm.CastType;
@ -85,6 +89,7 @@ import java.util.TimeZone;
import jakarta.persistence.TemporalType;
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
import static org.hibernate.query.sqm.TemporalUnit.NANOSECOND;
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.INTEGER;
import static org.hibernate.type.SqlTypes.*;
@ -723,6 +728,20 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
public boolean supportsFetchClause(FetchClauseType type) {
return getVersion().isSameOrAfter( 11 );
}
@Override
public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
return new TemplatedViolatedConstraintNameExtractor(
sqle -> {
switch ( JdbcExceptionHelper.extractErrorCode( sqle ) ) {
case 2627:
case 2601:
return extractUsingTemplate( "'", "'", sqle.getMessage() );
default:
return null;
}
}
);
}
@Override
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
@ -740,6 +759,13 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
case 1222:
return new LockTimeoutException( message, sqlException, sql );
case 2627:
return new ConstraintViolationException(
message,
sqlException,
sql,
ConstraintViolationException.ConstraintKind.UNIQUE,
getViolatedConstraintNameExtractor().extractConstraintName( sqlException )
);
case 2601:
return new ConstraintViolationException(
message,

View File

@ -12,15 +12,20 @@ import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.query.sqm.FetchClauseType;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.MutationStatement;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
@ -30,12 +35,15 @@ import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.from.UnionTableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectClause;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.type.SqlTypes;
@ -54,6 +62,84 @@ public class SQLServerLegacySqlAstTranslator<T extends JdbcOperation> extends Ab
super( sessionFactory, statement );
}
@Override
protected void visitInsertStatementOnly(InsertSelectStatement statement) {
if ( statement.getConflictClause() == null || statement.getConflictClause().isDoNothing() ) {
// Render plain insert statement and possibly run into unique constraint violation
super.visitInsertStatementOnly( statement );
}
else {
visitInsertStatementEmulateMerge( statement );
appendSql( ';' );
}
}
@Override
protected void renderDeleteClause(DeleteStatement statement) {
appendSql( "delete" );
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.DELETE );
renderTableReferenceIdentificationVariable( statement.getTargetTable() );
if ( statement.getFromClause().getRoots().isEmpty() ) {
appendSql( " from " );
renderDmlTargetTableExpression( statement.getTargetTable() );
}
else {
visitFromClause( statement.getFromClause() );
}
}
finally {
clauseStack.pop();
}
}
@Override
protected void renderUpdateClause(UpdateStatement updateStatement) {
appendSql( "update" );
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.UPDATE );
renderTableReferenceIdentificationVariable( updateStatement.getTargetTable() );
}
finally {
clauseStack.pop();
}
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
if ( getClauseStack().getCurrent() != Clause.INSERT ) {
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected boolean supportsJoinsInDelete() {
return true;
}
@Override
protected void renderFromClauseAfterUpdateSet(UpdateStatement statement) {
if ( statement.getFromClause().getRoots().isEmpty() ) {
appendSql( " from " );
renderDmlTargetTableExpression( statement.getTargetTable() );
}
else {
visitFromClause( statement.getFromClause() );
}
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
if ( conflictClause != null ) {
if ( conflictClause.isDoUpdate() && conflictClause.getConstraintName() != null ) {
throw new IllegalQueryOperationException( "Insert conflict do update clause with constraint name is not supported" );
}
}
}
@Override
protected boolean needsRecursiveKeywordInWithClause() {
return false;
@ -129,14 +215,7 @@ public class SQLServerLegacySqlAstTranslator<T extends JdbcOperation> extends Ab
appendSql( " )" );
registerAffectedTable( tableReference );
final Clause currentClause = getClauseStack().getCurrent();
if ( rendersTableReferenceAlias( currentClause ) ) {
final String identificationVariable = tableReference.getIdentificationVariable();
if ( identificationVariable != null ) {
appendSql( ' ' );
appendSql( identificationVariable );
}
}
renderTableReferenceIdentificationVariable( tableReference );
}
else {
super.renderNamedTableReference( tableReference, lockMode );

View File

@ -628,27 +628,17 @@ public class SybaseASELegacyDialect extends SybaseLegacyDialect {
final int errorCode = JdbcExceptionHelper.extractErrorCode( sqle );
if ( sqlState != null ) {
switch ( sqlState ) {
// UNIQUE VIOLATION
case "S1000":
if ( 2601 == errorCode ) {
return extractUsingTemplate( "with unique index '", "'", sqle.getMessage() );
}
break;
case "23000":
if ( 546 == errorCode ) {
switch ( errorCode ) {
case 2601:
// UNIQUE VIOLATION
return extractUsingTemplate( "with unique index '", "'", sqle.getMessage() );
case 546:
// Foreign key violation
return extractUsingTemplate( "constraint name = '", "'", sqle.getMessage() );
}
break;
// // FOREIGN KEY VIOLATION
// case 23503:
// return extractUsingTemplate( "violates foreign key constraint \"","\"", sqle.getMessage() );
// // NOT NULL VIOLATION
// case 23502:
// return extractUsingTemplate( "null value in column \"","\" violates not-null constraint", sqle.getMessage() );
// // TODO: RESTRICT VIOLATION
// case 23001:
// return null;
}
}
return null;
@ -659,7 +649,6 @@ public class SybaseASELegacyDialect extends SybaseLegacyDialect {
if ( getVersion().isBefore( 15, 7 ) ) {
return null;
}
return (sqlException, message, sql) -> {
final String sqlState = JdbcExceptionHelper.extractSqlState( sqlException );
final int errorCode = JdbcExceptionHelper.extractErrorCode( sqlException );
@ -669,30 +658,44 @@ public class SybaseASELegacyDialect extends SybaseLegacyDialect {
case "JZ006":
return new LockTimeoutException( message, sqlException, sql );
case "S1000":
case "23000":
switch ( errorCode ) {
case 515:
// Attempt to insert NULL value into column; column does not allow nulls.
return new ConstraintViolationException(
message,
sqlException,
sql,
getViolatedConstraintNameExtractor().extractConstraintName( sqlException )
);
case 546:
// Foreign key violation
return new ConstraintViolationException(
message,
sqlException,
sql,
getViolatedConstraintNameExtractor().extractConstraintName( sqlException )
);
case 2601:
// Unique constraint violation
final String constraintName = getViolatedConstraintNameExtractor().extractConstraintName(
sqlException );
return new ConstraintViolationException( message, sqlException, sql, constraintName );
return new ConstraintViolationException(
message,
sqlException,
sql,
ConstraintViolationException.ConstraintKind.UNIQUE,
getViolatedConstraintNameExtractor().extractConstraintName( sqlException )
);
}
break;
case "ZZZZZ":
if ( 515 == errorCode ) {
// Attempt to insert NULL value into column; column does not allow nulls.
final String constraintName = getViolatedConstraintNameExtractor().extractConstraintName(
sqlException );
return new ConstraintViolationException( message, sqlException, sql, constraintName );
}
break;
case "23000":
if ( 546 == errorCode ) {
// Foreign key violation
final String constraintName = getViolatedConstraintNameExtractor().extractConstraintName(
sqlException );
return new ConstraintViolationException( message, sqlException, sql, constraintName );
return new ConstraintViolationException(
message,
sqlException,
sql,
getViolatedConstraintNameExtractor().extractConstraintName( sqlException )
);
}
break;
}

View File

@ -11,14 +11,19 @@ import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.MutationStatement;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
@ -33,6 +38,9 @@ import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.UnionTableReference;
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.insert.Values;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
@ -40,6 +48,7 @@ import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectClause;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
/**
@ -55,6 +64,77 @@ public class SybaseASELegacySqlAstTranslator<T extends JdbcOperation> extends Ab
super( sessionFactory, statement );
}
@Override
protected void visitInsertStatementOnly(InsertSelectStatement statement) {
if ( statement.getConflictClause() == null || statement.getConflictClause().isDoNothing() ) {
// Render plain insert statement and possibly run into unique constraint violation
super.visitInsertStatementOnly( statement );
}
else {
visitInsertStatementEmulateMerge( statement );
}
}
@Override
protected void renderDeleteClause(DeleteStatement statement) {
appendSql( "delete " );
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.DELETE );
renderDmlTargetTableExpression( statement.getTargetTable() );
if ( statement.getFromClause().getRoots().isEmpty() ) {
appendSql( " from " );
renderDmlTargetTableExpression( statement.getTargetTable() );
renderTableReferenceIdentificationVariable( statement.getTargetTable() );
}
else {
visitFromClause( statement.getFromClause() );
}
}
finally {
clauseStack.pop();
}
}
@Override
protected void renderUpdateClause(UpdateStatement updateStatement) {
appendSql( "update " );
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.UPDATE );
renderDmlTargetTableExpression( updateStatement.getTargetTable() );
}
finally {
clauseStack.pop();
}
}
@Override
protected boolean supportsJoinsInDelete() {
return true;
}
@Override
protected void renderFromClauseAfterUpdateSet(UpdateStatement statement) {
if ( statement.getFromClause().getRoots().isEmpty() ) {
appendSql( " from " );
renderDmlTargetTableExpression( statement.getTargetTable() );
renderTableReferenceIdentificationVariable( statement.getTargetTable() );
}
else {
visitFromClause( statement.getFromClause() );
}
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
if ( conflictClause != null ) {
if ( conflictClause.isDoUpdate() && conflictClause.getConstraintName() != null ) {
throw new IllegalQueryOperationException( "Insert conflict do update clause with constraint name is not supported" );
}
}
}
@Override
protected boolean supportsWithClause() {
return false;
@ -130,14 +210,7 @@ public class SybaseASELegacySqlAstTranslator<T extends JdbcOperation> extends Ab
appendSql( " )" );
registerAffectedTable( tableReference );
final Clause currentClause = getClauseStack().getCurrent();
if ( rendersTableReferenceAlias( currentClause ) ) {
final String identificationVariable = tableReference.getIdentificationVariable();
if ( identificationVariable != null ) {
appendSql( ' ' );
appendSql( identificationVariable );
}
}
renderTableReferenceIdentificationVariable( tableReference );
}
else {
super.renderNamedTableReference( tableReference, lockMode );
@ -252,6 +325,14 @@ public class SybaseASELegacySqlAstTranslator<T extends JdbcOperation> extends Ab
visitValuesListEmulateSelectUnion( valuesList );
}
@Override
public void visitValuesTableReference(ValuesTableReference tableReference) {
append( '(' );
visitValuesListEmulateSelectUnion( tableReference.getValuesList() );
append( ')' );
renderDerivedTableReference( tableReference );
}
@Override
public void visitOffsetFetchClause(QueryPart queryPart) {
assertRowsOnlyFetchClauseType( queryPart );
@ -386,9 +467,16 @@ public class SybaseASELegacySqlAstTranslator<T extends JdbcOperation> extends Ab
}
@Override
public void visitColumnReference(ColumnReference columnReference) {
final String dmlTargetTableAlias = getDmlTargetTableAlias();
if ( dmlTargetTableAlias != null && dmlTargetTableAlias.equals( columnReference.getQualifier() ) ) {
protected String determineColumnReferenceQualifier(ColumnReference columnReference) {
final DmlTargetColumnQualifierSupport qualifierSupport = getDialect().getDmlTargetColumnQualifierSupport();
final MutationStatement currentDmlStatement;
final String dmlAlias;
if ( qualifierSupport == DmlTargetColumnQualifierSupport.TABLE_ALIAS
|| ( currentDmlStatement = getCurrentDmlStatement() ) == null
|| ( dmlAlias = currentDmlStatement.getTargetTable().getIdentificationVariable() ) == null
|| !dmlAlias.equals( columnReference.getQualifier() ) ) {
return columnReference.getQualifier();
}
// Sybase needs a table name prefix
// but not if this is a restricted union table reference subquery
final QuerySpec currentQuerySpec = (QuerySpec) getQueryPartStack().getCurrent();
@ -396,53 +484,15 @@ public class SybaseASELegacySqlAstTranslator<T extends JdbcOperation> extends Ab
if ( currentQuerySpec != null && !currentQuerySpec.isRoot()
&& (roots = currentQuerySpec.getFromClause().getRoots()).size() == 1
&& roots.get( 0 ).getPrimaryTableReference() instanceof UnionTableReference ) {
columnReference.appendReadExpression( this );
return columnReference.getQualifier();
}
// for now, use the unqualified form
else if ( columnReference.isColumnExpressionFormula() ) {
// For formulas, we have to replace the qualifier as the alias was already rendered into the formula
// This is fine for now as this is only temporary anyway until we render aliases for table references
appendSql(
columnReference.getColumnExpression()
.replaceAll( "(\\b)(" + dmlTargetTableAlias + "\\.)(\\b)", "$1$3" )
);
return null;
}
else {
columnReference.appendReadExpression(
this,
getCurrentDmlStatement().getTargetTable().getTableExpression()
);
}
}
else {
columnReference.appendReadExpression( this );
}
}
@Override
public void visitAggregateColumnWriteExpression(AggregateColumnWriteExpression aggregateColumnWriteExpression) {
final String dmlTargetTableAlias = getDmlTargetTableAlias();
final ColumnReference columnReference = aggregateColumnWriteExpression.getColumnReference();
if ( dmlTargetTableAlias != null && dmlTargetTableAlias.equals( columnReference.getQualifier() ) ) {
// Sybase needs a table name prefix
// but not if this is a restricted union table reference subquery
final QuerySpec currentQuerySpec = (QuerySpec) getQueryPartStack().getCurrent();
final List<TableGroup> roots;
if ( currentQuerySpec != null && !currentQuerySpec.isRoot()
&& (roots = currentQuerySpec.getFromClause().getRoots()).size() == 1
&& roots.get( 0 ).getPrimaryTableReference() instanceof UnionTableReference ) {
aggregateColumnWriteExpression.appendWriteExpression( this, this );
}
else {
aggregateColumnWriteExpression.appendWriteExpression(
this,
this,
getCurrentDmlStatement().getTargetTable().getTableExpression()
);
}
}
else {
aggregateColumnWriteExpression.appendWriteExpression( this, this );
return getCurrentDmlStatement().getTargetTable().getTableExpression();
}
}
@ -472,8 +522,8 @@ public class SybaseASELegacySqlAstTranslator<T extends JdbcOperation> extends Ab
}
@Override
protected String getFromDual() {
return " from (select 1) dual(c1)";
protected String getDual() {
return "(select 1 c1)";
}
private boolean supportsTopClause() {

View File

@ -116,14 +116,7 @@ public class SybaseAnywhereSqlAstTranslator<T extends JdbcOperation> extends Abs
appendSql( " )" );
registerAffectedTable( tableReference );
final Clause currentClause = getClauseStack().getCurrent();
if ( rendersTableReferenceAlias( currentClause ) ) {
final String identificationVariable = tableReference.getIdentificationVariable();
if ( identificationVariable != null ) {
appendSql( ' ' );
appendSql( identificationVariable );
}
}
renderTableReferenceIdentificationVariable( tableReference );
}
else {
super.renderNamedTableReference( tableReference, lockMode );
@ -248,12 +241,12 @@ public class SybaseAnywhereSqlAstTranslator<T extends JdbcOperation> extends Abs
}
@Override
protected String getFromDual() {
return " from sys.dummy";
protected String getDual() {
return "sys.dummy";
}
@Override
protected String getFromDualForSelectOnly() {
return getFromDual();
return " from " + getDual();
}
}

View File

@ -18,6 +18,7 @@ import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.dialect.AbstractTransactSQLDialect;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
import org.hibernate.dialect.NationalizationSupport;
import org.hibernate.dialect.SybaseDriverKind;
import org.hibernate.dialect.function.CommonFunctionFactory;
@ -473,4 +474,14 @@ public class SybaseLegacyDialect extends AbstractTransactSQLDialect {
// Only the jTDS driver supports named parameters properly
return driverKind == SybaseDriverKind.JTDS && super.supportsNamedParameters( databaseMetaData );
}
@Override
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return DmlTargetColumnQualifierSupport.TABLE_ALIAS;
}
@Override
public boolean supportsFromClauseInUpdate() {
return true;
}
}

View File

@ -11,6 +11,8 @@ import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
@ -18,6 +20,7 @@ import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
@ -27,8 +30,13 @@ import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.UnionTableReference;
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.insert.Values;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
/**
@ -44,6 +52,46 @@ public class SybaseLegacySqlAstTranslator<T extends JdbcOperation> extends Abstr
super( sessionFactory, statement );
}
@Override
protected void visitInsertStatementOnly(InsertSelectStatement statement) {
if ( statement.getConflictClause() == null || statement.getConflictClause().isDoNothing() ) {
// Render plain insert statement and possibly run into unique constraint violation
super.visitInsertStatementOnly( statement );
}
else {
visitInsertStatementEmulateMerge( statement );
appendSql( ';' );
}
}
@Override
protected void renderDeleteClause(DeleteStatement statement) {
appendSql( "delete " );
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.DELETE );
renderDmlTargetTableExpression( statement.getTargetTable() );
}
finally {
clauseStack.pop();
}
visitFromClause( statement.getFromClause() );
}
@Override
protected void renderFromClauseAfterUpdateSet(UpdateStatement statement) {
visitFromClause( statement.getFromClause() );
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
if ( conflictClause != null ) {
if ( conflictClause.isDoUpdate() && conflictClause.getConstraintName() != null ) {
throw new IllegalQueryOperationException( "Insert conflict do update clause with constraint name is not supported" );
}
}
}
@Override
protected boolean supportsWithClause() {
return false;
@ -119,14 +167,7 @@ public class SybaseLegacySqlAstTranslator<T extends JdbcOperation> extends Abstr
appendSql( " )" );
registerAffectedTable( tableReference );
final Clause currentClause = getClauseStack().getCurrent();
if ( rendersTableReferenceAlias( currentClause ) ) {
final String identificationVariable = tableReference.getIdentificationVariable();
if ( identificationVariable != null ) {
appendSql( ' ' );
appendSql( identificationVariable );
}
}
renderTableReferenceIdentificationVariable( tableReference );
}
else {
super.renderNamedTableReference( tableReference, lockMode );
@ -147,6 +188,19 @@ public class SybaseLegacySqlAstTranslator<T extends JdbcOperation> extends Abstr
// Sybase does not support the FOR UPDATE clause
}
@Override
protected void visitValuesList(List<Values> valuesList) {
visitValuesListEmulateSelectUnion( valuesList );
}
@Override
public void visitValuesTableReference(ValuesTableReference tableReference) {
append( '(' );
visitValuesListEmulateSelectUnion( tableReference.getValuesList() );
append( ')' );
renderDerivedTableReference( tableReference );
}
@Override
public void visitOffsetFetchClause(QueryPart queryPart) {
assertRowsOnlyFetchClauseType( queryPart );

View File

@ -158,6 +158,8 @@ BY : [bB] [yY];
CASE : [cC] [aA] [sS] [eE];
CAST : [cC] [aA] [sS] [tT];
COLLATE : [cC] [oO] [lL] [lL] [aA] [tT] [eE];
CONFLICT : [cC] [oO] [nN] [fF] [lL] [iI] [cC] [tT];
CONSTRAINT : [cC] [oO] [nN] [sS] [tT] [rR] [aA] [iI] [nN] [tT];
COUNT : [cC] [oO] [uU] [nN] [tT];
CROSS : [cC] [rR] [oO] [sS] [sS];
CUBE : [cC] [uU] [bB] [eE];
@ -175,6 +177,7 @@ DELETE : [dD] [eE] [lL] [eE] [tT] [eE];
DEPTH : [dD] [eE] [pP] [tT] [hH];
DESC : [dD] [eE] [sS] [cC];
DISTINCT : [dD] [iI] [sS] [tT] [iI] [nN] [cC] [tT];
DO : [dD] [oO];
ELEMENT : [eE] [lL] [eE] [mM] [eE] [nN] [tT];
ELEMENTS : [eE] [lL] [eE] [mM] [eE] [nN] [tT] [sS];
ELSE : [eE] [lL] [sS] [eE];
@ -246,6 +249,7 @@ NEW : [nN] [eE] [wW];
NEXT : [nN] [eE] [xX] [tT];
NO : [nN] [oO];
NOT : [nN] [oO] [tT];
NOTHING : [nN] [oO] [tT] [hH] [iI] [nN] [gG];
NULLS : [nN] [uU] [lL] [lL] [sS];
OBJECT : [oO] [bB] [jJ] [eE] [cC] [tT];
OF : [oO] [fF];

View File

@ -83,7 +83,7 @@ assignment
* An 'insert' statement
*/
insertStatement
: INSERT INTO? targetEntity targetFields (queryExpression | valuesList)
: INSERT INTO? targetEntity targetFields (queryExpression | valuesList) conflictClause?
;
/**
@ -107,6 +107,18 @@ values
: LEFT_PAREN expressionOrPredicate (COMMA expressionOrPredicate)* RIGHT_PAREN
;
/**
* a 'conflict' clause in an 'insert' statement
*/
conflictClause: ON CONFLICT conflictTarget? conflictAction;
conflictTarget
: ON CONSTRAINT identifier
| LEFT_PAREN simplePath (COMMA simplePath)* RIGHT_PAREN;
conflictAction
: DO NOTHING
| DO UPDATE setClause whereClause?
;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// QUERY SPEC - general structure of root sqm or sub sqm
@ -1600,6 +1612,8 @@ rollup
| CASE
| CAST
| COLLATE
| CONFLICT
| CONSTRAINT
| COUNT
| CROSS
| CUBE
@ -1617,6 +1631,7 @@ rollup
| DEPTH
| DESC
| DISTINCT
| DO
| ELEMENT
| ELEMENTS
| ELSE
@ -1690,6 +1705,7 @@ rollup
| NEXT
| NO
| NOT
| NOTHING
| NULLS
| OBJECT
| OF

View File

@ -21,6 +21,7 @@ import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.HANACloudColumnStoreDialect;
import org.hibernate.dialect.HANAColumnStoreDialect;
import org.hibernate.dialect.HANADialect;
import org.hibernate.dialect.HANARowStoreDialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MariaDBDialect;
@ -70,6 +71,8 @@ public class DefaultDialectSelector implements DialectSelector {
return findCommunityDialect( name );
case "H2":
return H2Dialect.class;
case "HANA":
return HANADialect.class;
case "HANACloudColumnStore":
return HANACloudColumnStoreDialect.class;
case "HANAColumnStore":

View File

@ -57,4 +57,12 @@ public interface DialectSpecificSettings {
*/
public static final String COCKROACH_VERSION_STRING = "hibernate.dialect.cockroach.version_string";
/**
* Specifies the LOB prefetch size. LOBs larger than this value will be read into memory as the HANA JDBC driver closes
* the LOB when the result set is closed.
*
* @settingDefault {@code 1024}
*/
public static final String HANA_MAX_LOB_PREFETCH_SIZE = "hibernate.dialect.hana.max_lob_prefetch_size";
}

View File

@ -20,20 +20,20 @@ import java.nio.charset.StandardCharsets;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Statement;
import java.sql.Types;
import java.time.temporal.TemporalAccessor;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -54,6 +54,8 @@ import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.LimitOffsetLimitHandler;
import org.hibernate.dialect.sequence.HANASequenceSupport;
import org.hibernate.dialect.sequence.SequenceSupport;
import org.hibernate.dialect.temptable.TemporaryTable;
import org.hibernate.dialect.temptable.TemporaryTableKind;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.config.spi.StandardConverters;
import org.hibernate.engine.jdbc.BinaryStream;
@ -61,7 +63,6 @@ import org.hibernate.engine.jdbc.BlobImplementer;
import org.hibernate.engine.jdbc.CharacterStream;
import org.hibernate.engine.jdbc.ClobImplementer;
import org.hibernate.engine.jdbc.NClobImplementer;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.jdbc.env.spi.IdentifierCaseStrategy;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
@ -73,15 +74,19 @@ import org.hibernate.exception.LockAcquisitionException;
import org.hibernate.exception.LockTimeoutException;
import org.hibernate.exception.SQLGrammarException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.mapping.Table;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.procedure.internal.StandardCallableStatementSupport;
import org.hibernate.procedure.spi.CallableStatementSupport;
import org.hibernate.query.sqm.CastType;
import org.hibernate.query.sqm.IntervalType;
import org.hibernate.query.sqm.TemporalUnit;
import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableInsertStrategy;
import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableMutationStrategy;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.query.sqm.produce.function.FunctionParameterType;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
@ -121,6 +126,7 @@ import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.TemporalType;
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.ANY;
import static org.hibernate.type.SqlTypes.BINARY;
import static org.hibernate.type.SqlTypes.BOOLEAN;
import static org.hibernate.type.SqlTypes.CHAR;
@ -160,15 +166,15 @@ import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithM
* <p>
* Note: This dialect is configured to create foreign keys with {@code on update cascade}.
*
* @deprecated Will be replaced with {@link HANADialect} in the future.
* @author Andrew Clemons
* @author Jonathan Bregler
*/
@Deprecated(forRemoval = true)
public abstract class AbstractHANADialect extends Dialect {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( AbstractHANADialect.class );
// Set the LOB prefetch size. LOBs larger than this value will be read into memory as the HANA JDBC driver closes
// the LOB when the result set is closed.
private static final String MAX_LOB_PREFETCH_SIZE_PARAMETER_NAME = "hibernate.dialect.hana.max_lob_prefetch_size";
// Use column or row tables by default
public static final String USE_DEFAULT_TABLE_TYPE_COLUMN = "hibernate.dialect.hana.use_default_table_type_column";
// Use TINYINT instead of the native BOOLEAN type
private static final String USE_LEGACY_BOOLEAN_TYPE_PARAMETER_NAME = "hibernate.dialect.hana.use_legacy_boolean_type";
// Use unicode (NVARCHAR, NCLOB, etc.) instead of non-unicode (VARCHAR, CLOB) string types
@ -177,18 +183,15 @@ public abstract class AbstractHANADialect extends Dialect {
// JDBC driver (https://service.sap.com/sap/support/notes/2590160)
private static final String TREAT_DOUBLE_TYPED_FIELDS_AS_DECIMAL_PARAMETER_NAME = "hibernate.dialect.hana.treat_double_typed_fields_as_decimal";
private static final int MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE = 1024;
private static final Boolean USE_LEGACY_BOOLEAN_TYPE_DEFAULT_VALUE = Boolean.FALSE;
private static final Boolean TREAT_DOUBLE_TYPED_FIELDS_AS_DECIMAL_DEFAULT_VALUE = Boolean.FALSE;
private HANANClobJdbcType nClobTypeDescriptor = new HANANClobJdbcType( MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE );
private HANABlobType blobTypeDescriptor = new HANABlobType( MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE );
private HANAClobJdbcType clobTypeDescriptor;
private final int maxLobPrefetchSize;
private boolean defaultTableTypeColumn;
private boolean useLegacyBooleanType = USE_LEGACY_BOOLEAN_TYPE_DEFAULT_VALUE;
private boolean useUnicodeStringTypes;
private boolean treatDoubleTypedFieldsAsDecimal;
/*
* Tables named "TYPE" need to be quoted
@ -232,15 +235,57 @@ public abstract class AbstractHANADialect extends Dialect {
}
};
public AbstractHANADialect(DatabaseVersion version) {
super( version );
public AbstractHANADialect(DatabaseVersion version) {
this( new HANAServerConfiguration( version ), true );
}
public AbstractHANADialect(HANAServerConfiguration configuration, boolean defaultTableTypeColumn) {
super( configuration.getFullVersion() );
this.defaultTableTypeColumn = defaultTableTypeColumn;
this.maxLobPrefetchSize = configuration.getMaxLobPrefetchSize();
this.useUnicodeStringTypes = useUnicodeStringTypesDefault();
this.clobTypeDescriptor = new HANAClobJdbcType(
MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE,
}
@Override
public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
// This is the best hook for consuming dialect configuration that we have for now,
// since this method is called very early in the bootstrap process
final ConfigurationService configurationService = serviceRegistry.getService( ConfigurationService.class );
assert configurationService != null;
this.defaultTableTypeColumn = configurationService.getSetting(
USE_DEFAULT_TABLE_TYPE_COLUMN,
StandardConverters.BOOLEAN,
this.defaultTableTypeColumn
);
if ( supportsAsciiStringTypes() ) {
this.useUnicodeStringTypes = configurationService.getSetting(
USE_UNICODE_STRING_TYPES_PARAMETER_NAME,
StandardConverters.BOOLEAN,
useUnicodeStringTypesDefault()
);
}
this.useLegacyBooleanType = configurationService.getSetting(
USE_LEGACY_BOOLEAN_TYPE_PARAMETER_NAME,
StandardConverters.BOOLEAN,
USE_LEGACY_BOOLEAN_TYPE_DEFAULT_VALUE
);
this.treatDoubleTypedFieldsAsDecimal = configurationService.getSetting(
TREAT_DOUBLE_TYPED_FIELDS_AS_DECIMAL_PARAMETER_NAME,
StandardConverters.BOOLEAN,
TREAT_DOUBLE_TYPED_FIELDS_AS_DECIMAL_DEFAULT_VALUE
);
super.contribute( typeContributions, serviceRegistry );
}
protected boolean isDefaultTableTypeColumn() {
return defaultTableTypeColumn;
}
protected boolean isCloud() {
return getVersion().isSameOrAfter( 4 );
}
@Override
protected String columnType(int sqlTypeCode) {
@ -280,20 +325,6 @@ public abstract class AbstractHANADialect extends Dialect {
@Override
protected void registerColumnTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
final ConfigurationService configurationService = serviceRegistry.getService( ConfigurationService.class );
if ( supportsAsciiStringTypes() ) {
this.useUnicodeStringTypes = configurationService.getSetting(
USE_UNICODE_STRING_TYPES_PARAMETER_NAME,
StandardConverters.BOOLEAN,
useUnicodeStringTypesDefault()
);
}
this.useLegacyBooleanType = configurationService.getSetting(
USE_LEGACY_BOOLEAN_TYPE_PARAMETER_NAME,
StandardConverters.BOOLEAN,
USE_LEGACY_BOOLEAN_TYPE_DEFAULT_VALUE
);
super.registerColumnTypes( typeContributions, serviceRegistry );
final DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry();
@ -320,24 +351,12 @@ public abstract class AbstractHANADialect extends Dialect {
return false;
}
/**
* @deprecated Use {@link HANAServerConfiguration#fromDialectResolutionInfo(DialectResolutionInfo)} instead
*/
@Deprecated(forRemoval = true)
protected static DatabaseVersion createVersion(DialectResolutionInfo info) {
// Parse the version according to https://answers.sap.com/questions/9760991/hana-sps-version-check.html
final String versionString = info.getDatabaseVersion();
int majorVersion = 1;
int minorVersion = 0;
int patchLevel = 0;
final String[] components = versionString.split( "\\." );
if ( components.length >= 3 ) {
try {
majorVersion = Integer.parseInt( components[0] );
minorVersion = Integer.parseInt( components[1] );
patchLevel = Integer.parseInt( components[2] );
}
catch (NumberFormatException ex) {
// Ignore
}
}
return DatabaseVersion.make( majorVersion, minorVersion, patchLevel );
return HANAServerConfiguration.fromDialectResolutionInfo( info ).getFullVersion();
}
@Override
@ -434,6 +453,22 @@ public abstract class AbstractHANADialect extends Dialect {
functionContributions.getFunctionRegistry().register( "timestampadd",
new IntegralTimestampaddFunction( this, typeConfiguration ) );
// full-text search functions
functionContributions.getFunctionRegistry().registerNamed(
"score",
typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.DOUBLE )
);
functionContributions.getFunctionRegistry().registerNamed( "snippets" );
functionContributions.getFunctionRegistry().registerNamed( "highlighted" );
functionContributions.getFunctionRegistry().registerBinaryTernaryPattern(
"contains",
typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.BOOLEAN ),
"contains(?1,?2)",
"contains(?1,?2,?3)",
ANY, ANY, ANY,
typeConfiguration
);
}
@Override
@ -526,7 +561,15 @@ public abstract class AbstractHANADialect extends Dialect {
final String constraintName = getViolatedConstraintNameExtractor()
.extractConstraintName( sqlException );
return new ConstraintViolationException( message, sqlException, sql, constraintName );
return new ConstraintViolationException(
message,
sqlException,
sql,
errorCode == 301
? ConstraintViolationException.ConstraintKind.UNIQUE
: ConstraintViolationException.ConstraintKind.OTHER,
constraintName
);
}
return null;
@ -538,6 +581,11 @@ public abstract class AbstractHANADialect extends Dialect {
return RowLockStrategy.COLUMN;
}
@Override
public String getCreateTableString() {
return isDefaultTableTypeColumn() ? "create column table" : "create row table";
}
@Override
public String getAddColumnString() {
return "add (";
@ -621,6 +669,7 @@ public abstract class AbstractHANADialect extends Dialect {
@Override
protected void registerDefaultKeywords() {
super.registerDefaultKeywords();
// https://help.sap.com/docs/SAP_HANA_PLATFORM/4fe29514fd584807ac9f2a04f6754767/28bcd6af3eb6437892719f7c27a8a285.html?locale=en-US
registerKeyword( "all" );
registerKeyword( "alter" );
registerKeyword( "as" );
@ -668,6 +717,7 @@ public abstract class AbstractHANADialect extends Dialect {
registerKeyword( "into" );
registerKeyword( "is" );
registerKeyword( "join" );
registerKeyword( "lateral" );
registerKeyword( "leading" );
registerKeyword( "left" );
registerKeyword( "limit" );
@ -706,6 +756,29 @@ public abstract class AbstractHANADialect extends Dialect {
registerKeyword( "where" );
registerKeyword( "while" );
registerKeyword( "with" );
if ( isCloud() ) {
// https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/reserved-words
registerKeyword( "array" );
registerKeyword( "at" );
registerKeyword( "authorization" );
registerKeyword( "between" );
registerKeyword( "by" );
registerKeyword( "collate" );
registerKeyword( "empty" );
registerKeyword( "filter" );
registerKeyword( "grouping" );
registerKeyword( "no" );
registerKeyword( "not" );
registerKeyword( "of" );
registerKeyword( "over" );
registerKeyword( "recursive" );
registerKeyword( "row" );
registerKeyword( "table" );
registerKeyword( "to" );
registerKeyword( "unnest" );
registerKeyword( "window" );
registerKeyword( "within" );
}
}
@Override
@ -930,127 +1003,40 @@ public abstract class AbstractHANADialect extends Dialect {
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.contributeTypes( typeContributions, serviceRegistry );
final ConnectionProvider connectionProvider = serviceRegistry.getService( ConnectionProvider.class );
int maxLobPrefetchSizeDefault = MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE;
if ( connectionProvider != null ) {
Connection conn = null;
try {
conn = connectionProvider.getConnection();
try ( Statement statement = conn.createStatement() ) {
try ( ResultSet rs = statement.executeQuery(
"SELECT TOP 1 VALUE,MAP(LAYER_NAME,'DEFAULT',1,'SYSTEM',2,'DATABASE',3,4) AS LAYER FROM SYS.M_INIFILE_CONTENTS WHERE FILE_NAME='indexserver.ini' AND SECTION='session' AND KEY='max_lob_prefetch_size' ORDER BY LAYER DESC" ) ) {
// This only works if the current user has the privilege INIFILE ADMIN
if ( rs.next() ) {
maxLobPrefetchSizeDefault = rs.getInt( 1 );
}
}
}
}
catch (Exception e) {
LOG.debug(
"An error occurred while trying to determine the value of the HANA parameter indexserver.ini / session / max_lob_prefetch_size. Using the default value "
+ maxLobPrefetchSizeDefault,
e );
}
finally {
if ( conn != null ) {
try {
connectionProvider.closeConnection( conn );
}
catch (SQLException e) {
// ignore
}
}
}
}
final ConfigurationService configurationService = serviceRegistry.getService( ConfigurationService.class );
int maxLobPrefetchSize = configurationService.getSetting(
MAX_LOB_PREFETCH_SIZE_PARAMETER_NAME,
value -> Integer.valueOf( value.toString() ),
maxLobPrefetchSizeDefault
);
if ( this.nClobTypeDescriptor.getMaxLobPrefetchSize() != maxLobPrefetchSize ) {
this.nClobTypeDescriptor = new HANANClobJdbcType( maxLobPrefetchSize );
}
if ( this.blobTypeDescriptor.getMaxLobPrefetchSize() != maxLobPrefetchSize ) {
this.blobTypeDescriptor = new HANABlobType( maxLobPrefetchSize );
}
if ( supportsAsciiStringTypes() ) {
if ( this.clobTypeDescriptor.getMaxLobPrefetchSize() != maxLobPrefetchSize
|| this.clobTypeDescriptor.isUseUnicodeStringTypes() != this.useUnicodeStringTypes ) {
this.clobTypeDescriptor = new HANAClobJdbcType( maxLobPrefetchSize, this.useUnicodeStringTypes );
}
}
boolean treatDoubleTypedFieldsAsDecimal = configurationService.getSetting(TREAT_DOUBLE_TYPED_FIELDS_AS_DECIMAL_PARAMETER_NAME, StandardConverters.BOOLEAN,
TREAT_DOUBLE_TYPED_FIELDS_AS_DECIMAL_DEFAULT_VALUE);
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration()
.getJdbcTypeRegistry();
if (treatDoubleTypedFieldsAsDecimal) {
typeContributions.getTypeConfiguration().getBasicTypeRegistry()
final TypeConfiguration typeConfiguration = typeContributions.getTypeConfiguration();
final JdbcTypeRegistry jdbcTypeRegistry = typeConfiguration.getJdbcTypeRegistry();
if ( treatDoubleTypedFieldsAsDecimal ) {
typeConfiguration.getBasicTypeRegistry()
.register(
new BasicTypeImpl<>(
DoubleJavaType.INSTANCE,
NumericJdbcType.INSTANCE
),
new BasicTypeImpl<>( DoubleJavaType.INSTANCE, NumericJdbcType.INSTANCE ),
Double.class.getName()
);
typeContributions.getTypeConfiguration().getJdbcToHibernateTypeContributionMap()
.computeIfAbsent( Types.FLOAT, code -> new HashSet<>() )
.clear();
typeContributions.getTypeConfiguration().getJdbcToHibernateTypeContributionMap()
.computeIfAbsent( Types.REAL, code -> new HashSet<>() )
.clear();
typeContributions.getTypeConfiguration().getJdbcToHibernateTypeContributionMap()
.computeIfAbsent( Types.DOUBLE, code -> new HashSet<>() )
.clear();
typeContributions.getTypeConfiguration().getJdbcToHibernateTypeContributionMap()
.get( Types.FLOAT )
.add( StandardBasicTypes.BIG_DECIMAL.getName() );
typeContributions.getTypeConfiguration().getJdbcToHibernateTypeContributionMap()
.get( Types.REAL )
.add( StandardBasicTypes.BIG_DECIMAL.getName() );
typeContributions.getTypeConfiguration().getJdbcToHibernateTypeContributionMap()
.get( Types.DOUBLE )
.add( StandardBasicTypes.BIG_DECIMAL.getName() );
jdbcTypeRegistry.addDescriptor(
Types.FLOAT,
NumericJdbcType.INSTANCE
);
jdbcTypeRegistry.addDescriptor(
Types.REAL,
NumericJdbcType.INSTANCE
);
jdbcTypeRegistry.addDescriptor(
Types.DOUBLE,
NumericJdbcType.INSTANCE
);
final Map<Integer, Set<String>> jdbcToHibernateTypeContributionMap = typeConfiguration.getJdbcToHibernateTypeContributionMap();
jdbcToHibernateTypeContributionMap.computeIfAbsent( Types.FLOAT, code -> new HashSet<>() ).clear();
jdbcToHibernateTypeContributionMap.computeIfAbsent( Types.REAL, code -> new HashSet<>() ).clear();
jdbcToHibernateTypeContributionMap.computeIfAbsent( Types.DOUBLE, code -> new HashSet<>() ).clear();
jdbcToHibernateTypeContributionMap.get( Types.FLOAT ).add( StandardBasicTypes.BIG_DECIMAL.getName() );
jdbcToHibernateTypeContributionMap.get( Types.REAL ).add( StandardBasicTypes.BIG_DECIMAL.getName() );
jdbcToHibernateTypeContributionMap.get( Types.DOUBLE ).add( StandardBasicTypes.BIG_DECIMAL.getName() );
jdbcTypeRegistry.addDescriptor( Types.FLOAT, NumericJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( Types.REAL, NumericJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( Types.DOUBLE, NumericJdbcType.INSTANCE );
}
jdbcTypeRegistry.addDescriptor( Types.CLOB, this.clobTypeDescriptor );
jdbcTypeRegistry.addDescriptor( Types.NCLOB, this.nClobTypeDescriptor );
jdbcTypeRegistry.addDescriptor( Types.BLOB, this.blobTypeDescriptor );
jdbcTypeRegistry.addDescriptor( Types.CLOB, new HANAClobJdbcType( maxLobPrefetchSize, useUnicodeStringTypes ) );
jdbcTypeRegistry.addDescriptor( Types.NCLOB, new HANANClobJdbcType( maxLobPrefetchSize ) );
jdbcTypeRegistry.addDescriptor( Types.BLOB, new HANABlobType( maxLobPrefetchSize ) );
// tinyint is unsigned on HANA
jdbcTypeRegistry.addDescriptor( Types.TINYINT, TinyIntAsSmallIntJdbcType.INSTANCE );
if ( isUseUnicodeStringTypes() ) {
jdbcTypeRegistry.addDescriptor( Types.VARCHAR, NVarcharJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( Types.CHAR, NCharJdbcType.INSTANCE );
}
if (treatDoubleTypedFieldsAsDecimal) {
if ( treatDoubleTypedFieldsAsDecimal ) {
jdbcTypeRegistry.addDescriptor( Types.DOUBLE, DecimalJdbcType.INSTANCE );
}
}
public JdbcType getBlobTypeDescriptor() {
return this.blobTypeDescriptor;
}
@Override
public void appendBooleanValueString(SqlAppender appender, boolean bool) {
if ( this.useLegacyBooleanType ) {
@ -1266,12 +1252,16 @@ public abstract class AbstractHANADialect extends Dialect {
}
public boolean isUseUnicodeStringTypes() {
return this.useUnicodeStringTypes;
return this.useUnicodeStringTypes || isDefaultTableTypeColumn() && isCloud();
}
protected abstract boolean supportsAsciiStringTypes();
protected boolean supportsAsciiStringTypes() {
return !isDefaultTableTypeColumn() || !isCloud();
}
protected abstract Boolean useUnicodeStringTypesDefault();
protected Boolean useUnicodeStringTypesDefault() {
return isDefaultTableTypeColumn() ? isCloud() : Boolean.FALSE;
}
private static class CloseSuppressingReader extends FilterReader {
@ -1756,6 +1746,7 @@ public abstract class AbstractHANADialect extends Dialect {
public static class HANABlobType implements JdbcType {
private static final long serialVersionUID = 5874441715643764323L;
public static final JdbcType INSTANCE = new HANABlobType( HANAServerConfiguration.MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE );
final int maxLobPrefetchSize;
@ -1843,4 +1834,59 @@ public abstract class AbstractHANADialect extends Dialect {
return this.maxLobPrefetchSize;
}
}
@Override
public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy(
EntityMappingType entityDescriptor,
RuntimeModelCreationContext runtimeModelCreationContext) {
return new GlobalTemporaryTableMutationStrategy(
TemporaryTable.createIdTable(
entityDescriptor,
basename -> TemporaryTable.ID_TABLE_PREFIX + basename,
this,
runtimeModelCreationContext
),
runtimeModelCreationContext.getSessionFactory()
);
}
@Override
public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy(
EntityMappingType entityDescriptor,
RuntimeModelCreationContext runtimeModelCreationContext) {
return new GlobalTemporaryTableInsertStrategy(
TemporaryTable.createEntityTable(
entityDescriptor,
name -> TemporaryTable.ENTITY_TABLE_PREFIX + name,
this,
runtimeModelCreationContext
),
runtimeModelCreationContext.getSessionFactory()
);
}
@Override
public TemporaryTableKind getSupportedTemporaryTableKind() {
return TemporaryTableKind.GLOBAL;
}
@Override
public String getTemporaryTableCreateOptions() {
return "on commit delete rows";
}
@Override
public String getTemporaryTableCreateCommand() {
return "create global temporary row table";
}
@Override
public String getTemporaryTableTruncateCommand() {
return "truncate table";
}
@Override
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return DmlTargetColumnQualifierSupport.TABLE_ALIAS;
}
}

View File

@ -572,6 +572,11 @@ public class CockroachDialect extends Dialect {
return true;
}
@Override
public boolean supportsConflictClauseForInsertCTE() {
return true;
}
@Override
public String getNoColumnsInsertString() {
return "default values";
@ -1160,4 +1165,14 @@ public class CockroachDialect extends Dialect {
// RuntimeModelCreationContext runtimeModelCreationContext) {
// return new CteInsertStrategy( rootEntityDescriptor, runtimeModelCreationContext );
// }
@Override
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return DmlTargetColumnQualifierSupport.TABLE_ALIAS;
}
@Override
public boolean supportsFromClauseInUpdate() {
return true;
}
}

View File

@ -7,20 +7,27 @@
package org.hibernate.dialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.cte.CteMaterialization;
import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.InArrayPredicate;
import org.hibernate.sql.ast.tree.predicate.LikePredicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.internal.JdbcOperationQueryInsertImpl;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert;
/**
* A SQL AST translator for Cockroach.
@ -33,6 +40,56 @@ public class CockroachSqlAstTranslator<T extends JdbcOperation> extends Abstract
super( sessionFactory, statement );
}
@Override
protected JdbcOperationQueryInsert translateInsert(InsertSelectStatement sqlAst) {
visitInsertStatement( sqlAst );
return new JdbcOperationQueryInsertImpl(
getSql(),
getParameterBinders(),
getAffectedTableNames(),
null
);
}
@Override
protected void renderTableReferenceIdentificationVariable(TableReference tableReference) {
final String identificationVariable = tableReference.getIdentificationVariable();
if ( identificationVariable != null ) {
final Clause currentClause = getClauseStack().getCurrent();
if ( currentClause == Clause.INSERT ) {
// PostgreSQL requires the "as" keyword for inserts
appendSql( " as " );
}
else {
append( WHITESPACE );
}
append( tableReference.getIdentificationVariable() );
}
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
final Statement currentStatement = getStatementStack().getCurrent();
if ( !( currentStatement instanceof UpdateStatement )
|| !hasNonTrivialFromClause( ( (UpdateStatement) currentStatement ).getFromClause() ) ) {
// For UPDATE statements we render a full FROM clause and a join condition to match target table rows,
// but for that to work, we have to omit the alias for the target table reference here
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected void renderFromClauseAfterUpdateSet(UpdateStatement statement) {
renderFromClauseJoiningDmlTargetReference( statement );
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
visitStandardConflictClause( conflictClause );
}
@Override
protected void renderExpressionAsClauseItem(Expression expression) {
expression.accept( this );

View File

@ -42,8 +42,11 @@ import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.LockTimeoutException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor;
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.mapping.Column;
import org.hibernate.metamodel.mapping.EntityMappingType;
@ -87,6 +90,7 @@ import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.TemporalType;
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
import static org.hibernate.type.SqlTypes.BINARY;
import static org.hibernate.type.SqlTypes.BLOB;
import static org.hibernate.type.SqlTypes.BOOLEAN;
@ -973,6 +977,20 @@ public class DB2Dialect extends Dialect {
appender.appendSql( '\'' );
}
@Override
public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
return new TemplatedViolatedConstraintNameExtractor(
sqle -> {
switch ( JdbcExceptionHelper.extractErrorCode( sqle ) ) {
case -803:
return extractUsingTemplate( "SQLERRMC=1;", ",", sqle.getMessage() );
default:
return null;
}
}
);
}
@Override
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
return (sqlException, message, sql) -> {
@ -980,6 +998,14 @@ public class DB2Dialect extends Dialect {
switch ( errorCode ) {
case -952:
return new LockTimeoutException( message, sqlException, sql );
case -803:
return new ConstraintViolationException(
message,
sqlException,
sql,
ConstraintViolationException.ConstraintKind.UNIQUE,
getViolatedConstraintNameExtractor().extractConstraintName( sqlException )
);
}
return null;
};
@ -1171,4 +1197,14 @@ public class DB2Dialect extends Dialect {
public int rowIdSqlType() {
return VARBINARY;
}
@Override
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return DmlTargetColumnQualifierSupport.TABLE_ALIAS;
}
@Override
public boolean supportsFromClauseInUpdate() {
return getDB2Version().isSameOrAfter( 11 );
}
}

View File

@ -12,8 +12,10 @@ import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.query.sqm.FetchClauseType;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlSelection;
@ -27,10 +29,12 @@ import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableReferenceJoin;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
@ -347,12 +351,40 @@ public class DB2SqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAst
@Override
protected void visitInsertStatementOnly(InsertSelectStatement statement) {
final boolean closeWrapper = renderReturningClause( statement );
if ( statement.getConflictClause() == null || statement.getConflictClause().isDoNothing() ) {
// Render plain insert statement and possibly run into unique constraint violation
super.visitInsertStatementOnly( statement );
}
else {
visitInsertStatementEmulateMerge( statement );
}
if ( closeWrapper ) {
appendSql( ')' );
}
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
if ( conflictClause != null ) {
if ( conflictClause.isDoUpdate() && conflictClause.getConstraintName() != null ) {
throw new IllegalQueryOperationException( "Insert conflict do update clause with constraint name is not supported" );
}
}
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
if ( getClauseStack().getCurrent() != Clause.INSERT ) {
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected void renderFromClauseAfterUpdateSet(UpdateStatement statement) {
renderFromClauseExcludingDmlTargetReference( statement );
}
protected boolean renderReturningClause(MutationStatement statement) {
final List<ColumnReference> returningColumns = statement.getReturningColumns();
if ( isEmpty( returningColumns ) ) {
@ -521,13 +553,13 @@ public class DB2SqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAst
}
@Override
protected String getFromDual() {
return " from sysibm.dual";
protected String getDual() {
return "sysibm.dual";
}
@Override
protected String getFromDualForSelectOnly() {
return getFromDual();
return " from " + getDual();
}
@Override

View File

@ -135,7 +135,7 @@ public enum Database {
HANA {
@Override
public Dialect createDialect(DialectResolutionInfo info) {
return new HANAColumnStoreDialect( info );
return new HANADialect( info );
}
@Override
public boolean productNameMatches(String databaseName) {

View File

@ -35,8 +35,11 @@ import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.LockTimeoutException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor;
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
@ -690,14 +693,42 @@ public class DerbyDialect extends Dialect {
return 512;
}
@Override
public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
return new TemplatedViolatedConstraintNameExtractor( sqle -> {
final String sqlState = JdbcExceptionHelper.extractSqlState( sqle );
if ( sqlState != null ) {
switch ( sqlState ) {
case "23505":
return TemplatedViolatedConstraintNameExtractor.extractUsingTemplate(
"'", "'",
sqle.getMessage()
);
}
}
return null;
} );
}
@Override
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
return (sqlException, message, sql) -> {
final String sqlState = JdbcExceptionHelper.extractSqlState( sqlException );
// final int errorCode = JdbcExceptionHelper.extractErrorCode( sqlException );
final String constraintName;
if ( sqlState != null ) {
switch ( sqlState ) {
case "23505":
// Unique constraint violation
constraintName = getViolatedConstraintNameExtractor().extractConstraintName(sqlException);
return new ConstraintViolationException(
message,
sqlException,
sql,
ConstraintViolationException.ConstraintKind.UNIQUE,
constraintName
);
case "40XL1":
case "40XL2":
return new LockTimeoutException( message, sqlException, sql );
@ -1013,4 +1044,5 @@ public class DerbyDialect extends Dialect {
public UniqueDelegate getUniqueDelegate() {
return uniqueDelegate;
}
}

View File

@ -233,13 +233,13 @@ public class DerbySqlAstTranslator<T extends JdbcOperation> extends AbstractSqlA
}
@Override
protected String getFromDual() {
return " from (values 0) dual";
protected String getDual() {
return "(values 0)";
}
@Override
protected String getFromDualForSelectOnly() {
return getFromDual();
return " from " + getDual() + " dual";
}
@Override

View File

@ -4359,6 +4359,17 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
return false;
}
/**
* Does this dialect support the {@code conflict} clause for insert statements
* that appear in a CTE?
*
* @return {@code true} if {@code conflict} clause is supported
* @since 6.5
*/
public boolean supportsConflictClauseForInsertCTE() {
return false;
}
/**
* Does this dialect support {@code values} lists of form
* {@code VALUES (1), (2), (3)}?
@ -4380,6 +4391,16 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
return true;
}
/**
* Does this dialect support the {@code from} clause for update statements?
*
* @return {@code true} if {@code from} clause is supported
* @since 6.5
*/
public boolean supportsFromClauseInUpdate() {
return false;
}
/**
* Does this dialect support {@code SKIP_LOCKED} timeout.
*

View File

@ -721,8 +721,19 @@ public class H2Dialect extends Dialect {
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
return (sqlException, message, sql) -> {
final int errorCode = JdbcExceptionHelper.extractErrorCode( sqlException );
final String constraintName;
switch (errorCode) {
case 23505:
// Unique constraint violation
constraintName = getViolatedConstraintNameExtractor().extractConstraintName(sqlException);
return new ConstraintViolationException(
message,
sqlException,
sql,
ConstraintViolationException.ConstraintKind.UNIQUE,
constraintName
);
case 40001:
// DEADLOCK DETECTED
return new LockAcquisitionException(message, sqlException, sql);
@ -731,7 +742,7 @@ public class H2Dialect extends Dialect {
return new PessimisticLockException(message, sqlException, sql);
case 90006:
// NULL not allowed for column [90006-145]
final String constraintName = getViolatedConstraintNameExtractor().extractConstraintName(sqlException);
constraintName = getViolatedConstraintNameExtractor().extractConstraintName(sqlException);
return new ConstraintViolationException(message, sqlException, sql, constraintName);
case 57014:
return new QueryTimeoutException( message, sqlException, sql );
@ -935,4 +946,9 @@ public class H2Dialect extends Dialect {
return "?" + position;
}
}
@Override
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return DmlTargetColumnQualifierSupport.TABLE_ALIAS;
}
}

View File

@ -11,7 +11,9 @@ import java.util.List;
import org.hibernate.LockMode;
import org.hibernate.dialect.identity.H2IdentityColumnSupport;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.Statement;
@ -23,13 +25,17 @@ import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.LikePredicate;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.SelectClause;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.model.internal.TableInsertStandard;
@ -80,6 +86,44 @@ public class H2SqlAstTranslator<T extends JdbcOperation> extends SqlAstTranslato
);
}
@Override
protected void visitInsertStatementOnly(InsertSelectStatement statement) {
if ( statement.getConflictClause() == null || statement.getConflictClause().isDoNothing() ) {
// Render plain insert statement and possibly run into unique constraint violation
super.visitInsertStatementOnly( statement );
}
else {
visitInsertStatementEmulateMerge( statement );
}
}
@Override
protected void visitUpdateStatementOnly(UpdateStatement statement) {
if ( hasNonTrivialFromClause( statement.getFromClause() ) ) {
visitUpdateStatementEmulateMerge( statement );
}
else {
super.visitUpdateStatementOnly( statement );
}
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
if ( getClauseStack().getCurrent() != Clause.INSERT ) {
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
if ( conflictClause != null ) {
if ( conflictClause.isDoUpdate() && conflictClause.getConstraintName() != null ) {
throw new IllegalQueryOperationException( "Insert conflict do update clause with constraint name is not supported" );
}
}
}
@Override
protected void visitReturningColumns(List<ColumnReference> returningColumns) {
// do nothing - this is handled via `#visitReturningInsertStatement`
@ -267,11 +311,10 @@ public class H2SqlAstTranslator<T extends JdbcOperation> extends SqlAstTranslato
}
@Override
protected String getFromDual() {
return " from dual";
protected String getDual() {
return "dual";
}
private boolean supportsOffsetFetchClause() {
return true;
}

View File

@ -19,35 +19,14 @@ package org.hibernate.dialect;
*
* @author Jonathan Bregler
*
* @deprecated use HANAColumnStoreDialect(400)
* @deprecated use {@link HANADialect} with {@code DatabaseVersion.make( 4 )} instead
*/
@Deprecated
@Deprecated(forRemoval = true)
public class HANACloudColumnStoreDialect extends HANAColumnStoreDialect {
public HANACloudColumnStoreDialect() {
// No idea how the versioning scheme is here, but since this is deprecated anyway, keep it as is
super( DatabaseVersion.make( 4 ) );
}
@Override
public boolean supportsLateral() {
// Couldn't find a reference since when this is supported
return true;
}
@Override
protected boolean supportsAsciiStringTypes() {
return getVersion().isBefore( 4 );
}
@Override
protected Boolean useUnicodeStringTypesDefault() {
return getVersion().isSameOrAfter( 4 );
}
@Override
public boolean isUseUnicodeStringTypes() {
return getVersion().isSameOrAfter( 4 ) || super.isUseUnicodeStringTypes();
super( new HANAServerConfiguration( DatabaseVersion.make( 4 ) ) );
}
}

View File

@ -34,11 +34,14 @@ import static org.hibernate.query.sqm.produce.function.FunctionParameterType.ANY
*
* @author Andrew Clemons
* @author Jonathan Bregler
*
* @deprecated use {@link HANADialect} instead
*/
@Deprecated(forRemoval = true)
public class HANAColumnStoreDialect extends AbstractHANADialect {
public HANAColumnStoreDialect(DialectResolutionInfo info) {
this( AbstractHANADialect.createVersion( info ) );
this( HANAServerConfiguration.fromDialectResolutionInfo( info ) );
registerKeywords( info );
}
@ -48,129 +51,10 @@ public class HANAColumnStoreDialect extends AbstractHANADialect {
}
public HANAColumnStoreDialect(DatabaseVersion version) {
super( version );
this( new HANAServerConfiguration( version ) );
}
@Override
public boolean isUseUnicodeStringTypes() {
return getVersion().isSameOrAfter( 4 ) || super.isUseUnicodeStringTypes();
}
@Override
public int getMaxVarcharLength() {
return 5000;
}
@Override
protected void registerDefaultKeywords() {
super.registerDefaultKeywords();
registerKeyword( "array" );
registerKeyword( "at" );
registerKeyword( "authorization" );
registerKeyword( "between" );
registerKeyword( "by" );
registerKeyword( "collate" );
registerKeyword( "empty" );
registerKeyword( "filter" );
registerKeyword( "grouping" );
registerKeyword( "no" );
registerKeyword( "not" );
registerKeyword( "of" );
registerKeyword( "over" );
registerKeyword( "recursive" );
registerKeyword( "row" );
registerKeyword( "table" );
registerKeyword( "to" );
registerKeyword( "window" );
registerKeyword( "within" );
}
@Override
public void initializeFunctionRegistry(FunctionContributions functionContributions) {
super.initializeFunctionRegistry(functionContributions);
final TypeConfiguration typeConfiguration = functionContributions.getTypeConfiguration();
// full-text search functions
functionContributions.getFunctionRegistry().registerNamed(
"score",
typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.DOUBLE )
);
functionContributions.getFunctionRegistry().registerNamed( "snippets" );
functionContributions.getFunctionRegistry().registerNamed( "highlighted" );
functionContributions.getFunctionRegistry().registerBinaryTernaryPattern(
"contains",
typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.BOOLEAN ),
"contains(?1,?2)",
"contains(?1,?2,?3)",
ANY, ANY, ANY,
typeConfiguration
);
}
@Override
public String getCreateTableString() {
return "create column table";
}
@Override
public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy(
EntityMappingType entityDescriptor,
RuntimeModelCreationContext runtimeModelCreationContext) {
return new GlobalTemporaryTableMutationStrategy(
TemporaryTable.createIdTable(
entityDescriptor,
basename -> TemporaryTable.ID_TABLE_PREFIX + basename,
this,
runtimeModelCreationContext
),
runtimeModelCreationContext.getSessionFactory()
);
}
@Override
public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy(
EntityMappingType entityDescriptor,
RuntimeModelCreationContext runtimeModelCreationContext) {
return new GlobalTemporaryTableInsertStrategy(
TemporaryTable.createEntityTable(
entityDescriptor,
name -> TemporaryTable.ENTITY_TABLE_PREFIX + name,
this,
runtimeModelCreationContext
),
runtimeModelCreationContext.getSessionFactory()
);
}
@Override
public TemporaryTableKind getSupportedTemporaryTableKind() {
return TemporaryTableKind.GLOBAL;
}
@Override
public String getTemporaryTableCreateOptions() {
return "on commit delete rows";
}
@Override
public String getTemporaryTableCreateCommand() {
// We use a row table for temporary tables here because HANA doesn't support UPDATE on temporary column tables
return "create global temporary row table";
}
@Override
public String getTemporaryTableTruncateCommand() {
return "truncate table";
}
@Override
protected boolean supportsAsciiStringTypes() {
return true;
}
@Override
protected Boolean useUnicodeStringTypesDefault() {
return true;
public HANAColumnStoreDialect(HANAServerConfiguration configuration) {
super( configuration, true );
}
}

View File

@ -0,0 +1,55 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.dialect;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
/**
* An SQL dialect for the SAP HANA Platform and Cloud.
* <p>
* For more information on SAP HANA Cloud, refer to the
* <a href="https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/sap-hana-cloud-sap-hana-database-sql-reference-guide">SAP HANA Cloud SQL Reference Guide</a>.
* For more information on SAP HANA Platform, refer to the
* <a href="https://help.sap.com/docs/SAP_HANA_PLATFORM/4fe29514fd584807ac9f2a04f6754767/b4b0eec1968f41a099c828a4a6c8ca0f.html?locale=en-US">SAP HANA Platform SQL Reference Guide</a>.
* <p>
* Column tables are created by this dialect by default when using the auto-ddl feature.
*
* @author Andrew Clemons
* @author Jonathan Bregler
*/
@SuppressWarnings("removal")
public class HANADialect extends AbstractHANADialect {
private static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 1, 0, 120 );
public HANADialect(DialectResolutionInfo info) {
this( HANAServerConfiguration.fromDialectResolutionInfo( info ), true );
registerKeywords( info );
}
public HANADialect() {
// SAP HANA 1.0 SPS12 R0 is the default
this( MINIMUM_VERSION );
}
public HANADialect(DatabaseVersion version) {
this( new HANAServerConfiguration( version ), true );
}
public HANADialect(DatabaseVersion version, boolean defaultTableTypeColumn) {
this( new HANAServerConfiguration( version ), defaultTableTypeColumn );
}
public HANADialect(HANAServerConfiguration configuration, boolean defaultTableTypeColumn) {
super( configuration, defaultTableTypeColumn );
}
@Override
protected DatabaseVersion getMinimumSupportedVersion() {
return MINIMUM_VERSION;
}
}

View File

@ -29,11 +29,14 @@ import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
*
* @author Andrew Clemons
* @author Jonathan Bregler
*
* @deprecated use {@link HANADialect} instead
*/
@Deprecated(forRemoval = true)
public class HANARowStoreDialect extends AbstractHANADialect {
public HANARowStoreDialect(DialectResolutionInfo info) {
this( AbstractHANADialect.createVersion( info ) );
this( HANAServerConfiguration.fromDialectResolutionInfo( info ) );
registerKeywords( info );
}
@ -43,71 +46,10 @@ public class HANARowStoreDialect extends AbstractHANADialect {
}
public HANARowStoreDialect(DatabaseVersion version) {
super( version );
this( new HANAServerConfiguration( version ) );
}
@Override
public String getCreateTableString() {
return "create row table";
}
@Override
public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy(
EntityMappingType entityDescriptor,
RuntimeModelCreationContext runtimeModelCreationContext) {
return new GlobalTemporaryTableMutationStrategy(
TemporaryTable.createIdTable(
entityDescriptor,
basename -> TemporaryTable.ID_TABLE_PREFIX + basename,
this,
runtimeModelCreationContext
),
runtimeModelCreationContext.getSessionFactory()
);
}
@Override
public SqmMultiTableInsertStrategy getFallbackSqmInsertStrategy(
EntityMappingType entityDescriptor,
RuntimeModelCreationContext runtimeModelCreationContext) {
return new GlobalTemporaryTableInsertStrategy(
TemporaryTable.createEntityTable(
entityDescriptor,
name -> TemporaryTable.ENTITY_TABLE_PREFIX + name,
this,
runtimeModelCreationContext
),
runtimeModelCreationContext.getSessionFactory()
);
}
@Override
public TemporaryTableKind getSupportedTemporaryTableKind() {
return TemporaryTableKind.GLOBAL;
}
@Override
public String getTemporaryTableCreateOptions() {
return "on commit delete rows";
}
@Override
public String getTemporaryTableCreateCommand() {
return "create global temporary row table";
}
@Override
public String getTemporaryTableTruncateCommand() {
return "truncate table";
}
@Override
protected boolean supportsAsciiStringTypes() {
return true;
}
@Override
protected Boolean useUnicodeStringTypesDefault() {
return Boolean.FALSE;
public HANARowStoreDialect(HANAServerConfiguration configuration) {
super( configuration, false );
}
}

View File

@ -0,0 +1,99 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.dialect;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.config.ConfigurationHelper;
import static org.hibernate.cfg.DialectSpecificSettings.HANA_MAX_LOB_PREFETCH_SIZE;
/**
* Utility class that extract some initial configuration from the database for {@link HANADialect}.
*/
public class HANAServerConfiguration {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( HANAServerConfiguration.class );
public static final int MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE = 1024;
private final DatabaseVersion fullVersion;
private final int maxLobPrefetchSize;
public HANAServerConfiguration(DatabaseVersion fullVersion) {
this( fullVersion, MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE );
}
public HANAServerConfiguration(DatabaseVersion fullVersion, int maxLobPrefetchSize) {
this.fullVersion = fullVersion;
this.maxLobPrefetchSize = maxLobPrefetchSize;
}
public DatabaseVersion getFullVersion() {
return fullVersion;
}
public int getMaxLobPrefetchSize() {
return maxLobPrefetchSize;
}
public static HANAServerConfiguration fromDialectResolutionInfo(DialectResolutionInfo info) {
Integer maxLobPrefetchSize = null;
final DatabaseMetaData databaseMetaData = info.getDatabaseMetadata();
if ( databaseMetaData != null ) {
try (final Statement statement = databaseMetaData.getConnection().createStatement()) {
try ( ResultSet rs = statement.executeQuery(
"SELECT TOP 1 VALUE,MAP(LAYER_NAME,'DEFAULT',1,'SYSTEM',2,'DATABASE',3,4) AS LAYER FROM SYS.M_INIFILE_CONTENTS WHERE FILE_NAME='indexserver.ini' AND SECTION='session' AND KEY='max_lob_prefetch_size' ORDER BY LAYER DESC" ) ) {
// This only works if the current user has the privilege INIFILE ADMIN
if ( rs.next() ) {
maxLobPrefetchSize = rs.getInt( 1 );
}
}
}
catch (SQLException e) {
// Ignore
LOG.debug(
"An error occurred while trying to determine the value of the HANA parameter indexserver.ini / session / max_lob_prefetch_size.",
e );
}
}
// default to the dialect-specific configuration settings
if ( maxLobPrefetchSize == null ) {
maxLobPrefetchSize = ConfigurationHelper.getInt(
HANA_MAX_LOB_PREFETCH_SIZE,
info.getConfigurationValues(),
MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE
);
}
return new HANAServerConfiguration( createVersion( info ), maxLobPrefetchSize );
}
private static DatabaseVersion createVersion(DialectResolutionInfo info) {
// Parse the version according to https://answers.sap.com/questions/9760991/hana-sps-version-check.html
final String versionString = info.getDatabaseVersion();
int majorVersion = 1;
int minorVersion = 0;
int patchLevel = 0;
final String[] components = versionString.split( "\\." );
if ( components.length >= 3 ) {
try {
majorVersion = Integer.parseInt( components[0] );
minorVersion = Integer.parseInt( components[1] );
patchLevel = Integer.parseInt( components[2] );
}
catch (NumberFormatException ex) {
// Ignore
}
}
return DatabaseVersion.make( majorVersion, minorVersion, patchLevel );
}
}

View File

@ -8,24 +8,34 @@ package org.hibernate.dialect;
import java.util.List;
import org.hibernate.LockMode;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.FunctionTableReference;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.insert.Values;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.model.internal.TableInsertStandard;
@ -42,6 +52,83 @@ public class HANASqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
super( sessionFactory, statement );
}
@SuppressWarnings("removal")
private boolean isHanaCloud() {
return ( (AbstractHANADialect) getDialect() ).isCloud();
}
@Override
protected void visitInsertStatementOnly(InsertSelectStatement statement) {
if ( statement.getConflictClause() == null || statement.getConflictClause().isDoNothing() ) {
// Render plain insert statement and possibly run into unique constraint violation
super.visitInsertStatementOnly( statement );
}
else {
visitInsertStatementEmulateMerge( statement );
}
}
@Override
protected void visitUpdateStatementOnly(UpdateStatement statement) {
// HANA Cloud does not support the FROM clause in UPDATE statements
if ( isHanaCloud() && hasNonTrivialFromClause( statement.getFromClause() ) ) {
visitUpdateStatementEmulateMerge( statement );
}
else {
super.visitUpdateStatementOnly( statement );
}
}
@Override
protected void renderUpdateClause(UpdateStatement updateStatement) {
// HANA Cloud does not support the FROM clause in UPDATE statements
if ( isHanaCloud() ) {
super.renderUpdateClause( updateStatement );
}
else {
appendSql( "update" );
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.UPDATE );
renderTableReferenceIdentificationVariable( updateStatement.getTargetTable() );
}
finally {
clauseStack.pop();
}
}
}
@Override
protected void renderFromClauseAfterUpdateSet(UpdateStatement statement) {
// HANA Cloud does not support the FROM clause in UPDATE statements
if ( !isHanaCloud() ) {
if ( statement.getFromClause().getRoots().isEmpty() ) {
appendSql( " from " );
renderDmlTargetTableExpression( statement.getTargetTable() );
}
else {
visitFromClause( statement.getFromClause() );
}
}
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
if ( getClauseStack().getCurrent() != Clause.INSERT ) {
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
if ( conflictClause != null ) {
if ( conflictClause.isDoUpdate() && conflictClause.getConstraintName() != null ) {
throw new IllegalQueryOperationException( "Insert conflict do update clause with constraint name is not supported" );
}
}
}
protected boolean shouldEmulateFetchClause(QueryPart queryPart) {
// HANA only supports the LIMIT + OFFSET syntax but also window functions
// Check if current query part is already row numbering to avoid infinite recursion
@ -141,13 +228,13 @@ public class HANASqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
}
@Override
protected String getFromDual() {
return " from sys.dummy";
protected String getDual() {
return "sys.dummy";
}
@Override
protected String getFromDualForSelectOnly() {
return getFromDual();
return " from " + getDual();
}
@Override
@ -165,4 +252,9 @@ public class HANASqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
protected void visitValuesList(List<Values> valuesList) {
visitValuesListEmulateSelectUnion( valuesList );
}
@Override
public void visitValuesTableReference(ValuesTableReference tableReference) {
emulateValuesTableReferenceColumnAliasing( tableReference );
}
}

View File

@ -27,6 +27,8 @@ import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor;
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.util.JdbcExceptionHelper;
@ -423,6 +425,29 @@ public class HSQLDialect extends Dialect {
return null;
} );
@Override
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
return (sqlException, message, sql) -> {
final int errorCode = JdbcExceptionHelper.extractErrorCode( sqlException );
final String constraintName;
switch ( errorCode ) {
case -104:
// Unique constraint violation
constraintName = getViolatedConstraintNameExtractor().extractConstraintName(sqlException);
return new ConstraintViolationException(
message,
sqlException,
sql,
ConstraintViolationException.ConstraintKind.UNIQUE,
constraintName
);
}
return null;
};
}
@Override
public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfiguration) {
switch ( sqlType ) {
@ -683,4 +708,9 @@ public class HSQLDialect extends Dialect {
public String quoteCollation(String collation) {
return '\"' + collation + '\"';
}
@Override
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return DmlTargetColumnQualifierSupport.TABLE_ALIAS;
}
}

View File

@ -11,22 +11,30 @@ import java.util.function.Consumer;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.MutationStatement;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.InArrayPredicate;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
@ -41,6 +49,44 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
super( sessionFactory, statement );
}
@Override
protected void visitInsertStatementOnly(InsertSelectStatement statement) {
if ( statement.getConflictClause() == null || statement.getConflictClause().isDoNothing() ) {
// Render plain insert statement and possibly run into unique constraint violation
super.visitInsertStatementOnly( statement );
}
else {
visitInsertStatementEmulateMerge( statement );
}
}
@Override
protected void visitUpdateStatementOnly(UpdateStatement statement) {
if ( hasNonTrivialFromClause( statement.getFromClause() ) ) {
visitUpdateStatementEmulateMerge( statement );
}
else {
super.visitUpdateStatementOnly( statement );
}
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
if ( getClauseStack().getCurrent() != Clause.INSERT ) {
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
if ( conflictClause != null ) {
if ( conflictClause.isDoUpdate() && conflictClause.getConstraintName() != null ) {
throw new IllegalQueryOperationException( "Insert conflict do update clause with constraint name is not supported" );
}
}
}
@Override
protected void renderExpressionAsClauseItem(Expression expression) {
expression.accept( this );
@ -283,14 +329,9 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
return false;
}
@Override
protected String getFromDual() {
return " from (values(0))";
}
@Override
protected String getFromDualForSelectOnly() {
return getFromDual();
return " from " + getDual();
}
private boolean supportsOffsetFetchClause() {

View File

@ -6,21 +6,36 @@
*/
package org.hibernate.dialect;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.tree.MutationStatement;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.LikePredicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.internal.JdbcOperationQueryInsertImpl;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert;
/**
* A SQL AST translator for MariaDB.
@ -36,6 +51,135 @@ public class MariaDBSqlAstTranslator<T extends JdbcOperation> extends AbstractSq
this.dialect = (MariaDBDialect) DialectDelegateWrapper.extractRealDialect( super.getDialect() );
}
@Override
protected void visitInsertSource(InsertSelectStatement statement) {
if ( statement.getSourceSelectStatement() != null ) {
if ( statement.getConflictClause() != null ) {
final List<ColumnReference> targetColumnReferences = statement.getTargetColumns();
final List<String> columnNames = new ArrayList<>( targetColumnReferences.size() );
for ( ColumnReference targetColumnReference : targetColumnReferences ) {
columnNames.add( targetColumnReference.getColumnExpression() );
}
appendSql( "select * from " );
emulateQueryPartTableReferenceColumnAliasing(
new QueryPartTableReference(
new SelectStatement( statement.getSourceSelectStatement() ),
"excluded",
columnNames,
false,
getSessionFactory()
)
);
}
else {
statement.getSourceSelectStatement().accept( this );
}
}
else {
visitValuesList( statement.getValuesList() );
}
}
@Override
public void visitColumnReference(ColumnReference columnReference) {
final Statement currentStatement;
if ( "excluded".equals( columnReference.getQualifier() )
&& ( currentStatement = getStatementStack().getCurrent() ) instanceof InsertSelectStatement
&& ( (InsertSelectStatement) currentStatement ).getSourceSelectStatement() == null ) {
// Accessing the excluded row for an insert-values statement in the conflict clause requires the values qualifier
appendSql( "values(" );
columnReference.appendReadExpression( this, null );
append( ')' );
}
else {
super.visitColumnReference( columnReference );
}
}
@Override
protected void renderDeleteClause(DeleteStatement statement) {
appendSql( "delete" );
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.DELETE );
renderTableReferenceIdentificationVariable( statement.getTargetTable() );
if ( statement.getFromClause().getRoots().isEmpty() ) {
appendSql( " from " );
renderDmlTargetTableExpression( statement.getTargetTable() );
}
else {
visitFromClause( statement.getFromClause() );
}
}
finally {
clauseStack.pop();
}
}
@Override
protected void renderUpdateClause(UpdateStatement updateStatement) {
if ( updateStatement.getFromClause().getRoots().isEmpty() ) {
super.renderUpdateClause( updateStatement );
}
else {
appendSql( "update " );
renderFromClauseSpaces( updateStatement.getFromClause() );
}
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
if ( getClauseStack().getCurrent() != Clause.INSERT ) {
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected boolean supportsJoinsInDelete() {
return true;
}
@Override
protected JdbcOperationQueryInsert translateInsert(InsertSelectStatement sqlAst) {
visitInsertStatement( sqlAst );
return new JdbcOperationQueryInsertImpl(
getSql(),
getParameterBinders(),
getAffectedTableNames(),
null
);
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
visitOnDuplicateKeyConflictClause( conflictClause );
}
@Override
protected String determineColumnReferenceQualifier(ColumnReference columnReference) {
final DmlTargetColumnQualifierSupport qualifierSupport = getDialect().getDmlTargetColumnQualifierSupport();
final MutationStatement currentDmlStatement;
final String dmlAlias;
// Since MariaDB does not support aliasing the insert target table,
// we must detect column reference that are used in the conflict clause
// and use the table expression as qualifier instead
if ( getClauseStack().getCurrent() != Clause.SET
|| !( ( currentDmlStatement = getCurrentDmlStatement() ) instanceof InsertSelectStatement )
|| ( dmlAlias = currentDmlStatement.getTargetTable().getIdentificationVariable() ) == null
|| !dmlAlias.equals( columnReference.getQualifier() ) ) {
return columnReference.getQualifier();
}
// Qualify the column reference with the table expression also when in subqueries
else if ( qualifierSupport != DmlTargetColumnQualifierSupport.NONE || !getQueryPartStack().isEmpty() ) {
return getCurrentDmlStatement().getTargetTable().getTableExpression();
}
else {
return null;
}
}
@Override
protected boolean supportsWithClauseInSubquery() {
return false;
@ -216,8 +360,8 @@ public class MariaDBSqlAstTranslator<T extends JdbcOperation> extends AbstractSq
}
@Override
protected String getFromDual() {
return " from dual";
protected String getDual() {
return "dual";
}
@Override

View File

@ -1513,4 +1513,14 @@ public class MySQLDialect extends Dialect {
public String getEnableConstraintsStatement() {
return "set foreign_key_checks = 1";
}
@Override
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return DmlTargetColumnQualifierSupport.TABLE_ALIAS;
}
@Override
public boolean supportsFromClauseInUpdate() {
return true;
}
}

View File

@ -6,25 +6,39 @@
*/
package org.hibernate.dialect;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.tree.MutationStatement;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.LikePredicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.internal.JdbcOperationQueryInsertImpl;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert;
/**
* A SQL AST translator for MySQL.
@ -87,6 +101,146 @@ public class MySQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlA
return sqlType;
}
@Override
protected void visitInsertSource(InsertSelectStatement statement) {
if ( statement.getSourceSelectStatement() != null ) {
if ( statement.getConflictClause() != null ) {
final List<ColumnReference> targetColumnReferences = statement.getTargetColumns();
final List<String> columnNames = new ArrayList<>( targetColumnReferences.size() );
for ( ColumnReference targetColumnReference : targetColumnReferences ) {
columnNames.add( targetColumnReference.getColumnExpression() );
}
appendSql( "select * from " );
emulateQueryPartTableReferenceColumnAliasing(
new QueryPartTableReference(
new SelectStatement( statement.getSourceSelectStatement() ),
"excluded",
columnNames,
false,
getSessionFactory()
)
);
}
else {
statement.getSourceSelectStatement().accept( this );
}
}
else {
visitValuesList( statement.getValuesList() );
if ( statement.getConflictClause() != null && getDialect().getMySQLVersion().isSameOrAfter( 8, 0, 19 ) ) {
appendSql( " as excluded" );
char separator = '(';
for ( ColumnReference targetColumn : statement.getTargetColumns() ) {
appendSql( separator );
appendSql( targetColumn.getColumnExpression() );
separator = ',';
}
appendSql( ')' );
}
}
}
@Override
public void visitColumnReference(ColumnReference columnReference) {
final Statement currentStatement;
if ( getDialect().getMySQLVersion().isBefore( 8, 0, 19 )
&& "excluded".equals( columnReference.getQualifier() )
&& ( currentStatement = getStatementStack().getCurrent() ) instanceof InsertSelectStatement
&& ( (InsertSelectStatement) currentStatement ).getSourceSelectStatement() == null ) {
// Accessing the excluded row for an insert-values statement in the conflict clause requires the values qualifier
appendSql( "values(" );
columnReference.appendReadExpression( this, null );
append( ')' );
}
else {
super.visitColumnReference( columnReference );
}
}
@Override
protected void renderDeleteClause(DeleteStatement statement) {
appendSql( "delete" );
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.DELETE );
renderTableReferenceIdentificationVariable( statement.getTargetTable() );
if ( statement.getFromClause().getRoots().isEmpty() ) {
appendSql( " from " );
renderDmlTargetTableExpression( statement.getTargetTable() );
}
else {
visitFromClause( statement.getFromClause() );
}
}
finally {
clauseStack.pop();
}
}
@Override
protected void renderUpdateClause(UpdateStatement updateStatement) {
if ( updateStatement.getFromClause().getRoots().isEmpty() ) {
super.renderUpdateClause( updateStatement );
}
else {
appendSql( "update " );
renderFromClauseSpaces( updateStatement.getFromClause() );
}
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
if ( getClauseStack().getCurrent() != Clause.INSERT ) {
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected boolean supportsJoinsInDelete() {
return true;
}
@Override
protected JdbcOperationQueryInsert translateInsert(InsertSelectStatement sqlAst) {
visitInsertStatement( sqlAst );
return new JdbcOperationQueryInsertImpl(
getSql(),
getParameterBinders(),
getAffectedTableNames(),
null
);
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
visitOnDuplicateKeyConflictClause( conflictClause );
}
@Override
protected String determineColumnReferenceQualifier(ColumnReference columnReference) {
final DmlTargetColumnQualifierSupport qualifierSupport = getDialect().getDmlTargetColumnQualifierSupport();
final MutationStatement currentDmlStatement;
final String dmlAlias;
// Since MySQL does not support aliasing the insert target table,
// we must detect column reference that are used in the conflict clause
// and use the table expression as qualifier instead
if ( getClauseStack().getCurrent() != Clause.SET
|| !( ( currentDmlStatement = getCurrentDmlStatement() ) instanceof InsertSelectStatement )
|| ( dmlAlias = currentDmlStatement.getTargetTable().getIdentificationVariable() ) == null
|| !dmlAlias.equals( columnReference.getQualifier() ) ) {
return columnReference.getQualifier();
}
// Qualify the column reference with the table expression also when in subqueries
else if ( qualifierSupport != DmlTargetColumnQualifierSupport.NONE || !getQueryPartStack().isEmpty() ) {
return getCurrentDmlStatement().getTargetTable().getTableExpression();
}
else {
return null;
}
}
@Override
protected void renderExpressionAsClauseItem(Expression expression) {
expression.accept( this );
@ -275,8 +429,8 @@ public class MySQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlA
}
@Override
protected String getFromDual() {
return " from dual";
protected String getDual() {
return "dual";
}
@Override

View File

@ -1048,6 +1048,7 @@ public class OracleDialect extends Dialect {
@Override
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
return (sqlException, message, sql) -> {
final String constraintName;
// interpreting Oracle exceptions is much much more precise based on their specific vendor codes.
switch ( JdbcExceptionHelper.extractErrorCode( sqlException ) ) {
@ -1080,9 +1081,19 @@ public class OracleDialect extends Dialect {
// data integrity violation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
case 1:
// ORA-00001: unique constraint violated
constraintName = getViolatedConstraintNameExtractor().extractConstraintName( sqlException );
return new ConstraintViolationException(
message,
sqlException,
sql,
ConstraintViolationException.ConstraintKind.UNIQUE,
constraintName
);
case 1407:
// ORA-01407: cannot update column to NULL
final String constraintName = getViolatedConstraintNameExtractor().extractConstraintName( sqlException );
constraintName = getViolatedConstraintNameExtractor().extractConstraintName( sqlException );
return new ConstraintViolationException( message, sqlException, sql, constraintName );
default:
@ -1564,4 +1575,9 @@ public class OracleDialect extends Dialect {
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return DmlTargetColumnQualifierSupport.TABLE_ALIAS;
}
@Override
public boolean supportsFromClauseInUpdate() {
return true;
}
}

View File

@ -33,25 +33,28 @@ import org.hibernate.sql.ast.tree.expression.SqlTupleContainer;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.FromClause;
import org.hibernate.sql.ast.tree.from.FunctionTableReference;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.UnionTableGroup;
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.insert.Values;
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectClause;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.sql.ast.tree.update.Assignment;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.model.ast.ColumnValueBinding;
import org.hibernate.sql.model.internal.OptionalTableUpdate;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
/**
@ -65,6 +68,54 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends SqlAstTrans
super( sessionFactory, statement );
}
@Override
protected void visitInsertStatementOnly(InsertSelectStatement statement) {
if ( statement.getConflictClause() == null || statement.getConflictClause().isDoNothing() ) {
// Render plain insert statement and possibly run into unique constraint violation
super.visitInsertStatementOnly( statement );
}
else {
visitInsertStatementEmulateMerge( statement );
}
}
@Override
protected void visitUpdateStatementOnly(UpdateStatement statement) {
if ( hasNonTrivialFromClause( statement.getFromClause() ) ) {
visitUpdateStatementEmulateInlineView( statement );
}
else {
renderUpdateClause( statement );
renderSetClause( statement.getAssignments() );
visitWhereClause( statement.getRestriction() );
visitReturningColumns( statement.getReturningColumns() );
}
}
@Override
protected void renderMergeUpdateClause(List<Assignment> assignments, Predicate wherePredicate) {
appendSql( " then update" );
renderSetClause( assignments );
visitWhereClause( wherePredicate );
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
if ( getClauseStack().getCurrent() != Clause.INSERT ) {
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
if ( conflictClause != null ) {
if ( conflictClause.isDoUpdate() && conflictClause.getConstraintName() != null ) {
throw new IllegalQueryOperationException( "Insert conflict do update clause with constraint name is not supported" );
}
}
}
@Override
protected boolean needsRecursiveKeywordInWithClause() {
return false;
@ -171,8 +222,15 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends SqlAstTrans
@Override
protected void visitValuesList(List<Values> valuesList) {
if ( valuesList.size() < 2 ) {
visitValuesListStandard( valuesList );
}
else {
// Oracle doesn't support a multi-values insert
// So we render a select union emulation instead
visitValuesListEmulateSelectUnion( valuesList );
}
}
@Override
public void visitValuesTableReference(ValuesTableReference tableReference) {
@ -555,13 +613,13 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends SqlAstTrans
}
@Override
protected String getFromDual() {
return " from dual";
protected String getDual() {
return "dual";
}
@Override
protected String getFromDualForSelectOnly() {
return getDialect().getVersion().isSameOrAfter( 23 ) ? super.getFromDualForSelectOnly() : getFromDual();
return getDialect().getVersion().isSameOrAfter( 23 ) ? "" : ( " from " + getDual() );
}
private boolean supportsOffsetFetchClause() {

View File

@ -787,6 +787,10 @@ public class PostgreSQLDialect extends Dialect {
public boolean supportsNonQueryWithCTE() {
return true;
}
@Override
public boolean supportsConflictClauseForInsertCTE() {
return true;
}
@Override
public SequenceSupport getSequenceSupport() {
@ -1547,4 +1551,14 @@ public class PostgreSQLDialect extends Dialect {
// The maximum scale for `interval second` is 6 unfortunately
return 6;
}
@Override
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return DmlTargetColumnQualifierSupport.TABLE_ALIAS;
}
@Override
public boolean supportsFromClauseInUpdate() {
return true;
}
}

View File

@ -10,6 +10,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.query.sqm.FetchClauseType;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.cte.CteMaterialization;
import org.hibernate.sql.ast.tree.cte.CteStatement;
@ -17,6 +18,10 @@ import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.InArrayPredicate;
import org.hibernate.sql.ast.tree.predicate.LikePredicate;
@ -24,7 +29,10 @@ import org.hibernate.sql.ast.tree.predicate.NullnessPredicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.internal.JdbcOperationQueryInsertImpl;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert;
import org.hibernate.sql.model.internal.TableInsertStandard;
import org.hibernate.type.SqlTypes;
@ -58,6 +66,55 @@ public class PostgreSQLSqlAstTranslator<T extends JdbcOperation> extends SqlAstT
appendSql( "default values" );
}
@Override
protected JdbcOperationQueryInsert translateInsert(InsertSelectStatement sqlAst) {
visitInsertStatement( sqlAst );
return new JdbcOperationQueryInsertImpl(
getSql(),
getParameterBinders(),
getAffectedTableNames(),
null
);
}
@Override
protected void renderTableReferenceIdentificationVariable(TableReference tableReference) {
final String identificationVariable = tableReference.getIdentificationVariable();
if ( identificationVariable != null ) {
final Clause currentClause = getClauseStack().getCurrent();
if ( currentClause == Clause.INSERT ) {
// PostgreSQL requires the "as" keyword for inserts
appendSql( " as " );
}
else {
append( WHITESPACE );
}
append( tableReference.getIdentificationVariable() );
}
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
final Statement currentStatement = getStatementStack().getCurrent();
if ( !( currentStatement instanceof UpdateStatement )
|| !hasNonTrivialFromClause( ( (UpdateStatement) currentStatement ).getFromClause() ) ) {
// For UPDATE statements we render a full FROM clause and a join condition to match target table rows,
// but for that to work, we have to omit the alias for the target table reference here
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected void renderFromClauseAfterUpdateSet(UpdateStatement statement) {
renderFromClauseJoiningDmlTargetReference( statement );
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
visitStandardConflictClause( conflictClause );
}
@Override
protected void renderExpressionAsClauseItem(Expression expression) {

View File

@ -49,6 +49,8 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.LockTimeoutException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor;
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.mapping.Column;
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
@ -84,6 +86,7 @@ import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
import jakarta.persistence.TemporalType;
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
import static org.hibernate.query.sqm.TemporalUnit.NANOSECOND;
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.INTEGER;
import static org.hibernate.type.SqlTypes.BLOB;
@ -699,6 +702,21 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
return true;
}
@Override
public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
return new TemplatedViolatedConstraintNameExtractor(
sqle -> {
switch ( JdbcExceptionHelper.extractErrorCode( sqle ) ) {
case 2627:
case 2601:
return extractUsingTemplate( "'", "'", sqle.getMessage() );
default:
return null;
}
}
);
}
@Override
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
return (sqlException, message, sql) -> {
@ -712,6 +730,13 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
case 1222:
return new LockTimeoutException( message, sqlException, sql );
case 2627:
return new ConstraintViolationException(
message,
sqlException,
sql,
ConstraintViolationException.ConstraintKind.UNIQUE,
getViolatedConstraintNameExtractor().extractConstraintName( sqlException )
);
case 2601:
return new ConstraintViolationException(
message,
@ -1106,4 +1131,14 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
final SQLServerSqlAstTranslator<JdbcOperation> translator = new SQLServerSqlAstTranslator<>( factory, optionalTableUpdate );
return translator.createMergeOperation( optionalTableUpdate );
}
@Override
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return DmlTargetColumnQualifierSupport.TABLE_ALIAS;
}
@Override
public boolean supportsFromClauseInUpdate() {
return true;
}
}

View File

@ -11,14 +11,19 @@ import java.util.List;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.query.sqm.FetchClauseType;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.MutationStatement;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
@ -28,12 +33,15 @@ import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.from.UnionTableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectClause;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.model.internal.OptionalTableUpdate;
import org.hibernate.type.SqlTypes;
@ -53,6 +61,85 @@ public class SQLServerSqlAstTranslator<T extends JdbcOperation> extends SqlAstTr
super( sessionFactory, statement );
}
@Override
protected void visitInsertStatementOnly(InsertSelectStatement statement) {
if ( statement.getConflictClause() == null || statement.getConflictClause().isDoNothing() ) {
// Render plain insert statement and possibly run into unique constraint violation
super.visitInsertStatementOnly( statement );
}
else {
visitInsertStatementEmulateMerge( statement );
// A merge statement must end with a `;` on SQL Server
appendSql( ';' );
}
}
@Override
protected void renderDeleteClause(DeleteStatement statement) {
appendSql( "delete" );
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.DELETE );
renderTableReferenceIdentificationVariable( statement.getTargetTable() );
if ( statement.getFromClause().getRoots().isEmpty() ) {
appendSql( " from " );
renderDmlTargetTableExpression( statement.getTargetTable() );
}
else {
visitFromClause( statement.getFromClause() );
}
}
finally {
clauseStack.pop();
}
}
@Override
protected void renderUpdateClause(UpdateStatement updateStatement) {
appendSql( "update" );
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.UPDATE );
renderTableReferenceIdentificationVariable( updateStatement.getTargetTable() );
}
finally {
clauseStack.pop();
}
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
if ( getClauseStack().getCurrent() != Clause.INSERT ) {
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected boolean supportsJoinsInDelete() {
return true;
}
@Override
protected void renderFromClauseAfterUpdateSet(UpdateStatement statement) {
if ( statement.getFromClause().getRoots().isEmpty() ) {
appendSql( " from " );
renderDmlTargetTableExpression( statement.getTargetTable() );
}
else {
visitFromClause( statement.getFromClause() );
}
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
if ( conflictClause != null ) {
if ( conflictClause.isDoUpdate() && conflictClause.getConstraintName() != null ) {
throw new IllegalQueryOperationException( "Insert conflict do update clause with constraint name is not supported" );
}
}
}
@Override
protected boolean needsRecursiveKeywordInWithClause() {
return false;
@ -128,14 +215,7 @@ public class SQLServerSqlAstTranslator<T extends JdbcOperation> extends SqlAstTr
appendSql( " )" );
registerAffectedTable( tableReference );
final Clause currentClause = getClauseStack().getCurrent();
if ( rendersTableReferenceAlias( currentClause ) ) {
final String identificationVariable = tableReference.getIdentificationVariable();
if ( identificationVariable != null ) {
appendSql( ' ' );
appendSql( identificationVariable );
}
}
renderTableReferenceIdentificationVariable( tableReference );
}
else {
super.renderNamedTableReference( tableReference, lockMode );

View File

@ -633,28 +633,17 @@ public class SybaseASEDialect extends SybaseDialect {
final int errorCode = JdbcExceptionHelper.extractErrorCode( sqle );
if ( sqlState != null ) {
switch ( sqlState ) {
// UNIQUE VIOLATION
case "S1000":
if ( 2601 == errorCode ) {
return extractUsingTemplate( "with unique index '", "'", sqle.getMessage() );
}
break;
case "23000":
if ( 546 == errorCode ) {
switch ( errorCode ) {
case 2601:
// UNIQUE VIOLATION
return extractUsingTemplate( "with unique index '", "'", sqle.getMessage() );
case 546:
// Foreign key violation
return extractUsingTemplate( "constraint name = '", "'", sqle.getMessage() );
}
break;
// // FOREIGN KEY VIOLATION
// case 23503:
// return extractUsingTemplate( "violates foreign key constraint \"","\"", sqle.getMessage() );
// // NOT NULL VIOLATION
// case 23502:
// return extractUsingTemplate( "null value in column \"","\" violates not-null constraint", sqle.getMessage() );
// // TODO: RESTRICT VIOLATION
// case 23001:
// return null;
// ALL OTHER
}
}
return null;
@ -671,30 +660,44 @@ public class SybaseASEDialect extends SybaseDialect {
case "JZ006":
return new LockTimeoutException( message, sqlException, sql );
case "S1000":
case "23000":
switch ( errorCode ) {
case 515:
// Attempt to insert NULL value into column; column does not allow nulls.
return new ConstraintViolationException(
message,
sqlException,
sql,
getViolatedConstraintNameExtractor().extractConstraintName( sqlException )
);
case 546:
// Foreign key violation
return new ConstraintViolationException(
message,
sqlException,
sql,
getViolatedConstraintNameExtractor().extractConstraintName( sqlException )
);
case 2601:
// Unique constraint violation
final String constraintName = getViolatedConstraintNameExtractor().extractConstraintName(
sqlException );
return new ConstraintViolationException( message, sqlException, sql, constraintName );
return new ConstraintViolationException(
message,
sqlException,
sql,
ConstraintViolationException.ConstraintKind.UNIQUE,
getViolatedConstraintNameExtractor().extractConstraintName( sqlException )
);
}
break;
case "ZZZZZ":
if ( 515 == errorCode ) {
// Attempt to insert NULL value into column; column does not allow nulls.
final String constraintName = getViolatedConstraintNameExtractor().extractConstraintName(
sqlException );
return new ConstraintViolationException( message, sqlException, sql, constraintName );
}
break;
case "23000":
if ( 546 == errorCode ) {
// Foreign key violation
final String constraintName = getViolatedConstraintNameExtractor().extractConstraintName(
sqlException );
return new ConstraintViolationException( message, sqlException, sql, constraintName );
return new ConstraintViolationException(
message,
sqlException,
sql,
getViolatedConstraintNameExtractor().extractConstraintName( sqlException )
);
}
break;
}

View File

@ -12,13 +12,17 @@ import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.MutationStatement;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
@ -33,6 +37,9 @@ import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.UnionTableReference;
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.insert.Values;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
@ -40,6 +47,7 @@ import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectClause;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
/**
@ -55,6 +63,77 @@ public class SybaseASESqlAstTranslator<T extends JdbcOperation> extends Abstract
super( sessionFactory, statement );
}
@Override
protected void visitInsertStatementOnly(InsertSelectStatement statement) {
if ( statement.getConflictClause() == null || statement.getConflictClause().isDoNothing() ) {
// Render plain insert statement and possibly run into unique constraint violation
super.visitInsertStatementOnly( statement );
}
else {
visitInsertStatementEmulateMerge( statement );
}
}
@Override
protected void renderDeleteClause(DeleteStatement statement) {
appendSql( "delete " );
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.DELETE );
renderDmlTargetTableExpression( statement.getTargetTable() );
if ( statement.getFromClause().getRoots().isEmpty() ) {
appendSql( " from " );
renderDmlTargetTableExpression( statement.getTargetTable() );
renderTableReferenceIdentificationVariable( statement.getTargetTable() );
}
else {
visitFromClause( statement.getFromClause() );
}
}
finally {
clauseStack.pop();
}
}
@Override
protected void renderUpdateClause(UpdateStatement updateStatement) {
appendSql( "update " );
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.UPDATE );
renderDmlTargetTableExpression( updateStatement.getTargetTable() );
}
finally {
clauseStack.pop();
}
}
@Override
protected boolean supportsJoinsInDelete() {
return true;
}
@Override
protected void renderFromClauseAfterUpdateSet(UpdateStatement statement) {
if ( statement.getFromClause().getRoots().isEmpty() ) {
appendSql( " from " );
renderDmlTargetTableExpression( statement.getTargetTable() );
renderTableReferenceIdentificationVariable( statement.getTargetTable() );
}
else {
visitFromClause( statement.getFromClause() );
}
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
if ( conflictClause != null ) {
if ( conflictClause.isDoUpdate() && conflictClause.getConstraintName() != null ) {
throw new IllegalQueryOperationException( "Insert conflict do update clause with constraint name is not supported" );
}
}
}
@Override
protected boolean supportsWithClause() {
return false;
@ -130,14 +209,7 @@ public class SybaseASESqlAstTranslator<T extends JdbcOperation> extends Abstract
appendSql( " )" );
registerAffectedTable( tableReference );
final Clause currentClause = getClauseStack().getCurrent();
if ( rendersTableReferenceAlias( currentClause ) ) {
final String identificationVariable = tableReference.getIdentificationVariable();
if ( identificationVariable != null ) {
appendSql( ' ' );
appendSql( identificationVariable );
}
}
renderTableReferenceIdentificationVariable( tableReference );
}
else {
super.renderNamedTableReference( tableReference, lockMode );
@ -250,6 +322,14 @@ public class SybaseASESqlAstTranslator<T extends JdbcOperation> extends Abstract
visitValuesListEmulateSelectUnion( valuesList );
}
@Override
public void visitValuesTableReference(ValuesTableReference tableReference) {
append( '(' );
visitValuesListEmulateSelectUnion( tableReference.getValuesList() );
append( ')' );
renderDerivedTableReference( tableReference );
}
@Override
public void visitOffsetFetchClause(QueryPart queryPart) {
assertRowsOnlyFetchClauseType( queryPart );
@ -384,9 +464,16 @@ public class SybaseASESqlAstTranslator<T extends JdbcOperation> extends Abstract
}
@Override
public void visitColumnReference(ColumnReference columnReference) {
final String dmlTargetTableAlias = getDmlTargetTableAlias();
if ( dmlTargetTableAlias != null && dmlTargetTableAlias.equals( columnReference.getQualifier() ) ) {
protected String determineColumnReferenceQualifier(ColumnReference columnReference) {
final DmlTargetColumnQualifierSupport qualifierSupport = getDialect().getDmlTargetColumnQualifierSupport();
final MutationStatement currentDmlStatement;
final String dmlAlias;
if ( qualifierSupport == DmlTargetColumnQualifierSupport.TABLE_ALIAS
|| ( currentDmlStatement = getCurrentDmlStatement() ) == null
|| ( dmlAlias = currentDmlStatement.getTargetTable().getIdentificationVariable() ) == null
|| !dmlAlias.equals( columnReference.getQualifier() ) ) {
return columnReference.getQualifier();
}
// Sybase needs a table name prefix
// but not if this is a restricted union table reference subquery
final QuerySpec currentQuerySpec = (QuerySpec) getQueryPartStack().getCurrent();
@ -394,53 +481,15 @@ public class SybaseASESqlAstTranslator<T extends JdbcOperation> extends Abstract
if ( currentQuerySpec != null && !currentQuerySpec.isRoot()
&& (roots = currentQuerySpec.getFromClause().getRoots()).size() == 1
&& roots.get( 0 ).getPrimaryTableReference() instanceof UnionTableReference ) {
columnReference.appendReadExpression( this );
return columnReference.getQualifier();
}
// for now, use the unqualified form
else if ( columnReference.isColumnExpressionFormula() ) {
// For formulas, we have to replace the qualifier as the alias was already rendered into the formula
// This is fine for now as this is only temporary anyway until we render aliases for table references
appendSql(
columnReference.getColumnExpression()
.replaceAll( "(\\b)(" + dmlTargetTableAlias + "\\.)(\\b)", "$1$3" )
);
return null;
}
else {
columnReference.appendReadExpression(
this,
getCurrentDmlStatement().getTargetTable().getTableExpression()
);
}
}
else {
columnReference.appendReadExpression( this );
}
}
@Override
public void visitAggregateColumnWriteExpression(AggregateColumnWriteExpression aggregateColumnWriteExpression) {
final String dmlTargetTableAlias = getDmlTargetTableAlias();
final ColumnReference columnReference = aggregateColumnWriteExpression.getColumnReference();
if ( dmlTargetTableAlias != null && dmlTargetTableAlias.equals( columnReference.getQualifier() ) ) {
// Sybase needs a table name prefix
// but not if this is a restricted union table reference subquery
final QuerySpec currentQuerySpec = (QuerySpec) getQueryPartStack().getCurrent();
final List<TableGroup> roots;
if ( currentQuerySpec != null && !currentQuerySpec.isRoot()
&& (roots = currentQuerySpec.getFromClause().getRoots()).size() == 1
&& roots.get( 0 ).getPrimaryTableReference() instanceof UnionTableReference ) {
aggregateColumnWriteExpression.appendWriteExpression( this, this );
}
else {
aggregateColumnWriteExpression.appendWriteExpression(
this,
this,
getCurrentDmlStatement().getTargetTable().getTableExpression()
);
}
}
else {
aggregateColumnWriteExpression.appendWriteExpression( this, this );
return getCurrentDmlStatement().getTargetTable().getTableExpression();
}
}
@ -465,8 +514,8 @@ public class SybaseASESqlAstTranslator<T extends JdbcOperation> extends Abstract
}
@Override
protected String getFromDual() {
return " from (select 1) dual(c1)";
protected String getDual() {
return "(select 1 c1)";
}
private boolean supportsParameterOffsetFetchExpression() {

View File

@ -514,4 +514,14 @@ public class SybaseDialect extends AbstractTransactSQLDialect {
? AbstractTransactSQLIdentityColumnSupport.INSTANCE
: SybaseJconnIdentityColumnSupport.INSTANCE;
}
@Override
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return DmlTargetColumnQualifierSupport.TABLE_ALIAS;
}
@Override
public boolean supportsFromClauseInUpdate() {
return true;
}
}

View File

@ -11,12 +11,15 @@ import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
@ -26,8 +29,13 @@ import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.UnionTableReference;
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.insert.Values;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
/**
@ -43,6 +51,46 @@ public class SybaseSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
super( sessionFactory, statement );
}
@Override
protected void visitInsertStatementOnly(InsertSelectStatement statement) {
if ( statement.getConflictClause() == null || statement.getConflictClause().isDoNothing() ) {
// Render plain insert statement and possibly run into unique constraint violation
super.visitInsertStatementOnly( statement );
}
else {
visitInsertStatementEmulateMerge( statement );
appendSql( ';' );
}
}
@Override
protected void renderDeleteClause(DeleteStatement statement) {
appendSql( "delete " );
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.DELETE );
renderDmlTargetTableExpression( statement.getTargetTable() );
}
finally {
clauseStack.pop();
}
visitFromClause( statement.getFromClause() );
}
@Override
protected void renderFromClauseAfterUpdateSet(UpdateStatement statement) {
visitFromClause( statement.getFromClause() );
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
if ( conflictClause != null ) {
if ( conflictClause.isDoUpdate() && conflictClause.getConstraintName() != null ) {
throw new IllegalQueryOperationException( "Insert conflict do update clause with constraint name is not supported" );
}
}
}
@Override
protected boolean supportsWithClause() {
return false;
@ -118,14 +166,7 @@ public class SybaseSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
appendSql( " )" );
registerAffectedTable( tableReference );
final Clause currentClause = getClauseStack().getCurrent();
if ( rendersTableReferenceAlias( currentClause ) ) {
final String identificationVariable = tableReference.getIdentificationVariable();
if ( identificationVariable != null ) {
appendSql( ' ' );
appendSql( identificationVariable );
}
}
renderTableReferenceIdentificationVariable( tableReference );
}
else {
super.renderNamedTableReference( tableReference, lockMode );
@ -146,6 +187,19 @@ public class SybaseSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
// Sybase does not support the FOR UPDATE clause
}
@Override
protected void visitValuesList(List<Values> valuesList) {
visitValuesListEmulateSelectUnion( valuesList );
}
@Override
public void visitValuesTableReference(ValuesTableReference tableReference) {
append( '(' );
visitValuesListEmulateSelectUnion( tableReference.getValuesList() );
append( ')' );
renderDerivedTableReference( tableReference );
}
@Override
public void visitOffsetFetchClause(QueryPart queryPart) {
assertRowsOnlyFetchClauseType( queryPart );

View File

@ -6,23 +6,38 @@
*/
package org.hibernate.dialect;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.tree.MutationStatement;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.LikePredicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.internal.JdbcOperationQueryInsertImpl;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert;
/**
* A SQL AST translator for TiDB.
@ -39,6 +54,135 @@ public class TiDBSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
this.dialect = (TiDBDialect) super.getDialect();
}
@Override
protected void visitInsertSource(InsertSelectStatement statement) {
if ( statement.getSourceSelectStatement() != null ) {
if ( statement.getConflictClause() != null ) {
final List<ColumnReference> targetColumnReferences = statement.getTargetColumns();
final List<String> columnNames = new ArrayList<>( targetColumnReferences.size() );
for ( ColumnReference targetColumnReference : targetColumnReferences ) {
columnNames.add( targetColumnReference.getColumnExpression() );
}
appendSql( "select * from " );
emulateQueryPartTableReferenceColumnAliasing(
new QueryPartTableReference(
new SelectStatement( statement.getSourceSelectStatement() ),
"excluded",
columnNames,
false,
getSessionFactory()
)
);
}
else {
statement.getSourceSelectStatement().accept( this );
}
}
else {
visitValuesList( statement.getValuesList() );
}
}
@Override
public void visitColumnReference(ColumnReference columnReference) {
final Statement currentStatement;
if ( "excluded".equals( columnReference.getQualifier() )
&& ( currentStatement = getStatementStack().getCurrent() ) instanceof InsertSelectStatement
&& ( (InsertSelectStatement) currentStatement ).getSourceSelectStatement() == null ) {
// Accessing the excluded row for an insert-values statement in the conflict clause requires the values qualifier
appendSql( "values(" );
columnReference.appendReadExpression( this, null );
append( ')' );
}
else {
super.visitColumnReference( columnReference );
}
}
@Override
protected void renderDeleteClause(DeleteStatement statement) {
appendSql( "delete" );
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.DELETE );
renderTableReferenceIdentificationVariable( statement.getTargetTable() );
if ( statement.getFromClause().getRoots().isEmpty() ) {
appendSql( " from " );
renderDmlTargetTableExpression( statement.getTargetTable() );
}
else {
visitFromClause( statement.getFromClause() );
}
}
finally {
clauseStack.pop();
}
}
@Override
protected void renderUpdateClause(UpdateStatement updateStatement) {
if ( updateStatement.getFromClause().getRoots().isEmpty() ) {
super.renderUpdateClause( updateStatement );
}
else {
appendSql( "update " );
renderFromClauseSpaces( updateStatement.getFromClause() );
}
}
@Override
protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
super.renderDmlTargetTableExpression( tableReference );
if ( getClauseStack().getCurrent() != Clause.INSERT ) {
renderTableReferenceIdentificationVariable( tableReference );
}
}
@Override
protected boolean supportsJoinsInDelete() {
return true;
}
@Override
protected JdbcOperationQueryInsert translateInsert(InsertSelectStatement sqlAst) {
visitInsertStatement( sqlAst );
return new JdbcOperationQueryInsertImpl(
getSql(),
getParameterBinders(),
getAffectedTableNames(),
null
);
}
@Override
protected void visitConflictClause(ConflictClause conflictClause) {
visitOnDuplicateKeyConflictClause( conflictClause );
}
@Override
protected String determineColumnReferenceQualifier(ColumnReference columnReference) {
final DmlTargetColumnQualifierSupport qualifierSupport = getDialect().getDmlTargetColumnQualifierSupport();
final MutationStatement currentDmlStatement;
final String dmlAlias;
// Since TiDB does not support aliasing the insert target table,
// we must detect column reference that are used in the conflict clause
// and use the table expression as qualifier instead
if ( getClauseStack().getCurrent() != Clause.SET
|| !( ( currentDmlStatement = getCurrentDmlStatement() ) instanceof InsertSelectStatement )
|| ( dmlAlias = currentDmlStatement.getTargetTable().getIdentificationVariable() ) == null
|| !dmlAlias.equals( columnReference.getQualifier() ) ) {
return columnReference.getQualifier();
}
// Qualify the column reference with the table expression also when in subqueries
else if ( qualifierSupport != DmlTargetColumnQualifierSupport.NONE || !getQueryPartStack().isEmpty() ) {
return getCurrentDmlStatement().getTargetTable().getTableExpression();
}
else {
return null;
}
}
@Override
protected void renderExpressionAsClauseItem(Expression expression) {
expression.accept( this );
@ -174,8 +318,8 @@ public class TiDBSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
}
@Override
protected String getFromDual() {
return " from dual";
protected String getDual() {
return "dual";
}
@Override

View File

@ -53,6 +53,7 @@ import org.hibernate.procedure.ProcedureCall;
import org.hibernate.query.MutationQuery;
import org.hibernate.query.SelectionQuery;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaInsert;
import org.hibernate.query.criteria.JpaCriteriaInsertSelect;
import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.query.spi.QueryProducerImplementor;
@ -513,6 +514,12 @@ public class SessionDelegatorBaseImpl implements SessionImplementor {
return delegate().createMutationQuery( insertSelect );
}
@Override
public MutationQuery createMutationQuery(@SuppressWarnings("rawtypes") JpaCriteriaInsert insertSelect) {
//noinspection resource
return delegate().createMutationQuery( insertSelect );
}
@Override
public <T> QueryImplementor<T> createQuery(CriteriaQuery<T> criteriaQuery) {
return queryDelegate().createQuery( criteriaQuery );

View File

@ -40,6 +40,7 @@ import org.hibernate.query.NativeQuery;
import org.hibernate.query.Query;
import org.hibernate.query.SelectionQuery;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaInsert;
import org.hibernate.query.criteria.JpaCriteriaInsertSelect;
import org.hibernate.stat.SessionStatistics;
@ -773,6 +774,11 @@ public class SessionLazyDelegator implements Session {
return this.lazySession.get().createMutationQuery( insertSelect );
}
@Override
public MutationQuery createMutationQuery(@SuppressWarnings("rawtypes") JpaCriteriaInsert insertSelect) {
return this.lazySession.get().createMutationQuery( insertSelect );
}
@Override
public MutationQuery createNativeMutationQuery(String sqlString) {
return this.lazySession.get().createNativeMutationQuery( sqlString );

View File

@ -34,6 +34,7 @@ import org.hibernate.procedure.ProcedureCall;
import org.hibernate.query.MutationQuery;
import org.hibernate.query.SelectionQuery;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaInsert;
import org.hibernate.query.criteria.JpaCriteriaInsertSelect;
import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.query.spi.QueryProducerImplementor;
@ -101,6 +102,12 @@ public class SharedSessionDelegatorBaseImpl implements SharedSessionContractImpl
return delegate().createMutationQuery( insertSelect );
}
@Override
public MutationQuery createMutationQuery(@SuppressWarnings("rawtypes") JpaCriteriaInsert insertSelect) {
//noinspection resource
return delegate().createMutationQuery( insertSelect );
}
@Override
public <T> QueryImplementor<T> createQuery(CriteriaQuery<T> criteriaQuery) {
return queryDelegate().createQuery( criteriaQuery );

View File

@ -19,15 +19,26 @@ import org.checkerframework.checker.nullness.qual.Nullable;
*/
public class ConstraintViolationException extends JDBCException {
private final ConstraintKind kind;
private final @Nullable String constraintName;
public ConstraintViolationException(String message, SQLException root, @Nullable String constraintName) {
super( message, root );
this.constraintName = constraintName;
this( message, root, ConstraintKind.OTHER, constraintName );
}
public ConstraintViolationException(String message, SQLException root, String sql, @Nullable String constraintName) {
this( message, root, sql, ConstraintKind.OTHER, constraintName );
}
public ConstraintViolationException(String message, SQLException root, ConstraintKind kind, @Nullable String constraintName) {
super( message, root );
this.kind = kind;
this.constraintName = constraintName;
}
public ConstraintViolationException(String message, SQLException root, String sql, ConstraintKind kind, @Nullable String constraintName) {
super( message, root, sql );
this.kind = kind;
this.constraintName = constraintName;
}
@ -39,4 +50,16 @@ public class ConstraintViolationException extends JDBCException {
public @Nullable String getConstraintName() {
return constraintName;
}
/**
* Returns the kind of constraint that was violated.
*/
public ConstraintKind getKind() {
return kind;
}
public enum ConstraintKind {
UNIQUE,
OTHER
}
}

View File

@ -70,6 +70,7 @@ import org.hibernate.query.SelectionQuery;
import org.hibernate.query.UnknownNamedQueryException;
import org.hibernate.query.criteria.CriteriaDefinition;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaInsert;
import org.hibernate.query.criteria.JpaCriteriaInsertSelect;
import org.hibernate.query.hql.spi.SqmQueryImplementor;
import org.hibernate.query.named.NamedResultSetMappingMemento;
@ -88,6 +89,7 @@ import org.hibernate.query.sqm.tree.SqmDmlStatement;
import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
import org.hibernate.query.sqm.tree.insert.SqmInsertStatement;
import org.hibernate.query.sqm.tree.select.SqmQueryGroup;
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
@ -1236,6 +1238,17 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
}
}
@Override
public MutationQuery createMutationQuery(@SuppressWarnings("rawtypes") JpaCriteriaInsert insertSelect) {
checkOpen();
try {
return createCriteriaQuery( (SqmInsertStatement<?>) insertSelect, null );
}
catch ( RuntimeException e ) {
throw getExceptionConverter().convert( e );
}
}
@Override
@SuppressWarnings("UnnecessaryLocalVariable")
public ProcedureCall getNamedProcedureCall(String name) {

View File

@ -65,7 +65,11 @@ public class PrimaryKey extends Constraint {
}
public String sqlConstraintString(Dialect dialect) {
StringBuilder buf = new StringBuilder("primary key (");
StringBuilder buf = new StringBuilder();
if ( orderingUniqueKey != null && orderingUniqueKey.isNameExplicit() ) {
buf.append( "constraint " ).append( orderingUniqueKey.getName() ).append( ' ' );
}
buf.append( "primary key (" );
boolean first = true;
for ( Column column : getColumns() ) {
if ( first ) {

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.query;
import org.hibernate.query.criteria.JpaCriteriaInsert;
import org.hibernate.query.criteria.JpaCriteriaInsertSelect;
import jakarta.persistence.criteria.CriteriaDelete;
@ -323,6 +324,11 @@ public interface QueryProducer {
*/
MutationQuery createMutationQuery(@SuppressWarnings("rawtypes") JpaCriteriaInsertSelect insertSelect);
/**
* Create a {@link MutationQuery} from the given insert criteria tree
*/
MutationQuery createMutationQuery(@SuppressWarnings("rawtypes") JpaCriteriaInsert insertSelect);
/**
* Create a {@link NativeQuery} instance for the given native SQL statement.
*

View File

@ -123,6 +123,12 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
<T> JpaCriteriaInsertSelect<T> createCriteriaInsertSelect(Class<T> targetEntity);
@Incubating
JpaValues values(Expression<?>... expressions);
@Incubating
JpaValues values(List<? extends Expression<?>> expressions);
/**
* Transform the given HQL {@code select} query to an equivalent criteria query.
*
@ -693,8 +699,6 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
<T> JpaExpression<T> value(T value);
<V, C extends Collection<V>> JpaExpression<Collection<V>> values(C collection);
@Override
<V, M extends Map<?, V>> Expression<Collection<V>> values(M map);

View File

@ -0,0 +1,107 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.query.criteria;
import java.util.List;
import org.hibernate.Incubating;
import jakarta.persistence.criteria.Path;
import jakarta.persistence.metamodel.SingularAttribute;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* A conflict clause for insert statements.
*
* @since 6.5
*/
@Incubating
public interface JpaConflictClause<T> {
/**
* The excluded row/object which was not inserted.
*/
JpaRoot<T> getExcludedRoot();
/**
* The unique constraint name for which a constraint violation is allowed.
*/
@Nullable String getConstraintName();
/**
* Sets the unique constraint name for which a constraint violation is allowed.
*
* @throws IllegalStateException when constraint paths have already been defined
*/
JpaConflictClause<T> conflictOnConstraint(@Nullable String constraintName);
/**
* The paths which are part of a unique constraint, for which a constraint violation is allowed.
*/
List<? extends JpaPath<?>> getConstraintPaths();
/**
* Shorthand for calling {@link #conflictOnConstraintPaths(List)} with paths resolved for the given attributes
* against the insert target.
*/
JpaConflictClause<T> conflictOnConstraintAttributes(String... attributes);
/**
* Shorthand for calling {@link #conflictOnConstraintPaths(List)} with paths resolved for the given attributes
* against the insert target.
*/
JpaConflictClause<T> conflictOnConstraintAttributes(SingularAttribute<T, ?>... attributes);
/**
* See {@link #conflictOnConstraintPaths(List)}.
*/
JpaConflictClause<T> conflictOnConstraintPaths(Path<?>... paths);
/**
* Sets the paths which are part of a unique constraint, for which a constraint violation is allowed.
*
* @throws IllegalStateException when a constraint name has already been defined
*/
JpaConflictClause<T> conflictOnConstraintPaths(List<? extends Path<?>> paths);
/**
* The action to do when a conflict due to a unique constraint violation happens.
*/
@Nullable JpaConflictUpdateAction<T> getConflictAction();
/**
* Sets the action to do on a conflict. Setting {@code null} means to do nothing.
*
* @see #createConflictUpdateAction()
*/
JpaConflictClause<T> onConflictDo(@Nullable JpaConflictUpdateAction<T> action);
/**
* Shorthand version for calling {@link #onConflictDo(JpaConflictUpdateAction)} with {@link #createConflictUpdateAction()}
* as argument and returning the update action.
*/
default JpaConflictUpdateAction<T> onConflictDoUpdate() {
final JpaConflictUpdateAction<T> conflictUpdateAction = createConflictUpdateAction();
onConflictDo( conflictUpdateAction );
return conflictUpdateAction;
}
/**
* Shorthand version for calling {@link #onConflictDo(JpaConflictUpdateAction)} with a {@code null} argument.
*/
default JpaConflictClause<T> onConflictDoNothing() {
return onConflictDo( null );
}
/**
* Create a new conflict update action for this insert statement.
*
* @return a new conflict update action
* @see #onConflictDo(JpaConflictUpdateAction)
*/
JpaConflictUpdateAction<T> createConflictUpdateAction();
}

View File

@ -0,0 +1,92 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.query.criteria;
import org.hibernate.Incubating;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.metamodel.SingularAttribute;
/**
* The update action that should happen on a unique constraint violation for an insert statement.
*
* @since 6.5
*/
@Incubating
public interface JpaConflictUpdateAction<T> {
/**
* Update the value of the specified attribute.
* @param attribute attribute to be updated
* @param value new value
* @return the modified update query
*/
<Y, X extends Y> JpaConflictUpdateAction<T> set(SingularAttribute<? super T, Y> attribute, X value);
/**
* Update the value of the specified attribute.
* @param attribute attribute to be updated
* @param value new value
* @return the modified update query
*/
<Y> JpaConflictUpdateAction<T> set(SingularAttribute<? super T, Y> attribute, Expression<? extends Y> value);
/**
* Update the value of the specified attribute.
* @param attribute attribute to be updated
* @param value new value
* @return the modified update query
*/
<Y, X extends Y> JpaConflictUpdateAction<T> set(Path<Y> attribute, X value);
/**
* Update the value of the specified attribute.
* @param attribute attribute to be updated
* @param value new value
* @return the modified update query
*/
<Y> JpaConflictUpdateAction<T> set(Path<Y> attribute, Expression<? extends Y> value);
/**
* Update the value of the specified attribute.
* @param attributeName name of the attribute to be updated
* @param value new value
* @return the modified update query
*/
JpaConflictUpdateAction<T> set(String attributeName, Object value);
/**
* Modify the update query to restrict the target of the update
* according to the specified boolean expression.
* Replaces the previously added restriction(s), if any.
* @param restriction a simple or compound boolean expression
* @return the modified update query
*/
JpaConflictUpdateAction<T> where(Expression<Boolean> restriction);
/**
* Modify the update query to restrict the target of the update
* according to the conjunction of the specified restriction
* predicates.
* Replaces the previously added restriction(s), if any.
* If no restrictions are specified, any previously added
* restrictions are simply removed.
* @param restrictions zero or more restriction predicates
* @return the modified update query
*/
JpaConflictUpdateAction<T> where(Predicate... restrictions);
/**
* Return the predicate that corresponds to the where clause
* restriction(s), or null if no restrictions have been
* specified.
* @return where clause predicate
*/
JpaPredicate getRestriction();
}

View File

@ -0,0 +1,62 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.query.criteria;
import java.util.List;
import org.hibernate.Incubating;
import jakarta.persistence.criteria.Path;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* The commonalities between insert-select and insert-values.
*
* @since 6.5
*/
@Incubating
public interface JpaCriteriaInsert<T> extends JpaManipulationCriteria<T> {
/**
* Returns the insertion target paths.
*/
List<? extends JpaPath<?>> getInsertionTargetPaths();
/**
* Sets the insertion target paths.
*/
JpaCriteriaInsert<T> setInsertionTargetPaths(Path<?>... insertionTargetPaths);
/**
* Sets the insertion target paths.
*/
JpaCriteriaInsert<T> setInsertionTargetPaths(List<? extends Path<?>> insertionTargetPaths);
/**
* Sets the conflict clause that defines what happens when an insert violates a unique constraint.
*/
JpaConflictClause<T> onConflict();
/**
* Sets the conflict clause that defines what happens when an insert violates a unique constraint.
*/
JpaCriteriaInsert<T> onConflict(@Nullable JpaConflictClause<T> conflictClause);
/**
* Returns the conflict clause that defines what happens when an insert violates a unique constraint,
* or {@code null} if there is none.
*/
@Nullable JpaConflictClause<T> getConflictClause();
/**
* Create a new conflict clause for this insert statement.
*
* @return a new conflict clause
* @see JpaCriteriaInsert#onConflict(JpaConflictClause)
*/
JpaConflictClause<T> createConflictClause();
}

View File

@ -8,6 +8,9 @@ package org.hibernate.query.criteria;
import org.hibernate.Incubating;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CriteriaQuery;
/**
* A representation of SqmInsertSelectStatement at the
* {@link org.hibernate.query.criteria} level, even though JPA does
@ -30,5 +33,10 @@ import org.hibernate.Incubating;
* @author Steve Ebersole
*/
@Incubating
public interface JpaCriteriaInsertSelect<T> extends JpaManipulationCriteria<T> {
public interface JpaCriteriaInsertSelect<T> extends JpaCriteriaInsert<T> {
JpaCriteriaInsertSelect<T> select(CriteriaQuery<Tuple> criteriaQuery);
@Override
JpaCriteriaInsertSelect<T> onConflict(JpaConflictClause<T> conflictClause);
}

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.query.criteria;
import java.util.List;
import org.hibernate.Incubating;
/**
@ -13,7 +15,7 @@ import org.hibernate.Incubating;
* {@link org.hibernate.query.criteria} level, even though JPA does
* not define support for insert-values criteria.
*
* @see org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement
* @see org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement
*
* @apiNote Incubating mainly for 2 purposes:<ul>
* <li>
@ -30,5 +32,13 @@ import org.hibernate.Incubating;
* @author Gavin King
*/
@Incubating
public interface JpaCriteriaInsertValues<T> extends JpaManipulationCriteria<T> {
public interface JpaCriteriaInsertValues<T> extends JpaCriteriaInsert<T> {
JpaCriteriaInsertValues<T> values(JpaValues... values);
JpaCriteriaInsertValues<T> values(List<? extends JpaValues> values);
@Override
JpaCriteriaInsertValues<T> onConflict(JpaConflictClause<T> conflictClause);
}

View File

@ -0,0 +1,25 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.query.criteria;
import java.util.List;
import org.hibernate.Incubating;
/**
* A tuple of values.
*
* @since 6.5
*/
@Incubating
public interface JpaValues {
/**
* Returns the expressions of this tuple.
*/
List<? extends JpaExpression<?>> getExpressions();
}

View File

@ -53,6 +53,7 @@ import org.hibernate.query.criteria.JpaSelection;
import org.hibernate.query.criteria.JpaSetJoin;
import org.hibernate.query.criteria.JpaSimpleCase;
import org.hibernate.query.criteria.JpaSubQuery;
import org.hibernate.query.criteria.JpaValues;
import org.hibernate.query.criteria.JpaWindow;
import org.hibernate.query.criteria.JpaWindowFrame;
import org.hibernate.query.NullPrecedence;
@ -148,6 +149,18 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde
return criteriaBuilder.createCriteriaInsertSelect( targetEntity );
}
@Override
@Incubating
public JpaValues values(Expression<?>... expressions) {
return criteriaBuilder.values( expressions );
}
@Override
@Incubating
public JpaValues values(List<? extends Expression<?>> expressions) {
return criteriaBuilder.values( expressions );
}
@Override
public <T> JpaCriteriaQuery<T> unionAll(CriteriaQuery<? extends T> query1, CriteriaQuery<?>... queries) {
return criteriaBuilder.unionAll( query1, queries );
@ -746,11 +759,6 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde
return criteriaBuilder.value( value );
}
@Override
public <V, C extends Collection<V>> JpaExpression<Collection<V>> values(C collection) {
return criteriaBuilder.values( collection );
}
@Override
public <V, M extends Map<?, V>> Expression<Collection<V>> values(M map) {
return criteriaBuilder.values( map );

View File

@ -24,9 +24,11 @@ import java.time.ZonedDateTime;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -105,6 +107,7 @@ import org.hibernate.query.sqm.produce.function.FunctionArgumentException;
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
import org.hibernate.query.sqm.spi.ParameterDeclarationContext;
import org.hibernate.query.sqm.spi.SqmCreationContext;
import org.hibernate.query.sqm.tree.AbstractSqmDmlStatement;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.SqmQuery;
import org.hibernate.query.sqm.tree.SqmStatement;
@ -171,6 +174,8 @@ import org.hibernate.query.sqm.tree.from.SqmFromClause;
import org.hibernate.query.sqm.tree.from.SqmJoin;
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
import org.hibernate.query.sqm.tree.from.SqmRoot;
import org.hibernate.query.sqm.tree.insert.SqmConflictClause;
import org.hibernate.query.sqm.tree.insert.SqmConflictUpdateAction;
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
import org.hibernate.query.sqm.tree.insert.SqmInsertStatement;
import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement;
@ -207,7 +212,10 @@ import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
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.SqmAssignment;
import org.hibernate.query.sqm.tree.update.SqmSetClause;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.tree.cte.CteMaterialization;
import org.hibernate.sql.ast.tree.cte.CteSearchClauseKind;
@ -487,8 +495,6 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
processingStateStack.push( processingState );
try {
queryExpressionContext.accept( this );
final SqmCreationProcessingState stateFieldsProcessingState = new SqmCreationProcessingStateImpl(
insertStatement,
this
@ -506,6 +512,9 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
processingStateStack.pop();
}
queryExpressionContext.accept( this );
insertStatement.onConflict( visitConflictClause( ctx.conflictClause() ) );
return insertStatement;
}
finally {
@ -526,21 +535,43 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
processingState.getPathRegistry().register( root );
try {
final HqlParser.ValuesListContext valuesListContext = ctx.valuesList();
for ( int i = 1; i < valuesListContext.getChildCount(); i += 2 ) {
final ParseTree values = valuesListContext.getChild( i );
final SqmValues sqmValues = new SqmValues();
for ( int j = 1; j < values.getChildCount(); j += 2 ) {
sqmValues.getExpressions().add( (SqmExpression<?>) values.getChild( j ).accept( this ) );
}
insertStatement.getValuesList().add( sqmValues );
}
for ( HqlParser.SimplePathContext stateFieldCtx : targetFieldsSpecContext.simplePath() ) {
final SqmPath<?> stateField = (SqmPath<?>) visitSimplePath( stateFieldCtx );
insertStatement.addInsertTargetStateField( stateField );
}
final ArrayList<SqmValues> valuesList = new ArrayList<>();
final HqlParser.ValuesListContext valuesListContext = ctx.valuesList();
for ( int i = 1; i < valuesListContext.getChildCount(); i += 2 ) {
final ParseTree values = valuesListContext.getChild( i );
final ArrayList<SqmExpression<?>> valuesExpressions = new ArrayList<>();
final Iterator<SqmPath<?>> iterator = insertStatement.getInsertionTargetPaths().iterator();
for ( int j = 1; j < values.getChildCount(); j += 2 ) {
final SqmPath<?> targetPath = iterator.next();
final Class<?> targetPathJavaType = targetPath.getJavaType();
final boolean isEnum = targetPathJavaType != null && targetPathJavaType.isEnum();
final ParseTree valuesContext = values.getChild( j );
final HqlParser.ExpressionContext expressionContext;
final Map<Class<?>, Enum<?>> possibleEnumValues;
final SqmExpression<?> value;
if ( isEnum && valuesContext.getChild( 0 ) instanceof HqlParser.ExpressionContext
&& ( possibleEnumValues = getPossibleEnumValues( expressionContext = (HqlParser.ExpressionContext) valuesContext.getChild( 0 ) ) ) != null ) {
value = resolveEnumShorthandLiteral(
expressionContext,
possibleEnumValues,
targetPathJavaType
);
}
else {
value = (SqmExpression<?>) valuesContext.accept( this );
}
valuesExpressions.add( value );
}
valuesList.add( new SqmValues( valuesExpressions ) );
}
insertStatement.values( valuesList );
insertStatement.onConflict( visitConflictClause( ctx.conflictClause() ) );
return insertStatement;
}
finally {
@ -549,6 +580,42 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
}
}
@Override
public SqmConflictClause<R> visitConflictClause(HqlParser.ConflictClauseContext ctx) {
if ( ctx == null ) {
return null;
}
final SqmCreationProcessingState processingState = processingStateStack.getCurrent();
final SqmInsertStatement<R> statement = (SqmInsertStatement<R>) processingState.getProcessingQuery();
final SqmConflictClause<R> conflictClause = new SqmConflictClause<>( statement );
final HqlParser.ConflictTargetContext conflictTargetContext = ctx.conflictTarget();
if ( conflictTargetContext != null ) {
final HqlParser.IdentifierContext identifierCtx = conflictTargetContext.identifier();
if ( identifierCtx != null ) {
conflictClause.conflictOnConstraint( visitIdentifier( identifierCtx ) );
}
else {
final List<SqmPath<?>> constraintAttributes = new ArrayList<>();
for ( HqlParser.SimplePathContext pathContext : conflictTargetContext.simplePath() ) {
constraintAttributes.add( consumeDomainPath( pathContext ) );
}
conflictClause.conflictOnConstraintPaths( constraintAttributes );
}
}
final HqlParser.ConflictActionContext conflictActionContext = ctx.conflictAction();
final HqlParser.SetClauseContext setClauseContext = conflictActionContext.setClause();
if ( setClauseContext != null ) {
processingState.getPathRegistry().registerByAliasOnly( conflictClause.getExcludedRoot() );
final SqmConflictUpdateAction<R> updateAction = conflictClause.onConflictDoUpdate();
for ( HqlParser.AssignmentContext assignmentContext : setClauseContext.assignment() ) {
updateAction.addAssignment( visitAssignment( assignmentContext ) );
}
final SqmPredicate sqmPredicate = visitWhereClause( conflictActionContext.whereClause() );
updateAction.where( sqmPredicate );
}
return conflictClause;
}
@Override
public SqmUpdateStatement<R> visitUpdateStatement(HqlParser.UpdateStatementContext ctx) {
final boolean versioned = ctx.VERSIONED() != null;
@ -577,12 +644,29 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
final HqlParser.SetClauseContext setClauseCtx = ctx.setClause();
for ( ParseTree subCtx : setClauseCtx.children ) {
if ( subCtx instanceof HqlParser.AssignmentContext ) {
final HqlParser.AssignmentContext assignmentContext = (HqlParser.AssignmentContext) subCtx;
updateStatement.applyAssignment( visitAssignment( (HqlParser.AssignmentContext) subCtx ) );
}
}
final HqlParser.WhereClauseContext whereClauseContext = ctx.whereClause();
if ( whereClauseContext != null ) {
updateStatement.applyPredicate( visitWhereClause( whereClauseContext ) );
}
return updateStatement;
}
finally {
processingStateStack.pop();
}
}
@Override
public SqmAssignment<?> visitAssignment(HqlParser.AssignmentContext ctx) {
//noinspection unchecked
final SqmPath<Object> targetPath = (SqmPath<Object>) consumeDomainPath( assignmentContext.simplePath() );
final SqmPath<Object> targetPath = (SqmPath<Object>) consumeDomainPath( ctx.simplePath() );
final Class<?> targetPathJavaType = targetPath.getJavaType();
final boolean isEnum = targetPathJavaType != null && targetPathJavaType.isEnum();
final ParseTree rightSide = assignmentContext.getChild( 2 );
final ParseTree rightSide = ctx.getChild( 2 );
final HqlParser.ExpressionContext expressionContext;
final Map<Class<?>, Enum<?>> possibleEnumValues;
final SqmExpression<?> value;
@ -597,20 +681,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
else {
value = (SqmExpression<?>) rightSide.accept( this );
}
updateStatement.applyAssignment( targetPath, value );
}
}
final HqlParser.WhereClauseContext whereClauseContext = ctx.whereClause();
if ( whereClauseContext != null ) {
updateStatement.applyPredicate( visitWhereClause( whereClauseContext ) );
}
return updateStatement;
}
finally {
processingStateStack.pop();
}
return new SqmAssignment<>( targetPath, value );
}
@Override

View File

@ -71,26 +71,7 @@ public class SqmPathRegistryImpl implements SqmPathRegistry {
if ( sqmPath instanceof SqmFrom<?, ?> ) {
final SqmFrom<?, ?> sqmFrom = (SqmFrom<?, ?>) sqmPath;
final String alias = sqmPath.getExplicitAlias();
if ( alias != null ) {
final String aliasToUse = jpaCompliance.isJpaQueryComplianceEnabled()
? alias.toLowerCase( Locale.getDefault() )
: alias;
final SqmFrom<?, ?> previousFrom = sqmFromByAlias.put( aliasToUse, sqmFrom );
if ( previousFrom != null ) {
throw new AliasCollisionException(
String.format(
Locale.ENGLISH,
"Alias [%s] used for multiple from-clause elements : %s, %s",
alias,
previousFrom,
sqmPath
)
);
}
}
registerByAliasOnly( sqmFrom );
final SqmFrom<?, ?> previousFromByPath = sqmFromByPath.put( sqmPath.getNavigablePath(), sqmFrom );
@ -124,6 +105,30 @@ public class SqmPathRegistryImpl implements SqmPathRegistry {
}
}
@Override
public void registerByAliasOnly(SqmFrom<?, ?> sqmFrom) {
final String alias = sqmFrom.getExplicitAlias();
if ( alias != null ) {
final String aliasToUse = jpaCompliance.isJpaQueryComplianceEnabled()
? alias.toLowerCase( Locale.getDefault() )
: alias;
final SqmFrom<?, ?> previousFrom = sqmFromByAlias.put( aliasToUse, sqmFrom );
if ( previousFrom != null ) {
throw new AliasCollisionException(
String.format(
Locale.ENGLISH,
"Alias [%s] used for multiple from-clause elements : %s, %s",
alias,
previousFrom,
sqmFrom
)
);
}
}
}
@Override
public <E> void replace(SqmEntityJoin<E> sqmJoin, SqmRoot<E> sqmRoot) {
final String alias = sqmJoin.getExplicitAlias();

View File

@ -36,6 +36,13 @@ public interface SqmPathRegistry {
*/
void register(SqmPath<?> sqmPath);
/**
* Register an SqmFrom by alias only.
* Effectively, this makes the from node only resolvable via the alias,
* which means that the from node is ignored in {@link #findFromExposing(String)}.
*/
void registerByAliasOnly(SqmFrom<?, ?> sqmFrom);
/**
* Used with {@linkplain JpaCompliance#isJpaQueryComplianceEnabled() JPA compliance}
* to treat secondary query roots as cross-joins. Here we will replace the {@code sqmRoot}

View File

@ -32,6 +32,7 @@ import org.hibernate.query.criteria.JpaPredicate;
import org.hibernate.query.criteria.JpaSearchedCase;
import org.hibernate.query.criteria.JpaSelection;
import org.hibernate.query.criteria.JpaSimpleCase;
import org.hibernate.query.criteria.JpaValues;
import org.hibernate.query.criteria.JpaWindow;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
@ -48,6 +49,7 @@ import org.hibernate.query.sqm.tree.expression.SqmTuple;
import org.hibernate.query.sqm.tree.from.SqmRoot;
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement;
import org.hibernate.query.sqm.tree.insert.SqmValues;
import org.hibernate.query.sqm.tree.predicate.SqmInPredicate;
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
@ -522,6 +524,12 @@ public interface NodeBuilder extends HibernateCriteriaBuilder {
@Override
<T> SqmInsertSelectStatement<T> createCriteriaInsertSelect(Class<T> targetEntity);
@Override
SqmValues values(Expression<?>... expressions);
@Override
SqmValues values(List<? extends Expression<?>> expressions);
@Override
<N extends Number> SqmExpression<N> abs(Expression<N> x);
@ -775,9 +783,6 @@ public interface NodeBuilder extends HibernateCriteriaBuilder {
@Override
<K, L extends List<?>> SqmExpression<Set<K>> indexes(L list);
@Override
<V, C extends Collection<V>> SqmExpression<Collection<V>> values(C collection);
@Override
<V, M extends Map<?, V>> Expression<Collection<V>> values(M map);

View File

@ -87,6 +87,7 @@ import org.hibernate.query.sqm.tree.from.SqmDerivedJoin;
import org.hibernate.query.sqm.tree.from.SqmEntityJoin;
import org.hibernate.query.sqm.tree.from.SqmFromClause;
import org.hibernate.query.sqm.tree.from.SqmRoot;
import org.hibernate.query.sqm.tree.insert.SqmConflictClause;
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement;
import org.hibernate.query.sqm.tree.insert.SqmValues;
@ -136,6 +137,8 @@ public interface SemanticQueryWalker<T> {
T visitInsertValuesStatement(SqmInsertValuesStatement<?> statement);
T visitConflictClause(SqmConflictClause<?> sqmConflictClause);
T visitDeleteStatement(SqmDeleteStatement<?> statement);
T visitSelectStatement(SqmSelectStatement<?> statement);

View File

@ -827,7 +827,7 @@ public class QuerySqmImpl<R>
final NonSelectQueryPlan[] planParts = new NonSelectQueryPlan[valuesList.size()];
for ( int i = 0; i < valuesList.size(); i++ ) {
final SqmInsertValuesStatement<?> subInsert = insertValues.copyWithoutValues( SqmCopyContext.simpleContext() );
subInsert.getValuesList().add( valuesList.get( i ) );
subInsert.values( valuesList );
planParts[i] = new SimpleInsertQueryPlan( subInsert, domainParameterXref );
}

View File

@ -131,6 +131,7 @@ import org.hibernate.query.sqm.tree.expression.ValueBindJpaCriteriaParameter;
import org.hibernate.query.sqm.tree.from.SqmRoot;
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement;
import org.hibernate.query.sqm.tree.insert.SqmValues;
import org.hibernate.query.sqm.tree.predicate.SqmBetweenPredicate;
import org.hibernate.query.sqm.tree.predicate.SqmBooleanExpressionPredicate;
import org.hibernate.query.sqm.tree.predicate.SqmComparisonPredicate;
@ -353,6 +354,17 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
return new SqmInsertSelectStatement<>( targetEntity, this );
}
@Override
public SqmValues values(Expression<?>... expressions) {
return values( Arrays.asList( expressions ) );
}
@Override
public SqmValues values(List<? extends Expression<?>> expressions) {
//noinspection unchecked
return new SqmValues( (List<SqmExpression<?>>) expressions );
}
@Override
public <T> JpaCriteriaQuery<T> union(boolean all, CriteriaQuery<? extends T> query1, CriteriaQuery<?>... queries) {
return setOperation( all ? SetOperator.UNION_ALL : SetOperator.UNION, query1, queries );
@ -1904,14 +1916,9 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
// || is a literal enum mapped to a PostgreSQL named 'enum' type
}
@Override
public <V, C extends Collection<V>> SqmExpression<Collection<V>> values(C collection) {
throw new UnsupportedOperationException();
}
@Override
public <V, M extends Map<?, V>> Expression<Collection<V>> values(M map) {
throw new UnsupportedOperationException();
return value( map.values() );
}
@Override

View File

@ -78,6 +78,8 @@ import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.from.SqmFromClause;
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
import org.hibernate.query.sqm.tree.from.SqmRoot;
import org.hibernate.query.sqm.tree.insert.SqmConflictClause;
import org.hibernate.query.sqm.tree.insert.SqmConflictUpdateAction;
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement;
import org.hibernate.query.sqm.tree.insert.SqmValues;
@ -330,6 +332,12 @@ public class SqmTreePrinter implements SemanticQueryWalker<Object> {
"into",
() -> statement.getInsertionTargetPaths().forEach( sqmPath -> sqmPath.accept( this ) )
);
if ( statement.getConflictClause() != null ) {
processStanza(
"on conflict",
() -> statement.getConflictClause().accept( this )
);
}
}
);
}
@ -337,6 +345,29 @@ public class SqmTreePrinter implements SemanticQueryWalker<Object> {
return null;
}
@Override
public Object visitConflictClause(SqmConflictClause<?> sqmConflictClause) {
if ( sqmConflictClause.getConstraintName() != null ) {
logWithIndentation( "[constraintName = %s]", sqmConflictClause.getConstraintName() );
}
else {
processStanza(
"constraint attributes",
() -> sqmConflictClause.getConstraintPaths().forEach( sqmPath -> sqmPath.accept( this ) )
);
}
final SqmConflictUpdateAction<?> updateAction = sqmConflictClause.getConflictAction();
if ( updateAction == null ) {
logWithIndentation( "do nothing" );
}
else {
logWithIndentation( "do update " );
visitSetClause( updateAction.getSetClause() );
visitWhereClause( updateAction.getWhereClause() );
}
return null;
}
@Override
public Object visitSelectStatement(SqmSelectStatement<?> statement) {
if ( DEBUG_ENABLED ) {

View File

@ -6,62 +6,37 @@
*/
package org.hibernate.query.sqm.mutation.internal;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter;
import org.hibernate.query.sqm.sql.internal.SqlAstProcessingStateImpl;
import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.from.SqmRoot;
import org.hibernate.query.sqm.tree.insert.SqmInsertStatement;
import org.hibernate.query.sqm.tree.predicate.SqmWhereClause;
import org.hibernate.query.sqm.tree.update.SqmSetClause;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstHelper;
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.update.Assignable;
import org.hibernate.sql.ast.tree.update.Assignment;
/**
* Specialized BaseSqmToSqlAstConverter implementation used during conversion
* of an SQM mutation query tree representing into the various SQL AST trees
* needed to perform that operation.
*
* @see #visitSetClause(SqmSetClause, Consumer, SqmParameterResolutionConsumer)
* @see #visitWhereClause(SqmWhereClause, Consumer, SqmParameterResolutionConsumer)
*
* @author Steve Ebersole
*/
public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter<Statement> {
public interface SqmParameterResolutionConsumer {
void accept(SqmParameter<?> sqmParam, MappingModelExpressible<?> mappingType, List<JdbcParameter> jdbcParameters);
}
private final EntityMappingType mutatingEntityDescriptor;
private final TableGroup mutatingTableGroup;
private Predicate discriminatorPredicate;
private SqmParameterResolutionConsumer parameterResolutionConsumer;
public MultiTableSqmMutationConverter(
EntityMappingType mutatingEntityDescriptor,
SqmStatement<?> statement,
@ -119,6 +94,7 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter<Sta
sourceAlias,
null,
() -> (predicate) -> {
assert this.discriminatorPredicate == null;
this.discriminatorPredicate = predicate;
},
this
@ -146,98 +122,9 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter<Sta
return super.getProcessingStateStack();
}
/**
* Specialized hook to visit the assignments defined by the update SQM.
*/
public void visitSetClause(
SqmSetClause setClause,
Consumer<Assignment> assignmentConsumer,
SqmParameterResolutionConsumer parameterResolutionConsumer) {
this.parameterResolutionConsumer = parameterResolutionConsumer;
final List<Assignment> assignments = super.visitSetClause( setClause );
for ( Assignment assignment : assignments ) {
assignmentConsumer.accept( assignment );
}
}
public List<Assignment> visitSetClause(SqmSetClause setClause) {
throw new UnsupportedOperationException();
}
public Predicate visitWhereClause(
SqmWhereClause sqmWhereClause,
Consumer<ColumnReference> restrictionColumnReferenceConsumer,
SqmParameterResolutionConsumer parameterResolutionConsumer) {
this.parameterResolutionConsumer = parameterResolutionConsumer;
if ( sqmWhereClause == null || sqmWhereClause.getPredicate() == null ) {
return discriminatorPredicate;
}
final SqlAstProcessingState rootProcessingState = getCurrentProcessingState();
final SqlAstProcessingStateImpl restrictionProcessingState = new SqlAstProcessingStateImpl(
rootProcessingState,
this,
getCurrentClauseStack()::getCurrent
) {
@Override
public SqlExpressionResolver getSqlExpressionResolver() {
return this;
}
@Override
public Expression resolveSqlExpression(
ColumnReferenceKey key, Function<SqlAstProcessingState, Expression> creator) {
final Expression expression = rootProcessingState.getSqlExpressionResolver().resolveSqlExpression(
key,
creator
);
if ( expression instanceof ColumnReference ) {
restrictionColumnReferenceConsumer.accept( (ColumnReference) expression );
}
return expression;
}
};
pushProcessingState( restrictionProcessingState, getFromClauseIndex() );
try {
getCurrentClauseStack().push( Clause.WHERE );
return SqlAstHelper.combinePredicates(
(Predicate) sqmWhereClause.getPredicate().accept( this ),
discriminatorPredicate
);
}
finally {
getCurrentClauseStack().pop();
popProcessingStateStack();
this.parameterResolutionConsumer = null;
}
}
@Override
public Predicate visitWhereClause(SqmWhereClause whereClause) {
return (Predicate) super.visitWhereClause( whereClause );
}
@Override
protected Expression consumeSqmParameter(
SqmParameter<?> sqmParameter,
MappingModelExpressible<?> valueMapping,
BiConsumer<Integer, JdbcParameter> jdbcParameterConsumer) {
assert parameterResolutionConsumer != null;
final Expression expression = super.consumeSqmParameter( sqmParameter, valueMapping, jdbcParameterConsumer );
final List<List<JdbcParameter>> jdbcParameters = getJdbcParamsBySqmParam().get( sqmParameter );
final MappingModelExpressible<?> mappingType = getSqmParameterMappingModelExpressibleResolutions().get( sqmParameter );
parameterResolutionConsumer.accept(
sqmParameter,
mappingType,
jdbcParameters.get( jdbcParameters.size() - 1 )
);
return expression;
return SqlAstHelper.combinePredicates( super.visitWhereClause( whereClause ), discriminatorPredicate );
}
}

View File

@ -28,6 +28,7 @@ import org.hibernate.query.sqm.internal.SqmUtil;
import org.hibernate.query.sqm.mutation.internal.MatchingIdSelectionHelper;
import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter;
import org.hibernate.query.sqm.mutation.spi.AbstractMutationHandler;
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
@ -131,14 +132,7 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler
parameterResolutions = new IdentityHashMap<>();
}
//noinspection rawtypes
final Map<SqmParameter, MappingModelExpressible> paramTypeResolutions = new LinkedHashMap<>();
final Predicate restriction = sqmConverter.visitWhereClause(
sqmMutationStatement.getWhereClause(),
columnReference -> {},
(sqmParam, mappingType, jdbcParameters) -> paramTypeResolutions.put( sqmParam, mappingType )
);
final Predicate restriction = sqmConverter.visitWhereClause( sqmMutationStatement.getWhereClause() );
sqmConverter.pruneTableGroupJoins();
final CteStatement idSelectCte = new CteStatement(
@ -193,7 +187,12 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler
SqmUtil.generateJdbcParamsXref( domainParameterXref, sqmConverter ),
factory.getRuntimeMetamodels().getMappingMetamodel(),
navigablePath -> sqmConverter.getMutatingTableGroup(),
paramTypeResolutions::get,
new SqmParameterMappingModelResolutionAccess() {
@Override @SuppressWarnings("unchecked")
public <T> MappingModelExpressible<T> getResolvedMappingModelType(SqmParameter<T> parameter) {
return (MappingModelExpressible<T>) sqmConverter.getSqmParameterMappingModelExpressibleResolutions().get( parameter );
}
},
executionContext.getSession()
);
final LockOptions lockOptions = executionContext.getQueryOptions().getLockOptions();

View File

@ -8,14 +8,15 @@ package org.hibernate.query.sqm.mutation.internal.cte;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.temptable.TemporaryTable;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.id.BulkInsertionCapableIdentifierGenerator;
@ -37,6 +38,7 @@ import org.hibernate.query.results.TableGroupImpl;
import org.hibernate.query.spi.DomainQueryExecutionContext;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.query.sqm.SetOperator;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter;
import org.hibernate.query.sqm.internal.SqmUtil;
@ -46,9 +48,11 @@ import org.hibernate.query.sqm.mutation.internal.SqmInsertStrategyHelper;
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter;
import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.expression.SqmStar;
import org.hibernate.query.sqm.tree.insert.SqmConflictClause;
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
import org.hibernate.query.sqm.tree.insert.SqmInsertStatement;
import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement;
@ -70,21 +74,32 @@ import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.sql.ast.tree.expression.SelfRenderingSqlFragmentExpression;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.Star;
import org.hibernate.sql.ast.tree.from.DerivedTableReference;
import org.hibernate.sql.ast.tree.from.FromClause;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.QueryPartTableGroup;
import org.hibernate.sql.ast.tree.from.StandardTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.from.TableReferenceJoin;
import org.hibernate.sql.ast.tree.from.UnionTableReference;
import org.hibernate.sql.ast.tree.from.ValuesTableGroup;
import org.hibernate.sql.ast.tree.insert.ConflictClause;
import org.hibernate.sql.ast.tree.insert.InsertSelectStatement;
import org.hibernate.sql.ast.tree.insert.Values;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.sql.ast.tree.predicate.ExistsPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.sql.ast.tree.update.Assignment;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.results.graph.DomainResult;
@ -92,6 +107,7 @@ import org.hibernate.sql.results.graph.basic.BasicResult;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.sql.results.spi.ListResultsConsumer;
import org.hibernate.generator.Generator;
import org.hibernate.type.BasicType;
import org.hibernate.type.spi.TypeConfiguration;
/**
@ -191,12 +207,6 @@ public class CteInsertHandler implements InsertHandler {
final int size = sqmStatement.getInsertionTargetPaths().size();
final List<Map.Entry<List<CteColumn>, Assignment>> targetPathColumns = new ArrayList<>( size );
final List<CteColumn> targetPathCteColumns = new ArrayList<>( size );
final NamedTableReference entityTableReference = new NamedTableReference(
cteTable.getTableExpression(),
TemporaryTable.DEFAULT_ALIAS,
true
);
final InsertSelectStatement insertStatement = new InsertSelectStatement( entityTableReference );
final BaseSqmToSqlAstConverter.AdditionalInsertValues additionalInsertValues = sqmConverter.visitInsertionTargetPaths(
(assignable, columnReferences) -> {
@ -211,7 +221,6 @@ public class CteInsertHandler implements InsertHandler {
final int end = offset + pathInterpretation.getExpressionType().getJdbcTypeCount();
// Find a matching cte table column and set that at the current index
final List<CteColumn> columns = cteTable.getCteColumns().subList( offset, end );
insertStatement.addTargetColumnReferences( columnReferences );
targetPathCteColumns.addAll( columns );
targetPathColumns.add(
new AbstractMap.SimpleEntry<>(
@ -245,17 +254,6 @@ public class CteInsertHandler implements InsertHandler {
if ( additionalInsertValues.applySelections( querySpec, sessionFactory ) ) {
final CteColumn rowNumberColumn = cteTable.getCteColumns()
.get( cteTable.getCteColumns().size() - 1 );
final ColumnReference columnReference = new ColumnReference(
(String) null,
rowNumberColumn.getColumnExpression(),
false,
null,
rowNumberColumn.getJdbcMapping()
);
insertStatement.getTargetColumns().set(
insertStatement.getTargetColumns().size() - 1,
columnReference
);
targetPathCteColumns.set(
targetPathCteColumns.size() - 1,
rowNumberColumn
@ -326,14 +324,6 @@ public class CteInsertHandler implements InsertHandler {
// Add the row number to the assignments
final CteColumn rowNumberColumn = cteTable.getCteColumns()
.get( cteTable.getCteColumns().size() - 1 );
final ColumnReference columnReference = new ColumnReference(
(String) null,
rowNumberColumn.getColumnExpression(),
false,
null,
rowNumberColumn.getJdbcMapping()
);
insertStatement.getTargetColumns().add( columnReference );
targetPathCteColumns.add( rowNumberColumn );
}
@ -743,11 +733,13 @@ public class CteInsertHandler implements InsertHandler {
throw new IllegalStateException( "There must be at least a single root table assignment" );
}
final ConflictClause conflictClause = sqmConverter.visitConflictClause( sqmStatement.getConflictClause() );
final int tableSpan = persister.getTableSpan();
final String[] rootKeyColumns = persister.getKeyColumns( 0 );
final List<CteColumn> keyCteColumns = queryCte.getCteTable().getCteColumns().subList( 0, rootKeyColumns.length );
for ( int i = 0; i < tableSpan; i++ ) {
final String tableExpression = persister.getTableName( i );
for ( int tableIndex = 0; tableIndex < tableSpan; tableIndex++ ) {
final String tableExpression = persister.getTableName( tableIndex );
final TableReference updatingTableReference = updatingTableGroup.getTableReference(
updatingTableGroup.getNavigablePath(),
tableExpression,
@ -758,7 +750,7 @@ public class CteInsertHandler implements InsertHandler {
updatingTableReference,
tableExpression
);
final String[] keyColumns = persister.getKeyColumns( i );
final String[] keyColumns = persister.getKeyColumns( tableIndex );
final List<ColumnReference> returningColumnReferences = new ArrayList<>(
keyColumns.length + ( assignmentList == null ? 0 : assignmentList.size() )
);
@ -766,7 +758,7 @@ public class CteInsertHandler implements InsertHandler {
final QuerySpec insertSelectSpec = new QuerySpec( true );
CteStatement finalCteStatement = null;
final CteTable dmlResultCte;
if ( i == 0 && !assignsId && identifierGenerator.generatedOnExecution() ) {
if ( tableIndex == 0 && !assignsId && identifierGenerator.generatedOnExecution() ) {
// Special handling for identity generation
final String cteTableName = getCteTableName( tableExpression, "base_" );
if ( statement.getCteStatement( cteTableName ) != null ) {
@ -999,11 +991,57 @@ public class CteInsertHandler implements InsertHandler {
}
}
dmlStatement.setSourceSelectStatement( insertSelectSpec );
if ( conflictClause != null ) {
if ( conflictClause.isDoNothing() && conflictClause.getConstraintColumnNames().isEmpty() ) {
// Conflict clauses that use a constraint name and do nothing can just use the conflict clause as it is
handleConflictClause( dmlResultCte, dmlStatement, queryCte, tableIndex, conflictClause, statement );
}
else {
final List<Assignment> compatibleAssignments = getCompatibleAssignments( dmlStatement, conflictClause );
if ( isIdentifierConflictClause( sqmStatement ) ) {
// If the identifier is used in the SqmInsert, use the key columns of the respective table
handleConflictClause(
dmlResultCte,
dmlStatement,
queryCte,
tableIndex,
new ConflictClause(
conflictClause.getConstraintName(),
Arrays.asList( keyColumns ),
compatibleAssignments,
compatibleAssignments.isEmpty() ? null : conflictClause.getPredicate()
),
statement
);
}
else if ( targetColumnsContainAllConstraintColumns( dmlStatement, conflictClause ) ) {
// Also apply the conflict clause if the insert target columns contain the constraint columns
handleConflictClause(
dmlResultCte,
dmlStatement,
queryCte,
tableIndex,
new ConflictClause(
conflictClause.getConstraintName(),
conflictClause.getConstraintColumnNames(),
compatibleAssignments,
compatibleAssignments.isEmpty() ? null : conflictClause.getPredicate()
),
statement
);
}
else {
statement.addCteStatement( new CteStatement( dmlResultCte, dmlStatement ) );
}
}
}
else {
statement.addCteStatement( new CteStatement( dmlResultCte, dmlStatement ) );
}
if ( finalCteStatement != null ) {
statement.addCteStatement( finalCteStatement );
}
if ( i == 0 && !assignsId && identifierGenerator.generatedOnExecution() ) {
if ( tableIndex == 0 && !assignsId && identifierGenerator.generatedOnExecution() ) {
// Special handling for identity generation
statement.addCteStatement( queryCte );
}
@ -1011,6 +1049,319 @@ public class CteInsertHandler implements InsertHandler {
return getCteTableName( rootTableName );
}
private void handleConflictClause(
CteTable dmlResultCte,
InsertSelectStatement insertStatement,
CteStatement queryCte,
int tableIndex,
ConflictClause conflictClause,
CteContainer statement) {
if ( sessionFactory.getJdbcServices().getDialect().supportsConflictClauseForInsertCTE() ) {
insertStatement.setConflictClause( conflictClause );
statement.addCteStatement( new CteStatement( dmlResultCte, insertStatement ) );
}
else {
// Build an exists subquery clause to only insert if no row with a matching constraint column value exists i.e.
// insert into target (c1, c2)
// select e.c1, e.c2 from HTE_target e
// where not exists (select 1 from target excluded where e.c1=excluded.c1 and e.c2=excluded.c2)
final BasicType<Boolean> booleanType = sessionFactory.getNodeBuilder().getBooleanType();
final List<String> constraintColumnNames = conflictClause.getConstraintColumnNames();
final QuerySpec insertQuerySpec = (QuerySpec) insertStatement.getSourceSelectStatement();
final QuerySpec subquery = new QuerySpec( false, 1 );
// This is the table group we use in the subquery to check no row for the given constraint columns exists.
// We name it "excluded" because the predicates we build for this check are reused for the
// check in the update statement below.
// "excluded" is our well known name to refer to data that was not inserted
final TableGroup tableGroup = new StandardTableGroup(
false,
new NavigablePath( "excluded" ),
entityDescriptor,
null,
new NamedTableReference(
insertStatement.getTargetTable().getTableExpression(),
"excluded"
),
null,
sessionFactory
);
subquery.getSelectClause().addSqlSelection(
new SqlSelectionImpl( new QueryLiteral<>( 1, sessionFactory.getNodeBuilder().getIntegerType() ) )
);
subquery.getFromClause().addRoot( tableGroup );
List<String> columnsToMatch;
if ( constraintColumnNames.isEmpty() ) {
// Assume the primary key columns
final AbstractEntityPersister aep = (AbstractEntityPersister) entityDescriptor;
Predicate predicate = buildColumnMatchPredicate(
columnsToMatch = Arrays.asList( aep.getKeyColumns( tableIndex ) ),
insertStatement,
false,
true
);
if ( predicate == null ) {
throw new IllegalArgumentException( "Couldn't infer conflict constraint columns" );
}
subquery.applyPredicate( predicate );
}
else {
columnsToMatch = constraintColumnNames;
subquery.applyPredicate( buildColumnMatchPredicate( constraintColumnNames, insertStatement, true, true ) );
}
insertQuerySpec.applyPredicate( new ExistsPredicate( subquery, true, booleanType ) );
// Emulate the conflict do update clause by creating a separate update CTEs
if ( conflictClause.isDoUpdate() ) {
final TableGroup temporaryTableGroup = insertQuerySpec.getFromClause().getRoots().get( 0 );
final QuerySpec renamingSubquery = new QuerySpec( false, 1 );
final List<String> columnNames = buildCteRenaming(
renamingSubquery,
temporaryTableGroup,
queryCte
);
renamingSubquery.getFromClause().addRoot( temporaryTableGroup );
final QueryPartTableGroup excludedTableGroup = new QueryPartTableGroup(
new NavigablePath( "excluded" ),
null,
new SelectStatement( renamingSubquery ),
"excluded",
columnNames,
false,
false,
sessionFactory
);
final UpdateStatement updateStatement;
if ( sessionFactory.getJdbcServices().getDialect().supportsFromClauseInUpdate() ) {
final FromClause fromClause = new FromClause( 1 );
final TableGroup updateTableGroup = new StandardTableGroup(
false,
new NavigablePath( "updated" ),
entityDescriptor,
null,
insertStatement.getTargetTable(),
null,
sessionFactory
);
fromClause.addRoot( updateTableGroup );
updateStatement = new UpdateStatement(
insertStatement.getTargetTable(),
fromClause,
conflictClause.getAssignments(),
conflictClause.getPredicate(),
insertStatement.getReturningColumns()
);
updateTableGroup.addTableGroupJoin(
new TableGroupJoin(
excludedTableGroup.getNavigablePath(),
SqlAstJoinType.INNER,
excludedTableGroup,
buildColumnMatchPredicate(
columnsToMatch,
insertStatement,
true,
false
)
)
);
}
else {
final List<Assignment> assignments = conflictClause.getAssignments();
final List<ColumnReference> assignmentColumns = new ArrayList<>( assignments.size() );
final QuerySpec updateSubquery = new QuerySpec( false, 1 );
for ( Assignment assignment : assignments ) {
assignmentColumns.add( (ColumnReference) assignment.getAssignable() );
updateSubquery.getSelectClause().addSqlSelection(
new SqlSelectionImpl( assignment.getAssignedValue() )
);
}
updateSubquery.getFromClause().addRoot( excludedTableGroup );
updateSubquery.applyPredicate( buildColumnMatchPredicate(
columnsToMatch,
insertStatement,
true,
false
) );
final QuerySpec matchCteSubquery = new QuerySpec( false, 1 );
matchCteSubquery.getSelectClause().addSqlSelection(
new SqlSelectionImpl( new QueryLiteral<>(
1,
sessionFactory.getNodeBuilder().getIntegerType()
) )
);
matchCteSubquery.getFromClause().addRoot( updateSubquery.getFromClause().getRoots().get( 0 ) );
matchCteSubquery.applyPredicate( updateSubquery.getWhereClauseRestrictions() );
updateStatement = new UpdateStatement(
insertStatement.getTargetTable(),
List.of( new Assignment(
new SqlTuple( assignmentColumns, null ),
new SelectStatement( updateSubquery )
) ),
Predicate.combinePredicates(
new ExistsPredicate( matchCteSubquery, false, booleanType ),
conflictClause.getPredicate()
),
insertStatement.getReturningColumns()
);
}
final CteTable updateCte = dmlResultCte.withName( dmlResultCte.getTableExpression() + "_upd" );
statement.addCteStatement( new CteStatement( updateCte, updateStatement ) );
final CteTable insertCte = dmlResultCte.withName( dmlResultCte.getTableExpression() + "_ins" );
statement.addCteStatement( new CteStatement( insertCte, insertStatement ) );
// Union the update and inserted ids together to be able to determine the effective update count
final List<QueryPart> queryParts = new ArrayList<>( 2 );
final QuerySpec dmlCombinationQ1 = new QuerySpec( false, 1 );
final QuerySpec dmlCombinationQ2 = new QuerySpec( false, 1 );
dmlCombinationQ1.getSelectClause().addSqlSelection( new SqlSelectionImpl( new Star() ) );
dmlCombinationQ2.getSelectClause().addSqlSelection( new SqlSelectionImpl( new Star() ) );
dmlCombinationQ1.getFromClause().addRoot( new CteTableGroup( new NamedTableReference( updateCte.getTableExpression(), "t" ) ) );
dmlCombinationQ2.getFromClause().addRoot( new CteTableGroup( new NamedTableReference( insertCte.getTableExpression(), "t" ) ) );
queryParts.add( dmlCombinationQ1 );
queryParts.add( dmlCombinationQ2 );
final SelectStatement dmlCombinationStatement = new SelectStatement( new QueryGroup( true, SetOperator.UNION_ALL, queryParts ) );
statement.addCteStatement( new CteStatement( dmlResultCte, dmlCombinationStatement ) );
}
else {
statement.addCteStatement( new CteStatement( dmlResultCte, insertStatement ) );
}
}
}
private List<String> buildCteRenaming(
QuerySpec renamingSubquery,
TableGroup temporaryTableGroup,
CteStatement queryCte) {
final List<CteColumn> cteColumns = queryCte.getCteTable().getCteColumns();
for ( CteColumn cteColumn : cteColumns ) {
renamingSubquery.getSelectClause().addSqlSelection(
new SqlSelectionImpl(
new ColumnReference(
temporaryTableGroup.getPrimaryTableReference(),
cteColumn.getColumnExpression(),
cteColumn.getJdbcMapping()
)
)
);
}
final SelectStatement selectStatement = (SelectStatement) queryCte.getCteDefinition();
final QuerySpec querySpec = (QuerySpec) selectStatement.getQueryPart();
final DerivedTableReference tableReference = (DerivedTableReference) querySpec.getFromClause()
.getRoots()
.get( 0 )
.getPrimaryTableReference();
return tableReference.getColumnNames();
}
private Predicate buildColumnMatchPredicate(
List<String> constraintColumnNames,
InsertSelectStatement dmlStatement,
boolean errorIfMissing,
boolean compareAgainstSelectItems) {
final BasicType<Boolean> booleanType = sessionFactory.getNodeBuilder().getBooleanType();
final QuerySpec insertQuerySpec = (QuerySpec) dmlStatement.getSourceSelectStatement();
Predicate predicate = null;
OUTER: for ( String constraintColumnName : constraintColumnNames ) {
final List<ColumnReference> targetColumns = dmlStatement.getTargetColumns();
for ( int i = 0; i < targetColumns.size(); i++ ) {
final ColumnReference columnReference = targetColumns.get( i );
if ( columnReference.getColumnExpression().equals( constraintColumnName ) ) {
if ( compareAgainstSelectItems ) {
predicate = Predicate.combinePredicates(
predicate,
new ComparisonPredicate(
new ColumnReference(
"excluded",
columnReference.getColumnExpression(),
false,
null,
columnReference.getJdbcMapping()
),
ComparisonOperator.EQUAL,
insertQuerySpec.getSelectClause()
.getSqlSelections()
.get( i )
.getExpression(),
booleanType
)
);
}
else {
predicate = Predicate.combinePredicates(
predicate,
new ComparisonPredicate(
columnReference,
ComparisonOperator.EQUAL,
new ColumnReference(
"excluded",
columnReference.getColumnExpression(),
false,
null,
columnReference.getJdbcMapping()
),
booleanType
)
);
}
continue OUTER;
}
}
if ( errorIfMissing ) {
// Should never happen
final List<String> targetColumnNames = targetColumns.stream()
.map( ColumnReference::getColumnExpression )
.collect( Collectors.toList() );
throw new IllegalArgumentException( "Couldn't find conflict constraint column [" + constraintColumnName + "] in insert target columns: " + targetColumnNames );
}
return null;
}
return predicate;
}
private List<Assignment> getCompatibleAssignments(InsertSelectStatement dmlStatement, ConflictClause conflictClause) {
if ( conflictClause.isDoNothing() ) {
return Collections.emptyList();
}
List<Assignment> compatibleAssignments = null;
final List<Assignment> assignments = conflictClause.getAssignments();
for ( Assignment assignment : assignments ) {
for ( ColumnReference targetColumn : dmlStatement.getTargetColumns() ) {
if ( targetColumn.equals( assignment.getAssignable() ) ) {
if ( compatibleAssignments == null ) {
compatibleAssignments = new ArrayList<>( assignments.size() );
}
compatibleAssignments.add( assignment );
break;
}
}
}
return compatibleAssignments == null ? Collections.emptyList() : compatibleAssignments;
}
private boolean isIdentifierConflictClause(SqmInsertStatement<?> sqmStatement) {
final SqmConflictClause<?> conflictClause = sqmStatement.getConflictClause();
assert conflictClause != null;
final List<SqmPath<?>> constraintPaths = conflictClause.getConstraintPaths();
return constraintPaths.size() == 1
&& constraintPaths.get( 0 ).getReferencedPathSource() == sqmStatement.getTarget().getModel().getIdentifierDescriptor();
}
private boolean targetColumnsContainAllConstraintColumns(InsertSelectStatement statement, ConflictClause conflictClause) {
OUTER: for ( String constraintColumnName : conflictClause.getConstraintColumnNames() ) {
for ( ColumnReference targetColumn : statement.getTargetColumns() ) {
if ( targetColumn.getColumnExpression().equals( constraintColumnName ) ) {
continue OUTER;
}
}
return false;
}
return true;
}
protected NamedTableReference resolveUnionTableReference(
TableReference tableReference,
String tableExpression) {

View File

@ -98,15 +98,10 @@ public class CteUpdateHandler extends AbstractCteMutationHandler implements Upda
// visit the set-clause using our special converter, collecting
// information about the assignments
final SqmSetClause setClause = updateStatement.getSetClause();
final List<Assignment> assignments = new ArrayList<>( setClause.getAssignments().size() );
sqmConverter.visitSetClause(
setClause,
assignments::add,
(sqmParam, mappingType, jdbcParameters) -> {
parameterResolutions.put( sqmParam, jdbcParameters );
final List<Assignment> assignments = sqmConverter.visitSetClause( setClause );
for ( Map.Entry<SqmParameter<?>, List<List<JdbcParameter>>> entry : sqmConverter.getJdbcParamsBySqmParam().entrySet() ) {
parameterResolutions.put( entry.getKey(), entry.getValue().get( entry.getValue().size() - 1 ) );
}
);
sqmConverter.addVersionedAssignment( assignments::add, updateStatement );
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -11,16 +11,15 @@ import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.InListPredicate;
@ -45,19 +44,42 @@ import org.hibernate.sql.exec.spi.ExecutionContext;
* @author Steve Ebersole
*/
public class InPredicateRestrictionProducer implements MatchingIdRestrictionProducer {
@Override
public List<Expression> produceIdExpressionList(List<Object> idsAndFks, EntityMappingType entityDescriptor) {
final List<Expression> inListExpressions = new ArrayList<>( idsAndFks.size() );
final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping();
if ( identifierMapping instanceof BasicValuedModelPart ) {
final BasicValuedModelPart basicValuedModelPart = (BasicValuedModelPart) identifierMapping;
for ( int i = 0; i < idsAndFks.size(); i++ ) {
inListExpressions.add( new QueryLiteral<>( idsAndFks.get( i ), basicValuedModelPart ) );
}
}
else {
final int jdbcTypeCount = identifierMapping.getJdbcTypeCount();
for ( int i = 0; i < idsAndFks.size(); i++ ) {
final Object[] id = (Object[]) idsAndFks.get( i );
final List<Expression> tupleElements = new ArrayList<>( jdbcTypeCount );
inListExpressions.add( new SqlTuple( tupleElements, identifierMapping ) );
identifierMapping.forEachJdbcType( (index, jdbcMapping) -> {
tupleElements.add( new QueryLiteral<>( id[index], (BasicValuedMapping) jdbcMapping ) );
} );
}
}
return inListExpressions;
}
@Override
public InListPredicate produceRestriction(
List<?> matchingIdValues,
List<Expression> matchingIdValueExpressions,
EntityMappingType entityDescriptor,
int valueIndex,
ModelPart valueModelPart,
TableReference mutatingTableReference,
Supplier<Consumer<SelectableConsumer>> columnsToMatchVisitationSupplier,
ExecutionContext executionContext) {
assert matchingIdValues != null;
assert ! matchingIdValues.isEmpty();
final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory();
assert matchingIdValueExpressions != null;
assert ! matchingIdValueExpressions.isEmpty();
final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping();
final int idColumnCount = identifierMapping.getJdbcTypeCount();
@ -76,45 +98,27 @@ public class InPredicateRestrictionProducer implements MatchingIdRestrictionProd
null,
basicIdMapping.getJdbcMapping()
);
predicate = new InListPredicate( inFixture );
matchingIdValues.forEach(
matchingId -> predicate.addExpression( new JdbcLiteral<>( matchingId, basicIdMapping.getJdbcMapping() ) )
);
predicate = new InListPredicate( inFixture, matchingIdValueExpressions );
}
else {
final List<ColumnReference> columnReferences = new ArrayList<>( idColumnCount );
final List<JdbcMapping> jdbcMappings = new ArrayList<>( idColumnCount );
identifierMapping.forEachSelectable(
(columnIndex, selection) -> {
final SelectableConsumer selectableConsumer = (columnIndex, selection) -> {
columnReferences.add(
new ColumnReference(
mutatingTableReference,
selection
)
);
jdbcMappings.add( selection.getJdbcMapping() );
};
if ( columnsToMatchVisitationSupplier == null ) {
identifierMapping.forEachSelectable( selectableConsumer );
}
else {
columnsToMatchVisitationSupplier.get().accept( selectableConsumer );
}
);
final Expression inFixture = new SqlTuple( columnReferences, identifierMapping );
predicate = new InListPredicate( inFixture );
matchingIdValues.forEach(
matchingId -> {
assert matchingId instanceof Object[];
final Object[] matchingIdParts = (Object[]) matchingId;
final List<JdbcLiteral<?>> tupleParts = new ArrayList<>( idColumnCount );
for ( int p = 0; p < matchingIdParts.length; p++ ) {
tupleParts.add(
new JdbcLiteral<>( matchingIdParts[p],jdbcMappings.get( p ) )
);
}
predicate.addExpression( new SqlTuple( tupleParts, identifierMapping ) );
}
);
predicate = new InListPredicate( inFixture, matchingIdValueExpressions );
}
return predicate;

Some files were not shown because too many files have changed in this diff Show More