/******************************************************************************* * Copyright (c) 2012, 2015 SAP, Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * SAP AG - Initial implementation, enhancement bug 380226 * * This code is being developed under INCUBATION and is not currently included * in the automated EclipseLink build. The API in this code may change, or * may never be included in the product. Please provide feedback through mailing * lists or the bug database. ******************************************************************************/ package org.eclipse.persistence.platform.database; import java.io.IOException; import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Blob; import java.sql.Clob; import java.sql.Date; import java.sql.SQLException; import java.sql.Statement; import java.sql.Time; import java.sql.Timestamp; import java.util.Calendar; import java.util.Hashtable; import java.util.Vector; import org.eclipse.persistence.expressions.ExpressionOperator; import org.eclipse.persistence.internal.databaseaccess.DatabaseCall; import org.eclipse.persistence.internal.databaseaccess.FieldTypeDefinition; import org.eclipse.persistence.internal.expressions.ExpressionSQLPrinter; import org.eclipse.persistence.internal.expressions.FunctionExpression; import org.eclipse.persistence.internal.expressions.SQLSelectStatement; import org.eclipse.persistence.internal.helper.ClassConstants; import org.eclipse.persistence.internal.helper.DatabaseTable; import org.eclipse.persistence.internal.helper.Helper; import org.eclipse.persistence.internal.helper.NonSynchronizedVector; import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.queries.ReadQuery; import org.eclipse.persistence.queries.ValueReadQuery; import org.eclipse.persistence.tools.schemaframework.FieldDefinition; import org.eclipse.persistence.tools.schemaframework.TableDefinition; /** * <b>Database Platform for SAP HANA</b> <br> * * <p> * <b>Feature Testing</b><br> * ---------------------- * <ul> * <li>DDL Generation - Succeeds * <li>Outer Join - Succeeds * <li>Subquery - Succeeds * <li>Stored Procedure Calls - Not supported * <li>Stored Procedure Generation - Not supported * <li>Native Sequences/Identifier fields - Succeeds * <li>JPA Bulk Update/Delete - Succeeds with Limitations * <li>Batch Reading - Succeeds * <li>Batch Writing - Succeeds * <li>Pessimistic Locking - Succeeds with Limitations * <li>First Result/Limit - Succeeds with Limitations * <li>Expression Framework - Succeeds with Limitations * <li>Delimiters - Succeeds * <li>Auto Detection - Succeeds * </ul> * <br> * <p> * <b>Limitations</b><br> * ---------------- * <ul> * <li>Reserved SQL keywords cannot be used as table, column or sequence names. Use a different * name, or enclose the name in double quotes. For example: @Column(name="\"LANGUAGE\"") * <li>Pessimistic locking adds 'FOR UPDATE' to the SELECT statement, and cannot be used with * queries that use DISTINCT. * <li>Pessimistic locking cannot be used with queries that select from multiple tables. * <li>The LockNoWait option of Pessimistic Locking cannot be used; it is ignored when specified * (i.e. only 'FOR UPDATE' is added to the SELECT statement). * <li>Bulk update and delete operations that require multiple tables to be accessed cannot be used * (e.g. bulk operation on an entity that is part of an inheritance hierarchy, UpdateAll and * DeleteAll queries). * {@literal <li>'= NULL' and '<> NULL'} cannot be used for null comparisons in the WHERE clause. * Use 'IS (NOT) NULL' instead. * <li>Scrollable cursors are not supported. * <li>Query timeouts are not supported. * </ul> * * @author Reiner Singer (SAP AG), Sabine Heider (SAP AG) */ public final class HANAPlatform extends DatabasePlatform { private static final long serialVersionUID = 1L; private static final int MAX_VARTYPE_LENGTH = 2000; public HANAPlatform() { super(); this.pingSQL = "SELECT 1 FROM DUMMY"; } @Override public boolean isHANA() { return true; } @Override public boolean usesStringBinding() { return false; } @Override public boolean requiresUniqueConstraintCreationOnTableCreate() { return true; } @Override public boolean isForUpdateCompatibleWithDistinct() { return false; } @Override public boolean supportsIndividualTableLocking() { return false; } @Override protected final Hashtable buildFieldTypes() { final Hashtable<Class, FieldTypeDefinition> fieldTypeMapping = new Hashtable<Class, FieldTypeDefinition>(); fieldTypeMapping.put(Boolean.class, new FieldTypeDefinition("SMALLINT", false)); // TODO // boolean fieldTypeMapping.put(Number.class, new FieldTypeDefinition("DOUBLE", false)); fieldTypeMapping.put(Short.class, new FieldTypeDefinition("SMALLINT", false)); fieldTypeMapping.put(Integer.class, new FieldTypeDefinition("INTEGER", false)); fieldTypeMapping.put(Long.class, new FieldTypeDefinition("BIGINT", false)); fieldTypeMapping.put(Float.class, new FieldTypeDefinition("FLOAT", false)); fieldTypeMapping.put(Double.class, new FieldTypeDefinition("DOUBLE", false)); fieldTypeMapping.put(BigInteger.class, new FieldTypeDefinition("DECIMAL", 34)); fieldTypeMapping.put(BigDecimal.class, new FieldTypeDefinition("DECIMAL", 34).setLimits(34, -34, 34)); fieldTypeMapping.put(Character.class, new FieldTypeDefinition("NCHAR", 1)); fieldTypeMapping.put(Character[].class, new FieldTypeDefinition("NVARCHAR", 255)); fieldTypeMapping.put(char[].class, new FieldTypeDefinition("NVARCHAR", 255)); fieldTypeMapping.put(String.class, new FieldTypeDefinition("NVARCHAR", 255)); fieldTypeMapping.put(Byte.class, new FieldTypeDefinition("SMALLINT", false)); fieldTypeMapping.put(Byte[].class, new FieldTypeDefinition("VARBINARY", 255)); fieldTypeMapping.put(byte[].class, new FieldTypeDefinition("VARBINARY", 255)); fieldTypeMapping.put(Blob.class, new FieldTypeDefinition("BLOB", false)); fieldTypeMapping.put(Clob.class, new FieldTypeDefinition("NCLOB", false)); fieldTypeMapping.put(Date.class, new FieldTypeDefinition("DATE", false)); fieldTypeMapping.put(Time.class, new FieldTypeDefinition("TIME", false)); fieldTypeMapping.put(Timestamp.class, new FieldTypeDefinition("TIMESTAMP", false)); return fieldTypeMapping; } @Override /** * EclipseLink does not support length dependent type mapping. * Map varchar types with length > MAX_VARCHAR_UNICODE_LENGTH to CLOB (i.e clob); shorter types to NVARCHAR (n) * See also bugs 317597, 317448 */ protected void printFieldTypeSize(Writer writer, FieldDefinition field, FieldTypeDefinition fieldType) throws IOException { String typeName = fieldType.getName(); if ("NVARCHAR".equals(typeName)) { if (field.getSize() > MAX_VARTYPE_LENGTH) { fieldType = new FieldTypeDefinition("NCLOB", false); } } else if ("VARBINARY".equals(typeName)) { if (field.getSize() > MAX_VARTYPE_LENGTH || field.getSize() == 0) { fieldType = new FieldTypeDefinition("BLOB", false); } } super.printFieldTypeSize(writer, field, fieldType); if (fieldType.getTypesuffix() != null) { writer.append(" " + fieldType.getTypesuffix()); } } @Override protected final void initializePlatformOperators() { super.initializePlatformOperators(); this.addOperator(HANAPlatform.createConcatExpressionOperator()); this.addOperator(HANAPlatform.createNullifOperator()); this.addOperator(HANAPlatform.createTodayExpressionOperator()); this.addOperator(HANAPlatform.createCurrentDateExpressionOperator()); this.addOperator(HANAPlatform.createCurrentTimeExpressionOperator()); this.addOperator(HANAPlatform.createLogOperator()); this.addOperator(HANAPlatform.createLocateOperator()); this.addOperator(HANAPlatform.createLocate2Operator()); this.addOperator(HANAPlatform.createVarianceOperator()); this.addNonBindingOperator(HANAPlatform.createNullValueOperator()); } private static final ExpressionOperator createConcatExpressionOperator() { return ExpressionOperator.simpleLogicalNoParens(ExpressionOperator.Concat, "||"); } /** * Creates the expression operator representing the JPQL function current_timestamp as defined * by 4.6.17.2.3 of the JPA 2.0 specification * * @return the expression operator representing the JPQL function current_timestamp as defined * by 4.6.17.2.3 of the JPA 2.0 specification */ private static final ExpressionOperator createTodayExpressionOperator() { return ExpressionOperator.simpleLogicalNoParens(ExpressionOperator.Today, "CURRENT_TIMESTAMP"); } /** * Creates the expression operator representing the JPQL function current_date as defined by * 4.6.17.2.3 of the JPA 2.0 specification * * @return the expression operator representing the JPQL function current_date as defined by * 4.6.17.2.3 of the JPA 2.0 specification */ private static final ExpressionOperator createCurrentDateExpressionOperator() { return ExpressionOperator.simpleLogicalNoParens(ExpressionOperator.CurrentDate, "CURRENT_DATE"); } /** * Creates the expression operator representing the JPQL function current_timestamp as defined * by 4.6.17.2.3 of the JPA 2.0 specification * * @return the expression operator representing the JPQL function current_timestamp as defined * by 4.6.17.2.3 of the JPA 2.0 specification */ private static final ExpressionOperator createCurrentTimeExpressionOperator() { return ExpressionOperator.simpleLogicalNoParens(ExpressionOperator.CurrentTime, "CURRENT_TIME"); } /** * Creates the expression operator representing the JPQL function variance * * @return the expression operator representing the JPQL function variance */ private static final ExpressionOperator createVarianceOperator() { return ExpressionOperator.simpleAggregate(ExpressionOperator.Variance, "VAR", "variance"); } /** * Create the log operator for this platform */ private static final ExpressionOperator createLogOperator() { ExpressionOperator result = new ExpressionOperator(); result.setSelector(ExpressionOperator.Log); Vector v = NonSynchronizedVector.newInstance(2); v.addElement("LOG(10,"); v.addElement(")"); result.printsAs(v); result.bePrefix(); result.setNodeClass(FunctionExpression.class); return result; } /** * INTERNAL: Build locate operator i.e. LOCATE("ob", t0.F_NAME) */ public static ExpressionOperator createLocateOperator() { ExpressionOperator expOperator = ExpressionOperator.simpleTwoArgumentFunction( ExpressionOperator.Locate, "INSTR"); int[] argumentIndices = new int[2]; argumentIndices[0] = 0; argumentIndices[1] = 1; expOperator.setArgumentIndices(argumentIndices); expOperator.setIsBindingSupported(false); return expOperator; } /** * INTERNAL: Build locate operator with 3 params i.e. LOCATE("coffee", t0.DESCRIP, 4). Last * parameter is a start at. */ public static ExpressionOperator createLocate2Operator() { ExpressionOperator expOperator = ExpressionOperator.simpleThreeArgumentFunction( ExpressionOperator.Locate2, "INSTR"); int[] argumentIndices = new int[3]; argumentIndices[0] = 0; argumentIndices[1] = 1; argumentIndices[2] = 2; expOperator.setArgumentIndices(argumentIndices); expOperator.setIsBindingSupported(false); return expOperator; } private static final ExpressionOperator createNullifOperator() { ExpressionOperator exOperator = new ExpressionOperator(); exOperator.setType(ExpressionOperator.FunctionOperator); exOperator.setSelector(ExpressionOperator.NullIf); Vector v = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(4); v.addElement(" (CASE WHEN "); v.addElement(" = "); v.addElement(" THEN NULL ELSE "); v.addElement(" END) "); exOperator.printsAs(v); exOperator.bePrefix(); int[] indices = { 0, 1, 0 }; exOperator.setArgumentIndices(indices); exOperator.setNodeClass(ClassConstants.FunctionExpression_Class); return exOperator; } private static final ExpressionOperator createNullValueOperator() { return ExpressionOperator.simpleTwoArgumentFunction(ExpressionOperator.Nvl, "IFNULL"); } @Override public void printSQLSelectStatement(DatabaseCall call, ExpressionSQLPrinter printer, SQLSelectStatement statement) { int max = 0; int firstRow = 0; ReadQuery query = statement.getQuery(); if (query != null) { max = query.getMaxRows(); firstRow = query.getFirstResult(); } if (max <= 0 && firstRow <= 0) { // neither max nor firstRow is set super.printSQLSelectStatement(call, printer, statement); return; } if (max <= 0) { // if max row is not set use MAX_VALUE instead // this is done, because NewDB does not allow // OFFSET without LIMIT, and scrollable cursor is not supported // in order to support firstRows without MaxRows also MaxRow has to be set // this limits also the size of the result set in this case to Integer.MAX_VALUE rows query.setMaxRows(Integer.MAX_VALUE); } statement.setUseUniqueFieldAliases(true); call.setFields(statement.printSQL(printer)); printer.printString(" LIMIT "); printer.printParameter(DatabaseCall.MAXROW_FIELD); printer.printString(" OFFSET "); printer.printParameter(DatabaseCall.FIRSTRESULT_FIELD); call.setIgnoreFirstRowSetting(true); call.setIgnoreMaxResultsSetting(true); } @Override public int computeMaxRowsForSQL(int firstResultIndex, int maxResults) { return maxResults - ((firstResultIndex >= 0) ? firstResultIndex : 0); } @Override public boolean shouldOptimizeDataConversion() { return true; // TODO is this needed? (seems to default to true) } private void addNonBindingOperator(ExpressionOperator operator) { operator.setIsBindingSupported(false); addOperator(operator); } @Override public final boolean supportsNativeSequenceNumbers() { return true; } @Override public final ValueReadQuery buildSelectQueryForSequenceObject(final String sequenceName, final Integer size) { return new ValueReadQuery("SELECT " + sequenceName + ".NEXTVAL FROM DUMMY"); } @Override public final boolean supportsGlobalTempTables() { return false; } @Override protected final String getCreateTempTableSqlPrefix() { return "CREATE LOCAL TEMPORARY TABLE "; } @Override public DatabaseTable getTempTableForTable(DatabaseTable table) { return new DatabaseTable("#" + table.getName(), table.getTableQualifier(), table.shouldUseDelimiters(), getStartDelimiter(), getEndDelimiter()); } @Override protected boolean shouldTempTableSpecifyPrimaryKeys() { return false; } @Override public final int getMaxFieldNameSize() { return 120; } @Override public final boolean supportsLocalTempTables() { return true; } @Override public final boolean shouldAlwaysUseTempStorageForModifyAll() { return false; } @Override public final boolean shouldBindLiterals() { return false; } @Override public final boolean shouldPrintOuterJoinInWhereClause() { return false; } @Override public final boolean shouldUseJDBCOuterJoinSyntax() { return false; } @Override public boolean supportsSequenceObjects() { return true; } @Override public boolean canBatchWriteWithOptimisticLocking(DatabaseCall call) { return true; } @Override public int executeBatch(Statement statement, boolean isStatementPrepared) throws SQLException { int[] updateResult = statement.executeBatch(); if (isStatementPrepared) { int updateCount = 0; for (int count : updateResult) { if (count == Statement.SUCCESS_NO_INFO) { count = 1; } updateCount += count; } return updateCount; } else { return updateResult.length; } } @Override public boolean supportsForeignKeyConstraints() { return false; } /** * Used for stored procedure creation: Prefix for INPUT parameters. Not required on most * platforms. */ @Override public String getInputProcedureToken() { return "IN"; } /** * This method is used to print the output parameter token when stored procedures are called */ @Override public String getOutputProcedureToken() { return "OUT"; } /** * Used for sp calls. */ @Override public String getProcedureCallTail() { return ""; } /** * INTERNAL: Should the variable name of a stored procedure call be printed as part of the * procedure call e.g. EXECUTE PROCEDURE MyStoredProc(myvariable = ?) */ @Override public boolean shouldPrintStoredProcedureArgumentNameInCall() { return false; } @Override public boolean requiresProcedureCallOuputToken() { return false; } @Override public boolean supportsStoredFunctions() { return false; } @Override protected void appendDate(Date date, Writer writer) throws IOException { writer.write("TO_DATE('"); writer.write(Helper.printDate(date)); writer.write("')"); } @Override protected void appendTime(Time time, Writer writer) throws IOException { writer.write("TO_TIME('"); writer.write(Helper.printTime(time)); writer.write("')"); } @Override protected void appendTimestamp(Timestamp timestamp, Writer writer) throws IOException { writer.write("TO_TIMESTAMP('"); writer.write(Helper.printTimestamp(timestamp)); writer.write("')"); } @Override protected void appendCalendar(Calendar calendar, Writer writer) throws IOException { writer.write("TO_TIMESTAMP('"); writer.write(Helper.printCalendar(calendar)); writer.write("')"); } @Override public void writeAddColumnClause(Writer writer, AbstractSession session, TableDefinition table, FieldDefinition field) throws IOException { writer.write("ADD ("); field.appendDBString(writer, session, table); writer.write(")"); } @Override public String getProcedureCallHeader() { return "CALL "; } }