HHH-15192 - Remove support for Sybase ASE versions older than 16

Signed-off-by: Jan Schatteman <jschatte@redhat.com>
This commit is contained in:
Jan Schatteman 2022-06-29 20:57:17 +02:00 committed by Christian Beikov
parent b57f4a6b12
commit 11fb9440ef
10 changed files with 1771 additions and 69 deletions

View File

@ -0,0 +1,724 @@
/*
* 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.community.dialect;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Map;
import org.hibernate.LockOptions;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.RowLockStrategy;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.TopLimitHandler;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
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.query.sqm.IntervalType;
import org.hibernate.query.sqm.TemporalUnit;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.sql.ForUpdateFragment;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.TimestampJdbcType;
import org.hibernate.type.descriptor.jdbc.TinyIntJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
import jakarta.persistence.TemporalType;
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
import static org.hibernate.type.SqlTypes.BIGINT;
import static org.hibernate.type.SqlTypes.BOOLEAN;
import static org.hibernate.type.SqlTypes.DATE;
import static org.hibernate.type.SqlTypes.TIME;
import static org.hibernate.type.SqlTypes.TIMESTAMP;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
/**
* A {@linkplain Dialect SQL dialect} for Sybase Adaptive Server Enterprise 11.9 and above.
*/
public class SybaseASELegacyDialect extends SybaseLegacyDialect {
private final SizeStrategy sizeStrategy = new SizeStrategyImpl() {
@Override
public Size resolveSize(
JdbcType jdbcType,
JavaType<?> javaType,
Integer precision,
Integer scale,
Long length) {
switch ( jdbcType.getDefaultSqlTypeCode() ) {
case Types.FLOAT:
// Sybase ASE allows FLOAT with a precision up to 48
if ( precision != null ) {
return Size.precision( Math.min( Math.max( precision, 1 ), 48 ) );
}
}
return super.resolveSize( jdbcType, javaType, precision, scale, length );
}
};
private final boolean ansiNull;
public SybaseASELegacyDialect() {
this( DatabaseVersion.make( 11 ) );
}
public SybaseASELegacyDialect(DatabaseVersion version) {
super(version);
ansiNull = false;
}
public SybaseASELegacyDialect(DialectResolutionInfo info) {
super(info);
ansiNull = isAnsiNull( info.getDatabaseMetadata() );
}
@Override
protected String columnType(int sqlTypeCode) {
switch ( sqlTypeCode ) {
case BOOLEAN:
// On Sybase ASE, the 'bit' type cannot be null,
// and cannot have indexes (while we don't use
// tinyint to store signed bytes, we can use it
// to store boolean values)
return "tinyint";
case BIGINT:
// Sybase ASE didn't introduce 'bigint' until version 15.0
return getVersion().isBefore( 15 ) ? "numeric(19,0)" : super.columnType( sqlTypeCode );
case DATE:
return getVersion().isSameOrAfter( 12 ) ? "date" : super.columnType( sqlTypeCode );
case TIME:
return getVersion().isSameOrAfter( 12 ) ? "time" : super.columnType( sqlTypeCode );
}
return super.columnType( sqlTypeCode );
}
@Override
protected void registerColumnTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.registerColumnTypes( typeContributions, serviceRegistry );
final DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry();
// According to Wikipedia bigdatetime and bigtime were added in 15.5
// But with jTDS we can't use them as the driver can't handle the types
if ( getVersion().isSameOrAfter( 15, 5 ) && !jtdsDriver ) {
ddlTypeRegistry.addDescriptor(
CapacityDependentDdlType.builder( DATE, "bigdatetime", "bigdatetime", this )
.withTypeCapacity( 3, "datetime" )
.build()
);
ddlTypeRegistry.addDescriptor(
CapacityDependentDdlType.builder( TIME, "bigdatetime", "bigdatetime", this )
.withTypeCapacity( 3, "datetime" )
.build()
);
ddlTypeRegistry.addDescriptor(
CapacityDependentDdlType.builder( TIMESTAMP, "bigdatetime", "bigdatetime", this )
.withTypeCapacity( 3, "datetime" )
.build()
);
ddlTypeRegistry.addDescriptor(
CapacityDependentDdlType.builder( TIMESTAMP_WITH_TIMEZONE, "bigdatetime", "bigdatetime", this )
.withTypeCapacity( 3, "datetime" )
.build()
);
}
}
@Override
public int getMaxVarcharLength() {
// the maximum length of a VARCHAR or VARBINARY
// column depends on the page size and ASE version
// and is actually a limit on the whole row length,
// not the individual column length -- anyway, the
// largest possible page size is 16k, so that's a
// hard upper limit
return 16_384;
}
private static boolean isAnsiNull(DatabaseMetaData databaseMetaData) {
if ( databaseMetaData != null ) {
try (java.sql.Statement s = databaseMetaData.getConnection().createStatement() ) {
final ResultSet rs = s.executeQuery( "SELECT @@options" );
if ( rs.next() ) {
final byte[] optionBytes = rs.getBytes( 1 );
// By trial and error, enabling and disabling ansinull revealed that this bit is the indicator
return ( optionBytes[4] & 2 ) == 2;
}
}
catch (SQLException ex) {
// Ignore
}
}
return false;
}
@Override
public boolean isAnsiNullOn() {
return ansiNull;
}
@Override
public int getFloatPrecision() {
return 15;
}
@Override
public int getDoublePrecision() {
return 48;
}
@Override
public SizeStrategy getSizeStrategy() {
return sizeStrategy;
}
@Override
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
return new StandardSqlAstTranslatorFactory() {
@Override
protected <T extends JdbcOperation> SqlAstTranslator<T> buildTranslator(
SessionFactoryImplementor sessionFactory, Statement statement) {
return new SybaseASELegacySqlAstTranslator<>( sessionFactory, statement );
}
};
}
/**
* The Sybase ASE {@code BIT} type does not allow
* null values, so we don't use it.
*
* @return false
*/
@Override
public boolean supportsBitType() {
return false;
}
@Override
public boolean supportsDistinctFromPredicate() {
return getVersion().isSameOrAfter( 16, 3 );
}
@Override
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.contributeTypes( typeContributions, serviceRegistry );
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration()
.getJdbcTypeRegistry();
jdbcTypeRegistry.addDescriptor( Types.BOOLEAN, TinyIntJdbcType.INSTANCE );
// At least the jTDS driver does not support this type code
if ( jtdsDriver ) {
jdbcTypeRegistry.addDescriptor( Types.TIMESTAMP_WITH_TIMEZONE, TimestampJdbcType.INSTANCE );
}
}
@Override
public int resolveSqlTypeLength(
String columnTypeName,
int jdbcTypeCode,
int precision,
int scale,
int displaySize) {
// Sybase ASE reports the "actual" precision in the display size
switch ( jdbcTypeCode ) {
case Types.REAL:
case Types.DOUBLE:
return displaySize;
}
return super.resolveSqlTypeLength( columnTypeName, jdbcTypeCode, precision, scale, displaySize );
}
@Override
public String currentDate() {
return "current_date()";
}
@Override
public String currentTime() {
return "current_time()";
}
@Override
public String currentTimestamp() {
return "current_bigdatetime()";
}
@Override
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
//TODO!!
switch ( unit ) {
case NANOSECOND:
case NATIVE:
// If the driver or database do not support bigdatetime and bigtime types,
// we try to operate on milliseconds instead
if ( getVersion().isBefore( 15, 5 ) || jtdsDriver ) {
return "dateadd(millisecond,?2/1000000,?3)";
}
else {
return "dateadd(mcs,?2/1000,?3)";
}
default:
return "dateadd(?1,?2,?3)";
}
}
@Override
public long getFractionalSecondPrecisionInNanos() {
// If the database does not support bigdatetime and bigtime types,
// we try to operate on milliseconds instead
if ( getVersion().isBefore( 15, 5 ) ) {
return 1_000_000;
}
else {
return 1_000;
}
}
@Override
public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) {
//TODO!!
switch ( unit ) {
case NANOSECOND:
case NATIVE:
// If the database does not support bigdatetime and bigtime types,
// we try to operate on milliseconds instead
if ( getVersion().isBefore( 15, 5 ) ) {
return "cast(datediff(ms,?2,?3) as numeric(21))";
}
else {
return "cast(datediff(mcs,cast(?2 as bigdatetime),cast(?3 as bigdatetime)) as numeric(21))";
}
default:
return "datediff(?1,?2,?3)";
}
}
@Override
protected void registerDefaultKeywords() {
super.registerDefaultKeywords();
registerKeyword( "add" );
registerKeyword( "all" );
registerKeyword( "alter" );
registerKeyword( "and" );
registerKeyword( "any" );
registerKeyword( "arith_overflow" );
registerKeyword( "as" );
registerKeyword( "asc" );
registerKeyword( "at" );
registerKeyword( "authorization" );
registerKeyword( "avg" );
registerKeyword( "begin" );
registerKeyword( "between" );
registerKeyword( "break" );
registerKeyword( "browse" );
registerKeyword( "bulk" );
registerKeyword( "by" );
registerKeyword( "cascade" );
registerKeyword( "case" );
registerKeyword( "char_convert" );
registerKeyword( "check" );
registerKeyword( "checkpoint" );
registerKeyword( "close" );
registerKeyword( "clustered" );
registerKeyword( "coalesce" );
registerKeyword( "commit" );
registerKeyword( "compute" );
registerKeyword( "confirm" );
registerKeyword( "connect" );
registerKeyword( "constraint" );
registerKeyword( "continue" );
registerKeyword( "controlrow" );
registerKeyword( "convert" );
registerKeyword( "count" );
registerKeyword( "count_big" );
registerKeyword( "create" );
registerKeyword( "current" );
registerKeyword( "cursor" );
registerKeyword( "database" );
registerKeyword( "dbcc" );
registerKeyword( "deallocate" );
registerKeyword( "declare" );
registerKeyword( "decrypt" );
registerKeyword( "default" );
registerKeyword( "delete" );
registerKeyword( "desc" );
registerKeyword( "determnistic" );
registerKeyword( "disk" );
registerKeyword( "distinct" );
registerKeyword( "drop" );
registerKeyword( "dummy" );
registerKeyword( "dump" );
registerKeyword( "else" );
registerKeyword( "encrypt" );
registerKeyword( "end" );
registerKeyword( "endtran" );
registerKeyword( "errlvl" );
registerKeyword( "errordata" );
registerKeyword( "errorexit" );
registerKeyword( "escape" );
registerKeyword( "except" );
registerKeyword( "exclusive" );
registerKeyword( "exec" );
registerKeyword( "execute" );
registerKeyword( "exist" );
registerKeyword( "exit" );
registerKeyword( "exp_row_size" );
registerKeyword( "external" );
registerKeyword( "fetch" );
registerKeyword( "fillfactor" );
registerKeyword( "for" );
registerKeyword( "foreign" );
registerKeyword( "from" );
registerKeyword( "goto" );
registerKeyword( "grant" );
registerKeyword( "group" );
registerKeyword( "having" );
registerKeyword( "holdlock" );
registerKeyword( "identity" );
registerKeyword( "identity_gap" );
registerKeyword( "identity_start" );
registerKeyword( "if" );
registerKeyword( "in" );
registerKeyword( "index" );
registerKeyword( "inout" );
registerKeyword( "insensitive" );
registerKeyword( "insert" );
registerKeyword( "install" );
registerKeyword( "intersect" );
registerKeyword( "into" );
registerKeyword( "is" );
registerKeyword( "isolation" );
registerKeyword( "jar" );
registerKeyword( "join" );
registerKeyword( "key" );
registerKeyword( "kill" );
registerKeyword( "level" );
registerKeyword( "like" );
registerKeyword( "lineno" );
registerKeyword( "load" );
registerKeyword( "lock" );
registerKeyword( "materialized" );
registerKeyword( "max" );
registerKeyword( "max_rows_per_page" );
registerKeyword( "min" );
registerKeyword( "mirror" );
registerKeyword( "mirrorexit" );
registerKeyword( "modify" );
registerKeyword( "national" );
registerKeyword( "new" );
registerKeyword( "noholdlock" );
registerKeyword( "nonclustered" );
registerKeyword( "nonscrollable" );
registerKeyword( "non_sensitive" );
registerKeyword( "not" );
registerKeyword( "null" );
registerKeyword( "nullif" );
registerKeyword( "numeric_truncation" );
registerKeyword( "of" );
registerKeyword( "off" );
registerKeyword( "offsets" );
registerKeyword( "on" );
registerKeyword( "once" );
registerKeyword( "online" );
registerKeyword( "only" );
registerKeyword( "open" );
registerKeyword( "option" );
registerKeyword( "or" );
registerKeyword( "order" );
registerKeyword( "out" );
registerKeyword( "output" );
registerKeyword( "over" );
registerKeyword( "artition" );
registerKeyword( "perm" );
registerKeyword( "permanent" );
registerKeyword( "plan" );
registerKeyword( "prepare" );
registerKeyword( "primary" );
registerKeyword( "print" );
registerKeyword( "privileges" );
registerKeyword( "proc" );
registerKeyword( "procedure" );
registerKeyword( "processexit" );
registerKeyword( "proxy_table" );
registerKeyword( "public" );
registerKeyword( "quiesce" );
registerKeyword( "raiserror" );
registerKeyword( "read" );
registerKeyword( "readpast" );
registerKeyword( "readtext" );
registerKeyword( "reconfigure" );
registerKeyword( "references" );
registerKeyword( "remove" );
registerKeyword( "reorg" );
registerKeyword( "replace" );
registerKeyword( "replication" );
registerKeyword( "reservepagegap" );
registerKeyword( "return" );
registerKeyword( "returns" );
registerKeyword( "revoke" );
registerKeyword( "role" );
registerKeyword( "rollback" );
registerKeyword( "rowcount" );
registerKeyword( "rows" );
registerKeyword( "rule" );
registerKeyword( "save" );
registerKeyword( "schema" );
registerKeyword( "scroll" );
registerKeyword( "scrollable" );
registerKeyword( "select" );
registerKeyword( "semi_sensitive" );
registerKeyword( "set" );
registerKeyword( "setuser" );
registerKeyword( "shared" );
registerKeyword( "shutdown" );
registerKeyword( "some" );
registerKeyword( "statistics" );
registerKeyword( "stringsize" );
registerKeyword( "stripe" );
registerKeyword( "sum" );
registerKeyword( "syb_identity" );
registerKeyword( "syb_restree" );
registerKeyword( "syb_terminate" );
registerKeyword( "top" );
registerKeyword( "table" );
registerKeyword( "temp" );
registerKeyword( "temporary" );
registerKeyword( "textsize" );
registerKeyword( "to" );
registerKeyword( "tracefile" );
registerKeyword( "tran" );
registerKeyword( "transaction" );
registerKeyword( "trigger" );
registerKeyword( "truncate" );
registerKeyword( "tsequal" );
registerKeyword( "union" );
registerKeyword( "unique" );
registerKeyword( "unpartition" );
registerKeyword( "update" );
registerKeyword( "use" );
registerKeyword( "user" );
registerKeyword( "user_option" );
registerKeyword( "using" );
registerKeyword( "values" );
registerKeyword( "varying" );
registerKeyword( "view" );
registerKeyword( "waitfor" );
registerKeyword( "when" );
registerKeyword( "where" );
registerKeyword( "while" );
registerKeyword( "with" );
registerKeyword( "work" );
registerKeyword( "writetext" );
registerKeyword( "xmlextract" );
registerKeyword( "xmlparse" );
registerKeyword( "xmltest" );
registerKeyword( "xmlvalidate" );
}
// Overridden informational metadata ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
public boolean supportsCascadeDelete() {
return false;
}
@Override
public int getMaxAliasLength() {
return 30;
}
@Override
public int getMaxIdentifierLength() {
return 255;
}
@Override
public boolean supportsValuesListForInsert() {
return false;
}
@Override
public boolean supportsLockTimeouts() {
return false;
}
@Override
public boolean supportsOrderByInSubquery() {
return false;
}
@Override
public boolean supportsUnionInSubquery() {
// At least not according to HHH-3637
return false;
}
@Override
public boolean supportsPartitionBy() {
return false;
}
@Override
public String getTableTypeString() {
//HHH-7298 I don't know if this would break something or cause some side affects
//but it is required to use 'select for update'
return getVersion().isBefore( 15, 7 ) ? super.getTableTypeString() : " lock datarows";
}
@Override
public boolean supportsExpectedLobUsagePattern() {
// Earlier Sybase did not support LOB locators at all
return getVersion().isSameOrAfter( 15, 7 );
}
@Override
public boolean supportsLobValueChangePropagation() {
return false;
}
@Override
public RowLockStrategy getWriteRowLockStrategy() {
return getVersion().isSameOrAfter( 15, 7 ) ? RowLockStrategy.COLUMN : RowLockStrategy.TABLE;
}
@Override
public String getForUpdateString() {
return getVersion().isBefore( 15, 7 ) ? "" : " for update";
}
@Override
public String getForUpdateString(String aliases) {
return getVersion().isBefore( 15, 7 )
? ""
: getForUpdateString() + " of " + aliases;
}
@Override
public String appendLockHint(LockOptions mode, String tableName) {
//TODO: is this really necessary??!
return getVersion().isBefore( 15, 7 ) ? super.appendLockHint( mode, tableName ) : tableName;
}
@Override
public String applyLocksToSql(String sql, LockOptions aliasedLockOptions, Map<String, String[]> keyColumnNames) {
//TODO: is this really correct?
return getVersion().isBefore( 15, 7 )
? super.applyLocksToSql( sql, aliasedLockOptions, keyColumnNames )
: sql + new ForUpdateFragment( this, aliasedLockOptions, keyColumnNames ).toFragmentString();
}
@Override
public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
return EXTRACTOR;
}
/**
* Constraint-name extractor for Sybase ASE constraint violation exceptions.
* Orginally contributed by Denny Bartelt.
*/
private static final ViolatedConstraintNameExtractor EXTRACTOR =
new TemplatedViolatedConstraintNameExtractor( sqle -> {
final int errorCode = JdbcExceptionHelper.extractErrorCode( sqle );
switch ( JdbcExceptionHelper.extractSqlState( sqle ) ) {
// UNIQUE VIOLATION
case "S1000":
if (2601 == errorCode) {
return extractUsingTemplate( "with unique index '", "'", sqle.getMessage() );
}
break;
case "23000":
if (546 == errorCode) {
// 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
default:
return null;
}
return null;
} );
@Override
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
if ( getVersion().isBefore( 15, 7 ) ) {
return null;
}
return (sqlException, message, sql) -> {
final String sqlState = JdbcExceptionHelper.extractSqlState( sqlException );
final int errorCode = JdbcExceptionHelper.extractErrorCode( sqlException );
switch ( sqlState ) {
case "JZ0TO":
case "JZ006":
throw new LockTimeoutException( message, sqlException, sql );
case "S1000":
switch ( errorCode ) {
case 515:
// Attempt to insert NULL value into column; column does not allow nulls.
case 2601:
// Unique constraint violation
final String constraintName = getViolatedConstraintNameExtractor().extractConstraintName( sqlException );
return new ConstraintViolationException( message, sqlException, sql, constraintName );
}
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 );
}
break;
}
return null;
};
}
@Override
public LimitHandler getLimitHandler() {
if ( getVersion().isBefore( 12, 5 ) ) {
//support for SELECT TOP was introduced in Sybase ASE 12.5.3
return super.getLimitHandler();
}
return new TopLimitHandler(false);
}
}

View File

@ -0,0 +1,403 @@
/*
* 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.community.dialect;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.sqm.ComparisonOperator;
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.Statement;
import org.hibernate.sql.ast.tree.cte.CteStatement;
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.QueryLiteral;
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.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.UnionTableReference;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
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.exec.spi.JdbcOperation;
/**
* A SQL AST translator for Sybase ASE.
*
* @author Christian Beikov
*/
public class SybaseASELegacySqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstTranslator<T> {
public SybaseASELegacySqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) {
super( sessionFactory, statement );
}
// Sybase ASE does not allow CASE expressions where all result arms contain plain parameters.
// At least one result arm must provide some type context for inference,
// so we cast the first result arm if we encounter this condition
@Override
protected void visitAnsiCaseSearchedExpression(
CaseSearchedExpression caseSearchedExpression,
Consumer<Expression> resultRenderer) {
if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( caseSearchedExpression ) ) {
final List<CaseSearchedExpression.WhenFragment> whenFragments = caseSearchedExpression.getWhenFragments();
final Expression firstResult = whenFragments.get( 0 ).getResult();
super.visitAnsiCaseSearchedExpression(
caseSearchedExpression,
e -> {
if ( e == firstResult ) {
renderCasted( e );
}
else {
resultRenderer.accept( e );
}
}
);
}
else {
super.visitAnsiCaseSearchedExpression( caseSearchedExpression, resultRenderer );
}
}
@Override
protected void visitAnsiCaseSimpleExpression(
CaseSimpleExpression caseSimpleExpression,
Consumer<Expression> resultRenderer) {
if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( caseSimpleExpression ) ) {
final List<CaseSimpleExpression.WhenFragment> whenFragments = caseSimpleExpression.getWhenFragments();
final Expression firstResult = whenFragments.get( 0 ).getResult();
super.visitAnsiCaseSimpleExpression(
caseSimpleExpression,
e -> {
if ( e == firstResult ) {
renderCasted( e );
}
else {
resultRenderer.accept( e );
}
}
);
}
else {
super.visitAnsiCaseSimpleExpression( caseSimpleExpression, resultRenderer );
}
}
@Override
protected boolean renderNamedTableReference(NamedTableReference tableReference, LockMode lockMode) {
super.renderNamedTableReference( tableReference, lockMode );
if ( getDialect().getVersion().isBefore( 15, 7 ) ) {
if ( LockMode.READ.lessThan( lockMode ) ) {
appendSql( " holdlock" );
}
return true;
}
return false;
}
@Override
protected void renderTableGroupJoin(TableGroupJoin tableGroupJoin, List<TableGroupJoin> tableGroupJoinCollector) {
if ( tableGroupJoin.getJoinType() == SqlAstJoinType.CROSS ) {
appendSql( ", " );
}
else {
appendSql( WHITESPACE );
appendSql( tableGroupJoin.getJoinType().getText() );
appendSql( "join " );
}
final Predicate predicate;
if ( tableGroupJoin.getPredicate() == null ) {
if ( tableGroupJoin.getJoinType() == SqlAstJoinType.CROSS ) {
predicate = null;
}
else {
predicate = new BooleanExpressionPredicate( new QueryLiteral<>( true, getBooleanType() ) );
}
}
else {
predicate = tableGroupJoin.getPredicate();
}
if ( predicate != null && !predicate.isEmpty() ) {
renderTableGroup( tableGroupJoin.getJoinedGroup(), predicate, tableGroupJoinCollector );
}
else {
renderTableGroup( tableGroupJoin.getJoinedGroup(), null, tableGroupJoinCollector );
}
}
@Override
protected void renderForUpdateClause(QuerySpec querySpec, ForUpdateClause forUpdateClause) {
if ( getDialect().getVersion().isBefore( 15, 7 ) ) {
return;
}
super.renderForUpdateClause( querySpec, forUpdateClause );
}
@Override
protected void renderSearchClause(CteStatement cte) {
// Sybase ASE does not support this, but it's just a hint anyway
}
@Override
protected void renderCycleClause(CteStatement cte) {
// Sybase ASE does not support this, but it can be emulated
}
@Override
protected void visitSqlSelections(SelectClause selectClause) {
if ( supportsTopClause() ) {
renderTopClause( (QuerySpec) getQueryPartStack().getCurrent(), true, false );
}
super.visitSqlSelections( selectClause );
}
@Override
protected void renderFetchPlusOffsetExpression(
Expression fetchClauseExpression,
Expression offsetClauseExpression,
int offset) {
renderFetchPlusOffsetExpressionAsLiteral( fetchClauseExpression, offsetClauseExpression, offset );
}
@Override
public void visitQueryGroup(QueryGroup queryGroup) {
if ( queryGroup.hasSortSpecifications() || queryGroup.hasOffsetOrFetchClause() ) {
appendSql( "select " );
renderTopClause(
queryGroup.getOffsetClauseExpression(),
queryGroup.getFetchClauseExpression(),
queryGroup.getFetchClauseType(),
true,
false
);
appendSql( "* from (" );
renderQueryGroup( queryGroup, false );
appendSql( ") grp_(c0" );
// Sybase doesn't have implicit names for non-column select expressions, so we need to assign names
final int itemCount = queryGroup.getFirstQuerySpec().getSelectClause().getSqlSelections().size();
for (int i = 1; i < itemCount; i++) {
appendSql( ",c" );
appendSql( i );
}
appendSql( ')' );
visitOrderBy( queryGroup.getSortSpecifications() );
}
else {
super.visitQueryGroup( queryGroup );
}
}
@Override
public void visitOffsetFetchClause(QueryPart queryPart) {
assertRowsOnlyFetchClauseType( queryPart );
if ( !queryPart.isRoot() && queryPart.hasOffsetOrFetchClause() ) {
if ( queryPart.getFetchClauseExpression() != null && !supportsTopClause() || queryPart.getOffsetClauseExpression() != null ) {
throw new IllegalArgumentException( "Can't emulate offset fetch clause in subquery" );
}
}
}
@Override
protected void renderFetchExpression(Expression fetchExpression) {
if ( supportsParameterOffsetFetchExpression() ) {
super.renderFetchExpression( fetchExpression );
}
else {
renderExpressionAsLiteral( fetchExpression, getJdbcParameterBindings() );
}
}
@Override
protected void renderOffsetExpression(Expression offsetExpression) {
if ( supportsParameterOffsetFetchExpression() ) {
super.renderOffsetExpression( offsetExpression );
}
else {
renderExpressionAsLiteral( offsetExpression, getJdbcParameterBindings() );
}
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
// I think intersect is only supported in 16.0 SP3
if ( getDialect().isAnsiNullOn() ) {
if ( supportsDistinctFromPredicate() ) {
renderComparisonEmulateIntersect( lhs, operator, rhs );
}
else {
renderComparisonEmulateCase( lhs, operator, rhs );
}
}
else {
// The ansinull setting only matters if using a parameter or literal and the eq operator according to the docs
// http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc32300.1570/html/sqlug/sqlug89.htm
boolean rhsNotNullPredicate =
lhs instanceof Literal
|| isParameter( lhs );
boolean lhsNotNullPredicate =
rhs instanceof Literal
|| isParameter( rhs );
if ( rhsNotNullPredicate || lhsNotNullPredicate ) {
lhs.accept( this );
switch ( operator ) {
case DISTINCT_FROM:
appendSql( "<>" );
break;
case NOT_DISTINCT_FROM:
appendSql( '=' );
break;
case LESS_THAN:
case GREATER_THAN:
case LESS_THAN_OR_EQUAL:
case GREATER_THAN_OR_EQUAL:
// These operators are not affected by ansinull=off
lhsNotNullPredicate = false;
rhsNotNullPredicate = false;
default:
appendSql( operator.sqlText() );
break;
}
rhs.accept( this );
if ( lhsNotNullPredicate ) {
appendSql( " and " );
lhs.accept( this );
appendSql( " is not null" );
}
if ( rhsNotNullPredicate ) {
appendSql( " and " );
rhs.accept( this );
appendSql( " is not null" );
}
}
else {
if ( supportsDistinctFromPredicate() ) {
renderComparisonEmulateIntersect( lhs, operator, rhs );
}
else {
renderComparisonEmulateCase( lhs, operator, rhs );
}
}
}
}
@Override
protected boolean supportsIntersect() {
// At least the version that
return false;
}
@Override
protected void renderSelectTupleComparison(
List<SqlSelection> lhsExpressions,
SqlTuple tuple,
ComparisonOperator operator) {
emulateSelectTupleComparison( lhsExpressions, tuple.getExpressions(), operator, true );
}
@Override
protected void renderPartitionItem(Expression expression) {
if ( expression instanceof Literal ) {
// Note that this depends on the SqmToSqlAstConverter to add a dummy table group
appendSql( "dummy_.x" );
}
else if ( expression instanceof Summarization ) {
// This could theoretically be emulated by rendering all grouping variations of the query and
// connect them via union all but that's probably pretty inefficient and would have to happen
// on the query spec level
throw new UnsupportedOperationException( "Summarization is not supported by DBMS" );
}
else {
expression.accept( this );
}
}
@Override
public void visitColumnReference(ColumnReference columnReference) {
final String dmlTargetTableAlias = getDmlTargetTableAlias();
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 ) {
columnReference.appendReadExpression( this );
}
// 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" )
);
}
else {
appendSql( getCurrentDmlStatement().getTargetTable().getTableExpression() );
appendSql( '.' );
appendSql( columnReference.getColumnExpression() );
}
}
else {
columnReference.appendReadExpression( this );
}
}
@Override
protected boolean needsRowsToSkip() {
return true;
}
@Override
protected boolean needsMaxRows() {
return !supportsTopClause();
}
@Override
protected boolean supportsRowValueConstructorSyntax() {
return false;
}
@Override
protected boolean supportsRowValueConstructorSyntaxInInList() {
return false;
}
@Override
protected boolean supportsRowValueConstructorSyntaxInQuantifiedPredicates() {
return false;
}
@Override
protected String getFromDual() {
return " from (select 1) dual(c1)";
}
private boolean supportsTopClause() {
return getDialect().getVersion().isSameOrAfter( 12, 5 );
}
private boolean supportsParameterOffsetFetchExpression() {
return false;
}
}

View File

@ -0,0 +1,335 @@
/*
* 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.community.dialect;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.sql.Types;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.dialect.AbstractTransactSQLDialect;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.NationalizationSupport;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.function.CountFunction;
import org.hibernate.dialect.function.IntegralTimestampaddFunction;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.jdbc.env.spi.IdentifierCaseStrategy;
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.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.query.sqm.CastType;
import org.hibernate.query.sqm.IntervalType;
import org.hibernate.query.sqm.TemporalUnit;
import org.hibernate.query.sqm.TrimSpec;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.sql.SqmTranslator;
import org.hibernate.query.sqm.sql.SqmTranslatorFactory;
import org.hibernate.query.sqm.sql.StandardSqmTranslatorFactory;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.type.JavaObjectType;
import org.hibernate.type.descriptor.jdbc.BlobJdbcType;
import org.hibernate.type.descriptor.jdbc.ClobJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.ObjectNullAsNullTypeJdbcType;
import org.hibernate.type.descriptor.jdbc.SmallIntJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import jakarta.persistence.TemporalType;
/**
* Superclass for all Sybase dialects.
*
* @author Brett Meyer
*/
public class SybaseLegacyDialect extends AbstractTransactSQLDialect {
protected final boolean jtdsDriver;
//All Sybase dialects share an IN list size limit.
private static final int PARAM_LIST_SIZE_LIMIT = 250000;
public SybaseLegacyDialect() {
this( DatabaseVersion.make( 11, 0 ) );
}
public SybaseLegacyDialect(DatabaseVersion version) {
super(version);
jtdsDriver = true;
}
public SybaseLegacyDialect(DialectResolutionInfo info) {
super(info);
jtdsDriver = info.getDriverName() != null
&& info.getDriverName().contains( "jTDS" );
}
@Override
public JdbcType resolveSqlTypeDescriptor(
String columnTypeName,
int jdbcTypeCode,
int precision,
int scale,
JdbcTypeRegistry jdbcTypeRegistry) {
switch ( jdbcTypeCode ) {
case Types.NUMERIC:
case Types.DECIMAL:
if ( precision == 19 && scale == 0 ) {
return jdbcTypeRegistry.getDescriptor( Types.BIGINT );
}
case Types.TINYINT:
if ( jtdsDriver ) {
return jdbcTypeRegistry.getDescriptor( Types.SMALLINT );
}
}
return super.resolveSqlTypeDescriptor(
columnTypeName,
jdbcTypeCode,
precision,
scale,
jdbcTypeRegistry
);
}
@Override
public SqmTranslatorFactory getSqmTranslatorFactory() {
return new StandardSqmTranslatorFactory() {
@Override
public SqmTranslator<SelectStatement> createSelectTranslator(
SqmSelectStatement<?> sqmSelectStatement,
QueryOptions queryOptions,
DomainParameterXref domainParameterXref,
QueryParameterBindings domainParameterBindings,
LoadQueryInfluencers loadQueryInfluencers,
SqlAstCreationContext creationContext,
boolean deduplicateSelectionItems) {
return new SybaseLegacySqmToSqlAstConverter<>(
sqmSelectStatement,
queryOptions,
domainParameterXref,
domainParameterBindings,
loadQueryInfluencers,
creationContext,
deduplicateSelectionItems
);
}
};
}
@Override
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
return new StandardSqlAstTranslatorFactory() {
@Override
protected <T extends JdbcOperation> SqlAstTranslator<T> buildTranslator(
SessionFactoryImplementor sessionFactory, Statement statement) {
return new SybaseLegacySqlAstTranslator<>( sessionFactory, statement );
}
};
}
@Override
public boolean supportsNullPrecedence() {
return false;
}
@Override
public int getInExpressionCountLimit() {
return PARAM_LIST_SIZE_LIMIT;
}
@Override
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.contributeTypes(typeContributions, serviceRegistry);
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration()
.getJdbcTypeRegistry();
if ( jtdsDriver ) {
jdbcTypeRegistry.addDescriptor( Types.TINYINT, SmallIntJdbcType.INSTANCE );
// The jTDS driver doesn't support the JDBC4 signatures using 'long length' for stream bindings
jdbcTypeRegistry.addDescriptor( Types.CLOB, ClobJdbcType.CLOB_BINDING );
// The jTDS driver doesn't support nationalized types
jdbcTypeRegistry.addDescriptor( Types.NCLOB, ClobJdbcType.CLOB_BINDING );
jdbcTypeRegistry.addDescriptor( Types.NVARCHAR, ClobJdbcType.CLOB_BINDING );
}
else {
// Some Sybase drivers cannot support getClob. See HHH-7889
jdbcTypeRegistry.addDescriptor( Types.CLOB, ClobJdbcType.STREAM_BINDING_EXTRACTING );
}
jdbcTypeRegistry.addDescriptor( Types.BLOB, BlobJdbcType.PRIMITIVE_ARRAY_BINDING );
// Sybase requires a custom binder for binding untyped nulls with the NULL type
typeContributions.contributeJdbcType( ObjectNullAsNullTypeJdbcType.INSTANCE );
// Until we remove StandardBasicTypes, we have to keep this
typeContributions.contributeType(
new JavaObjectType(
ObjectNullAsNullTypeJdbcType.INSTANCE,
typeContributions.getTypeConfiguration()
.getJavaTypeRegistry()
.getDescriptor( Object.class )
)
);
}
@Override
public NationalizationSupport getNationalizationSupport() {
// At least the jTDS driver doesn't support this
return jtdsDriver ? NationalizationSupport.IMPLICIT : super.getNationalizationSupport();
}
@Override
public void initializeFunctionRegistry(QueryEngine queryEngine) {
super.initializeFunctionRegistry(queryEngine);
CommonFunctionFactory functionFactory = new CommonFunctionFactory(queryEngine);
// For SQL-Server we need to cast certain arguments to varchar(16384) to be able to concat them
queryEngine.getSqmFunctionRegistry().register(
"count",
new CountFunction(
this,
queryEngine.getTypeConfiguration(),
SqlAstNodeRenderingMode.DEFAULT,
"+",
"varchar(16384)",
false
)
);
// AVG by default uses the input type, so we possibly need to cast the argument type, hence a special function
functionFactory.avg_castingNonDoubleArguments( this, SqlAstNodeRenderingMode.DEFAULT );
//this doesn't work 100% on earlier versions of Sybase
//which were missing the third parameter in charindex()
//TODO: we could emulate it with substring() like in Postgres
functionFactory.locate_charindex();
functionFactory.replace_strReplace();
functionFactory.everyAny_minMaxCase();
functionFactory.octetLength_pattern( "datalength(?1)" );
functionFactory.bitLength_pattern( "datalength(?1)*8" );
queryEngine.getSqmFunctionRegistry().register( "timestampadd",
new IntegralTimestampaddFunction( this, queryEngine.getTypeConfiguration() ) );
}
@Override
public String getNullColumnString() {
return " null";
}
@Override
public boolean canCreateSchema() {
// As far as I can tell, it does not
return false;
}
@Override
public String getCurrentSchemaCommand() {
return "select db_name()";
}
@Override
public int getMaxIdentifierLength() {
return 128;
}
@Override
public String castPattern(CastType from, CastType to) {
if ( to == CastType.STRING ) {
switch ( from ) {
case DATE:
return "str_replace(convert(varchar,?1,102),'.','-')";
case TIME:
return "convert(varchar,?1,108)";
case TIMESTAMP:
return "str_replace(convert(varchar,?1,23),'T',' ')";
}
}
return super.castPattern( from, to );
}
@Override
public String translateExtractField(TemporalUnit unit) {
switch ( unit ) {
case WEEK: return "calweekofyear"; //the ISO week number I think
default: return super.translateExtractField(unit);
}
}
@Override
public String extractPattern(TemporalUnit unit) {
//TODO!!
return "datepart(?1,?2)";
}
@Override
public boolean supportsFractionalTimestampArithmetic() {
return false;
}
@Override
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
//TODO!!
return "dateadd(?1,?2,?3)";
}
@Override
public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) {
//TODO!!
return "datediff(?1,?2,?3)";
}
@Override
public String trimPattern(TrimSpec specification, char character) {
return super.trimPattern(specification, character)
.replace("replace", "str_replace");
}
@Override
public void appendDatetimeFormat(SqlAppender appender, String format) {
throw new UnsupportedOperationException( "format() function not supported on Sybase");
}
@Override
public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, DatabaseMetaData dbMetaData)
throws SQLException {
if ( dbMetaData == null ) {
builder.setUnquotedCaseStrategy( IdentifierCaseStrategy.MIXED );
builder.setQuotedCaseStrategy( IdentifierCaseStrategy.MIXED );
}
return super.buildIdentifierHelper( builder, dbMetaData );
}
@Override
public NameQualifierSupport getNameQualifierSupport() {
if ( getVersion().isSameOrAfter( 15 ) ) {
return NameQualifierSupport.BOTH;
}
return NameQualifierSupport.CATALOG;
}
}

View File

@ -0,0 +1,180 @@
/*
* 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.community.dialect;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.sqm.ComparisonOperator;
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.cte.CteStatement;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
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.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.exec.spi.JdbcOperation;
/**
* A SQL AST translator for Sybase.
*
* @author Christian Beikov
*/
public class SybaseLegacySqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstTranslator<T> {
public SybaseLegacySqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) {
super( sessionFactory, statement );
}
// Sybase does not allow CASE expressions where all result arms contain plain parameters.
// At least one result arm must provide some type context for inference,
// so we cast the first result arm if we encounter this condition
@Override
protected void visitAnsiCaseSearchedExpression(
CaseSearchedExpression caseSearchedExpression,
Consumer<Expression> resultRenderer) {
if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( caseSearchedExpression ) ) {
final List<CaseSearchedExpression.WhenFragment> whenFragments = caseSearchedExpression.getWhenFragments();
final Expression firstResult = whenFragments.get( 0 ).getResult();
super.visitAnsiCaseSearchedExpression(
caseSearchedExpression,
e -> {
if ( e == firstResult ) {
renderCasted( e );
}
else {
resultRenderer.accept( e );
}
}
);
}
else {
super.visitAnsiCaseSearchedExpression( caseSearchedExpression, resultRenderer );
}
}
@Override
protected void visitAnsiCaseSimpleExpression(
CaseSimpleExpression caseSimpleExpression,
Consumer<Expression> resultRenderer) {
if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( caseSimpleExpression ) ) {
final List<CaseSimpleExpression.WhenFragment> whenFragments = caseSimpleExpression.getWhenFragments();
final Expression firstResult = whenFragments.get( 0 ).getResult();
super.visitAnsiCaseSimpleExpression(
caseSimpleExpression,
e -> {
if ( e == firstResult ) {
renderCasted( e );
}
else {
resultRenderer.accept( e );
}
}
);
}
else {
super.visitAnsiCaseSimpleExpression( caseSimpleExpression, resultRenderer );
}
}
@Override
protected boolean renderNamedTableReference(NamedTableReference tableReference, LockMode lockMode) {
super.renderNamedTableReference( tableReference, lockMode );
if ( LockMode.READ.lessThan( lockMode ) ) {
appendSql( " holdlock" );
}
return true;
}
@Override
protected void renderForUpdateClause(QuerySpec querySpec, ForUpdateClause forUpdateClause) {
// Sybase does not support the FOR UPDATE clause
}
@Override
protected void renderSearchClause(CteStatement cte) {
// Sybase does not support this, but it's just a hint anyway
}
@Override
protected void renderCycleClause(CteStatement cte) {
// Sybase does not support this, but it can be emulated
}
@Override
public void visitOffsetFetchClause(QueryPart queryPart) {
assertRowsOnlyFetchClauseType( queryPart );
if ( !queryPart.isRoot() && queryPart.getOffsetClauseExpression() != null ) {
throw new IllegalArgumentException( "Can't emulate offset clause in subquery" );
}
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonEmulateIntersect( lhs, operator, rhs );
}
@Override
protected void renderSelectTupleComparison(
List<SqlSelection> lhsExpressions,
SqlTuple tuple,
ComparisonOperator operator) {
emulateSelectTupleComparison( lhsExpressions, tuple.getExpressions(), operator, true );
}
@Override
protected void renderPartitionItem(Expression expression) {
if ( expression instanceof Literal ) {
// Note that this depends on the SqmToSqlAstConverter to add a dummy table group
appendSql( "dummy_.x" );
}
else if ( expression instanceof Summarization ) {
// This could theoretically be emulated by rendering all grouping variations of the query and
// connect them via union all but that's probably pretty inefficient and would have to happen
// on the query spec level
throw new UnsupportedOperationException( "Summarization is not supported by DBMS" );
}
else {
expression.accept( this );
}
}
@Override
protected boolean supportsRowValueConstructorSyntax() {
return false;
}
@Override
protected boolean supportsRowValueConstructorSyntaxInInList() {
return false;
}
@Override
protected boolean supportsRowValueConstructorSyntaxInQuantifiedPredicates() {
return false;
}
@Override
protected boolean needsRowsToSkip() {
return true;
}
@Override
protected boolean needsMaxRows() {
return true;
}
}

View File

@ -0,0 +1,93 @@
/*
* 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.community.dialect;
import org.hibernate.engine.spi.LoadQueryInfluencers;
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.tree.SqmStatement;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.StandardTableGroup;
import org.hibernate.sql.ast.tree.select.QuerySpec;
/**
* A SQM to SQL AST translator for Sybase ASE.
*
* @author Christian Beikov
*/
public class SybaseLegacySqmToSqlAstConverter<T extends Statement> extends BaseSqmToSqlAstConverter<T> {
private boolean needsDummyTableGroup;
public SybaseLegacySqmToSqlAstConverter(
SqmStatement<?> statement,
QueryOptions queryOptions,
DomainParameterXref domainParameterXref,
QueryParameterBindings domainParameterBindings,
LoadQueryInfluencers fetchInfluencers,
SqlAstCreationContext creationContext,
boolean deduplicateSelectionItems) {
super(
creationContext,
statement,
queryOptions,
fetchInfluencers,
domainParameterXref,
domainParameterBindings,
deduplicateSelectionItems
);
}
@Override
public QuerySpec visitQuerySpec(SqmQuerySpec<?> sqmQuerySpec) {
final boolean needsDummy = this.needsDummyTableGroup;
this.needsDummyTableGroup = false;
try {
final QuerySpec querySpec = super.visitQuerySpec( sqmQuerySpec );
if ( this.needsDummyTableGroup ) {
querySpec.getFromClause().addRoot(
new StandardTableGroup(
true,
null,
null,
null,
new NamedTableReference(
"(select 1)",
"dummy_(x)",
false,
getCreationContext().getSessionFactory()
),
null,
getCreationContext().getSessionFactory()
)
);
}
return querySpec;
}
finally {
this.needsDummyTableGroup = needsDummy;
}
}
@Override
protected Expression resolveGroupOrOrderByExpression(SqmExpression<?> groupByClauseExpression) {
final Expression expression = super.resolveGroupOrOrderByExpression( groupByClauseExpression );
if ( expression instanceof Literal ) {
// Note that SqlAstTranslator.renderPartitionItem depends on this
this.needsDummyTableGroup = true;
}
return expression;
}
}

View File

@ -4,16 +4,18 @@
* 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.orm.test.dialect.unit.lockhint;
package org.hibernate.community.dialect.unit.lockhint;
import org.hibernate.community.dialect.SybaseASELegacyDialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.SybaseASE15Dialect;
import org.hibernate.orm.test.dialect.unit.lockhint.AbstractLockHintTest;
/**
* @author Gail Badner
*/
public class SybaseASE15LockHintsTest extends AbstractLockHintTest {
public static final Dialect DIALECT = new SybaseASE15Dialect();
public static final Dialect DIALECT = new SybaseASELegacyDialect();
protected String getLockHintUsed() {
return "holdlock";

View File

@ -48,10 +48,12 @@ import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtract
import static org.hibernate.type.SqlTypes.*;
/**
* A {@linkplain Dialect SQL dialect} for Sybase Adaptive Server Enterprise 11.9 and above.
* A {@linkplain Dialect SQL dialect} for Sybase Adaptive Server Enterprise 16 and above.
*/
public class SybaseASEDialect extends SybaseDialect {
private static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 16, 0 );
private final SizeStrategy sizeStrategy = new SizeStrategyImpl() {
@Override
public Size resolveSize(
@ -74,7 +76,7 @@ public class SybaseASEDialect extends SybaseDialect {
private final boolean ansiNull;
public SybaseASEDialect() {
this( DatabaseVersion.make( 11 ) );
this( MINIMUM_VERSION );
}
public SybaseASEDialect(DatabaseVersion version) {
@ -96,13 +98,10 @@ public class SybaseASEDialect extends SybaseDialect {
// tinyint to store signed bytes, we can use it
// to store boolean values)
return "tinyint";
case BIGINT:
// Sybase ASE didn't introduce 'bigint' until version 15.0
return getVersion().isBefore( 15 ) ? "numeric(19,0)" : super.columnType( sqlTypeCode );
case DATE:
return getVersion().isSameOrAfter( 12 ) ? "date" : super.columnType( sqlTypeCode );
return "date";
case TIME:
return getVersion().isSameOrAfter( 12 ) ? "time" : super.columnType( sqlTypeCode );
return "time";
}
return super.columnType( sqlTypeCode );
}
@ -114,7 +113,7 @@ public class SybaseASEDialect extends SybaseDialect {
// According to Wikipedia bigdatetime and bigtime were added in 15.5
// But with jTDS we can't use them as the driver can't handle the types
if ( getVersion().isSameOrAfter( 15, 5 ) && !jtdsDriver ) {
if ( !jtdsDriver ) {
ddlTypeRegistry.addDescriptor(
CapacityDependentDdlType.builder( DATE, "bigdatetime", "bigdatetime", this )
.withTypeCapacity( 3, "datetime" )
@ -265,7 +264,7 @@ public class SybaseASEDialect extends SybaseDialect {
case NATIVE:
// If the driver or database do not support bigdatetime and bigtime types,
// we try to operate on milliseconds instead
if ( getVersion().isBefore( 15, 5 ) || jtdsDriver ) {
if ( jtdsDriver ) {
return "dateadd(millisecond,?2/1000000,?3)";
}
else {
@ -280,12 +279,7 @@ public class SybaseASEDialect extends SybaseDialect {
public long getFractionalSecondPrecisionInNanos() {
// If the database does not support bigdatetime and bigtime types,
// we try to operate on milliseconds instead
if ( getVersion().isBefore( 15, 5 ) ) {
return 1_000_000;
}
else {
return 1_000;
}
return 1_000;
}
@Override
@ -294,14 +288,7 @@ public class SybaseASEDialect extends SybaseDialect {
switch ( unit ) {
case NANOSECOND:
case NATIVE:
// If the database does not support bigdatetime and bigtime types,
// we try to operate on milliseconds instead
if ( getVersion().isBefore( 15, 5 ) ) {
return "cast(datediff(ms,?2,?3) as numeric(21))";
}
else {
return "cast(datediff(mcs,cast(?2 as bigdatetime),cast(?3 as bigdatetime)) as numeric(21))";
}
return "cast(datediff(mcs,cast(?2 as bigdatetime),cast(?3 as bigdatetime)) as numeric(21))";
default:
return "datediff(?1,?2,?3)";
}
@ -577,13 +564,7 @@ public class SybaseASEDialect extends SybaseDialect {
public String getTableTypeString() {
//HHH-7298 I don't know if this would break something or cause some side affects
//but it is required to use 'select for update'
return getVersion().isBefore( 15, 7 ) ? super.getTableTypeString() : " lock datarows";
}
@Override
public boolean supportsExpectedLobUsagePattern() {
// Earlier Sybase did not support LOB locators at all
return getVersion().isSameOrAfter( 15, 7 );
return " lock datarows";
}
@Override
@ -593,33 +574,29 @@ public class SybaseASEDialect extends SybaseDialect {
@Override
public RowLockStrategy getWriteRowLockStrategy() {
return getVersion().isSameOrAfter( 15, 7 ) ? RowLockStrategy.COLUMN : RowLockStrategy.TABLE;
return RowLockStrategy.COLUMN;
}
@Override
public String getForUpdateString() {
return getVersion().isBefore( 15, 7 ) ? "" : " for update";
return " for update";
}
@Override
public String getForUpdateString(String aliases) {
return getVersion().isBefore( 15, 7 )
? ""
: getForUpdateString() + " of " + aliases;
return getForUpdateString() + " of " + aliases;
}
@Override
public String appendLockHint(LockOptions mode, String tableName) {
//TODO: is this really necessary??!
return getVersion().isBefore( 15, 7 ) ? super.appendLockHint( mode, tableName ) : tableName;
return tableName;
}
@Override
public String applyLocksToSql(String sql, LockOptions aliasedLockOptions, Map<String, String[]> keyColumnNames) {
//TODO: is this really correct?
return getVersion().isBefore( 15, 7 )
? super.applyLocksToSql( sql, aliasedLockOptions, keyColumnNames )
: sql + new ForUpdateFragment( this, aliasedLockOptions, keyColumnNames ).toFragmentString();
return sql + new ForUpdateFragment( this, aliasedLockOptions, keyColumnNames ).toFragmentString();
}
@Override
@ -665,10 +642,6 @@ public class SybaseASEDialect extends SybaseDialect {
@Override
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
if ( getVersion().isBefore( 15, 7 ) ) {
return null;
}
return (sqlException, message, sql) -> {
final String sqlState = JdbcExceptionHelper.extractSqlState( sqlException );
final int errorCode = JdbcExceptionHelper.extractErrorCode( sqlException );
@ -707,10 +680,6 @@ public class SybaseASEDialect extends SybaseDialect {
@Override
public LimitHandler getLimitHandler() {
if ( getVersion().isBefore( 12, 5 ) ) {
//support for SELECT TOP was introduced in Sybase ASE 12.5.3
return super.getLimitHandler();
}
return new TopLimitHandler(false);
}
}

View File

@ -104,12 +104,6 @@ public class SybaseASESqlAstTranslator<T extends JdbcOperation> extends Abstract
@Override
protected boolean renderNamedTableReference(NamedTableReference tableReference, LockMode lockMode) {
super.renderNamedTableReference( tableReference, lockMode );
if ( getDialect().getVersion().isBefore( 15, 7 ) ) {
if ( LockMode.READ.lessThan( lockMode ) ) {
appendSql( " holdlock" );
}
return true;
}
return false;
}
@ -144,14 +138,6 @@ public class SybaseASESqlAstTranslator<T extends JdbcOperation> extends Abstract
}
}
@Override
protected void renderForUpdateClause(QuerySpec querySpec, ForUpdateClause forUpdateClause) {
if ( getDialect().getVersion().isBefore( 15, 7 ) ) {
return;
}
super.renderForUpdateClause( querySpec, forUpdateClause );
}
@Override
protected void renderSearchClause(CteStatement cte) {
// Sybase ASE does not support this, but it's just a hint anyway
@ -394,7 +380,7 @@ public class SybaseASESqlAstTranslator<T extends JdbcOperation> extends Abstract
}
private boolean supportsTopClause() {
return getDialect().getVersion().isSameOrAfter( 12, 5 );
return true;
}
private boolean supportsParameterOffsetFetchExpression() {

View File

@ -60,17 +60,20 @@ import jakarta.persistence.TemporalType;
*/
public class SybaseDialect extends AbstractTransactSQLDialect {
protected boolean jtdsDriver;
protected final boolean jtdsDriver;
private static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 16, 0 );
//All Sybase dialects share an IN list size limit.
private static final int PARAM_LIST_SIZE_LIMIT = 250000;
public SybaseDialect() {
this( DatabaseVersion.make( 11, 0 ) );
this( MINIMUM_VERSION );
}
public SybaseDialect(DatabaseVersion version) {
super(version);
jtdsDriver = true;
}
public SybaseDialect(DialectResolutionInfo info) {
@ -79,6 +82,11 @@ public class SybaseDialect extends AbstractTransactSQLDialect {
&& info.getDriverName().contains( "jTDS" );
}
@Override
protected DatabaseVersion getMinimumSupportedVersion() {
return MINIMUM_VERSION;
}
@Override
public JdbcType resolveSqlTypeDescriptor(
String columnTypeName,
@ -321,10 +329,12 @@ public class SybaseDialect extends AbstractTransactSQLDialect {
@Override
public NameQualifierSupport getNameQualifierSupport() {
if ( getVersion().isSameOrAfter( 15 ) ) {
if ( jtdsDriver ) {
return NameQualifierSupport.CATALOG;
}
else {
return NameQualifierSupport.BOTH;
}
return NameQualifierSupport.CATALOG;
}
}

View File

@ -51,7 +51,7 @@ import org.junit.jupiter.api.Test;
xmlMappings = "org/hibernate/orm/test/dialect/function/Product.hbm.xml"
)
@SessionFactory
@RequiresDialect(value = SybaseASEDialect.class, majorVersion = 11)
@RequiresDialect(value = SybaseASEDialect.class)
@SuppressWarnings("rawtypes")
public class SybaseASEFunctionTest {