Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
import org.hibernate.community.dialect.sequence.SequenceInformationExtractorTimesTenDatabaseImpl;
import org.hibernate.community.dialect.sequence.TimesTenSequenceSupport;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.BooleanDecoder;
import org.hibernate.dialect.RowLockStrategy;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.function.OracleTruncFunction;
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
import org.hibernate.dialect.lock.LockingStrategy;
import org.hibernate.dialect.lock.OptimisticForceIncrementLockingStrategy;
import org.hibernate.dialect.lock.OptimisticLockingStrategy;
Expand All @@ -34,6 +37,7 @@
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.entity.Lockable;
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;
Expand All @@ -42,6 +46,7 @@
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.exec.spi.JdbcOperation;
Expand All @@ -52,21 +57,29 @@
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration;

import org.hibernate.type.BasicType;
import org.hibernate.type.BasicTypeRegistry;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.dialect.function.CurrentFunction;
import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
import jakarta.persistence.GenerationType;
import java.util.Date;

import jakarta.persistence.TemporalType;

import static org.hibernate.dialect.SimpleDatabaseVersion.ZERO_VERSION;
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.INTEGER;
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING;

/**
* A SQL dialect for TimesTen 5.1.
* A SQL dialect for Oracle TimesTen
* <p>
* Known limitations:
* joined-subclass support because of no CASE support in TimesTen
* No support for subqueries that includes aggregation
* - size() in HQL not supported
* - user queries that does subqueries with aggregation
* No CLOB/BLOB support
* No cascade delete support.
* No Calendar support
* No support for updating primary keys.
Expand All @@ -90,6 +103,7 @@ protected String columnType(int sqlTypeCode) {
// for the default Oracle type mode
// TypeMode=0
case SqlTypes.BOOLEAN:
case SqlTypes.BIT:
case SqlTypes.TINYINT:
return "tt_tinyint";
case SqlTypes.SMALLINT:
Expand All @@ -101,15 +115,26 @@ protected String columnType(int sqlTypeCode) {
//note that 'binary_float'/'binary_double' might
//be better mappings for Java Float/Double

case SqlTypes.VARCHAR:
case SqlTypes.LONGVARCHAR:
return "varchar2($l)";

case SqlTypes.LONGVARBINARY:
return "varbinary($l)";

//'numeric'/'decimal' are synonyms for 'number'
case SqlTypes.NUMERIC:
case SqlTypes.DECIMAL:
return "number($p,$s)";
case SqlTypes.FLOAT:
return "binary_float";
case SqlTypes.DOUBLE:
return "binary_double";

case SqlTypes.DATE:
return "tt_date";
case SqlTypes.TIME:
return "tt_time";
//`timestamp` has more precision than `tt_timestamp`
case SqlTypes.TIMESTAMP_WITH_TIMEZONE:
return "timestamp($p)";

Expand Down Expand Up @@ -157,22 +182,97 @@ public int getDefaultDecimalPrecision() {
public void initializeFunctionRegistry(FunctionContributions functionContributions) {
super.initializeFunctionRegistry(functionContributions);

CommonFunctionFactory functionFactory = new CommonFunctionFactory(functionContributions);
final TypeConfiguration typeConfiguration = functionContributions.getTypeConfiguration();
CommonFunctionFactory functionFactory = new CommonFunctionFactory(functionContributions);
final BasicTypeRegistry basicTypeRegistry = typeConfiguration.getBasicTypeRegistry();
final BasicType<Date> timestampType = basicTypeRegistry.resolve( StandardBasicTypes.TIMESTAMP );
final BasicType<String> stringType = basicTypeRegistry.resolve( StandardBasicTypes.STRING );
final BasicType<Long> longType = basicTypeRegistry.resolve( StandardBasicTypes.LONG );
final BasicType<Integer>intType = basicTypeRegistry.resolve( StandardBasicTypes.INTEGER );

// String Functions
functionFactory.trim2();
functionFactory.soundex();
functionFactory.trunc();
functionFactory.characterLength_length( SqlAstNodeRenderingMode.DEFAULT );
functionFactory.concat_pipeOperator();
functionFactory.toCharNumberDateTimestamp();
functionFactory.ceiling_ceil();
functionFactory.char_chr();
functionFactory.instr();
functionFactory.substr();
functionFactory.substring_substr();
functionFactory.leftRight_substr();
functionFactory.char_chr();
functionFactory.rownumRowid();
functionFactory.sysdate();
functionFactory.soundex();

// Date/Time Functions
functionContributions.getFunctionRegistry().register(
"sysdate", new CurrentFunction("sysdate", "sysdate", timestampType)
);
functionContributions.getFunctionRegistry().register(
"getdate", new CurrentFunction("getdate", "getdate()", timestampType )
);

// Multi-param date dialect functions
functionFactory.addMonths();
functionFactory.monthsBetween();

// Math functions
functionFactory.ceiling_ceil();
functionFactory.radians_acos();
functionFactory.degrees_acos();
functionFactory.sinh();
functionFactory.tanh();
functionContributions.getFunctionRegistry().register(
"trunc",
new OracleTruncFunction( functionContributions.getTypeConfiguration() )
);
functionContributions.getFunctionRegistry().registerAlternateKey( "truncate", "trunc" );
functionFactory.round();

// Bitwise functions
functionContributions.getFunctionRegistry()
.patternDescriptorBuilder( "bitor", "(?1+?2-bitand(?1,?2))")
.setExactArgumentCount( 2 )
.setArgumentTypeResolver( StandardFunctionArgumentTypeResolvers
.ARGUMENT_OR_IMPLIED_RESULT_TYPE )
.register();

functionContributions.getFunctionRegistry()
.patternDescriptorBuilder( "bitxor", "(?1+?2-2*bitand(?1,?2))")
.setExactArgumentCount( 2 )
.setArgumentTypeResolver( StandardFunctionArgumentTypeResolvers
.ARGUMENT_OR_IMPLIED_RESULT_TYPE )
.register();

// Misc. functions
functionContributions.getFunctionRegistry().namedDescriptorBuilder( "nvl" )
.setMinArgumentCount( 2 )
.setArgumentTypeResolver( StandardFunctionArgumentTypeResolvers.ARGUMENT_OR_IMPLIED_RESULT_TYPE )
.setReturnTypeResolver( StandardFunctionReturnTypeResolvers.useFirstNonNull() )
.register();

functionContributions.getFunctionRegistry().register(
"user", new CurrentFunction("user", "user", stringType)
);
functionContributions.getFunctionRegistry().register(
"rowid", new CurrentFunction("rowid", "rowid", stringType)
);
functionContributions.getFunctionRegistry().register(
"uid", new CurrentFunction("uid", "uid", intType)
);
functionContributions.getFunctionRegistry().register(
"rownum", new CurrentFunction("rownum", "rownum", longType)
);
functionContributions.getFunctionRegistry().register(
"vsize", new StandardSQLFunction("vsize", StandardBasicTypes.DOUBLE)
);
functionContributions.getFunctionRegistry().register(
"SESSION_USER", new CurrentFunction("SESSION_USER","SESSION_USER", stringType)
);
functionContributions.getFunctionRegistry().register(
"SYSTEM_USER", new CurrentFunction("SYSTEM_USER", "SYSTEM_USER", stringType)
);
functionContributions.getFunctionRegistry().register(
"CURRENT_USER", new CurrentFunction("CURRENT_USER","CURRENT_USER", stringType)
);

functionContributions.getFunctionRegistry().registerBinaryTernaryPattern(
"locate",
functionContributions.getTypeConfiguration().getBasicTypeRegistry().resolve( StandardBasicTypes.INTEGER ),
Expand Down Expand Up @@ -251,9 +351,10 @@ public RowLockStrategy getWriteRowLockStrategy() {
return RowLockStrategy.COLUMN;
}


@Override
public String getForUpdateString(String aliases) {
return " for update of " + aliases;
public String getForUpdateString() {
return " for update";
}

@Override
Expand Down Expand Up @@ -426,4 +527,104 @@ public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfi
}
}

@Override
public String getNativeIdentifierGeneratorStrategy() {
return "sequence";
}

@Override
public String currentDate() {
return "sysdate";
}

@Override
public String currentTime() {
return "sysdate";
}

@Override
public String currentTimestamp() {
return "sysdate";
}

@Override
public int getMaxVarcharLength() {
// 1 to 4,194,304 bytes according to TimesTen Doc
return 4194304;
}

@Override
public int getMaxVarbinaryLength() {
// 1 to 4,194,304 bytes according to TimesTen Doc
return 4194304;
}

@Override
public boolean isEmptyStringTreatedAsNull() {
return true;
}

@Override
public boolean supportsTupleDistinctCounts() {
return false;
}

@Override
public String getDual() {
return "dual";
}

@Override
public String getFromDualForSelectOnly() {
return " from dual";
}

@Override
public String castPattern(CastType from, CastType to) {
String result;
switch ( to ) {
case INTEGER:
case LONG:
result = BooleanDecoder.toInteger( from );
if ( result != null ) {
return result;
}
break;
case STRING:
switch ( from ) {
case BOOLEAN:
case INTEGER_BOOLEAN:
case TF_BOOLEAN:
case YN_BOOLEAN:
return BooleanDecoder.toString( from );
case DATE:
return "to_char(?1,'YYYY-MM-DD')";
case TIME:
return "to_char(?1,'HH24:MI:SS')";
case TIMESTAMP:
return "to_char(?1,'YYYY-MM-DD HH24:MI:SS.FF9')";
}
break;
case CLOB:
return "to_clob(?1)";
case DATE:
if ( from == CastType.STRING ) {
return "to_date(?1,'YYYY-MM-DD')";
}
break;
case TIME:
if ( from == CastType.STRING ) {
return "to_date(?1,'HH24:MI:SS')";
}
break;
case TIMESTAMP:
if ( from == CastType.STRING ) {
return "to_timestamp(?1,'YYYY-MM-DD HH24:MI:SS.FF9')";
}
break;
}
return super.castPattern(from, to);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectClause;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.sql.ast.Clause;

/**
* A SQL AST translator for TimesTen.
Expand Down Expand Up @@ -143,4 +145,55 @@ protected boolean supportsRowValueConstructorSyntaxInInList() {
protected boolean supportsRowValueConstructorSyntaxInQuantifiedPredicates() {
return false;
}

protected void renderRowsToClause(Expression offsetClauseExpression, Expression fetchClauseExpression) {
// offsetClauseExpression -> firstRow
// fetchClauseExpression -> maxRows
final Stack<Clause> clauseStack = getClauseStack();

if ( offsetClauseExpression == null && fetchClauseExpression != null ) {
// We only have a maxRows/limit. We use 'SELECT FIRST n' syntax
appendSql("first ");
clauseStack.push( Clause.FETCH );
try {
renderFetchExpression( fetchClauseExpression );
}
finally {
clauseStack.pop();
}
}
else if ( offsetClauseExpression != null ) {
// We have an offset. We use 'SELECT ROWS m TO n' syntax
appendSql( "rows " );

// Render offset parameter
clauseStack.push( Clause.OFFSET );
try {
renderOffsetExpression( offsetClauseExpression );
}
finally {
clauseStack.pop();
}

appendSql( " to " );

// Render maxRows/limit parameter
clauseStack.push( Clause.FETCH );
try {
if ( fetchClauseExpression != null ) {
// We need to substract 1 row to fit maxRows
renderFetchPlusOffsetExpressionAsLiteral( fetchClauseExpression, offsetClauseExpression, -1 );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does TimesTen not support a parameter here?

Suggested change
renderFetchPlusOffsetExpressionAsLiteral( fetchClauseExpression, offsetClauseExpression, -1 );
renderFetchPlusOffsetExpressionAsSingleParameter( fetchClauseExpression, offsetClauseExpression, -1 );

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried using 'renderFetchPlusOffsetExpressionAsSingleParameter()'. But it doesn't work as expected, that's why I ended using the 'AsLiteral' version.

Let me explain...

It seems like if I use:
'renderFetchPlusOffsetExpressionAsSingleParameter(
Expression fetchClauseExpression,
Expression offsetClauseExpression,
int offset
)'

The third parameter 'int offset' which is set to '-1' is not being added to the calculation. The end resultSet has 'rowsLimit +1 row'.

But If I use 'renderFetchPlusOffsetExpressionAsLiteral()' with the 'int offset=-1' my end resultSet has exactly the 'rowsLimit' size. Which is what we want.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the implementation of AbstractSqlAstTranslator#renderFetchPlusOffsetExpressionAsSingleParameter, it should work. It might be the case that a fix that I did for HHH-19632 needs to be backported to ORM 6.6 to make this work fully. Can you please share how you were testing/verifying this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, we are testing it like:

      session.beginTransaction();
      int rowOffset    = 27;
      int maxRowsLimit = 93;

      List<Book> books = session.createQuery("FROM Book ORDER BY id", Book.class)
        .setFirstResult(rowOffset)   // Offset
        .setMaxResults(maxRowsLimit) // Limit
        .list();

      // Check max rows limit
      Assertions.assertEquals( maxRowsLimit, books.size() );

And this fails with:
[ERROR] TestLimitHandler.testPaginationQueryOffsetAndLimit:136 expected: <93> but was: <94>

Note: the table contains 300 rows we expected to have 93 books (MaxRowsLimit)

}
else{
// We dont have a maxRows param, we will just use a MAX_VALUE
appendSql( Integer.MAX_VALUE );
}
}
finally {
clauseStack.pop();
}
}

appendSql( WHITESPACE );
}
}
Loading