| /* |
| * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 2019 IBM Corporation. All rights reserved. |
| * Copyright (c) 2012, 2021 SAP. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // 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.ArrayList; |
| import java.util.Calendar; |
| import java.util.Hashtable; |
| import java.util.List; |
| |
| 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.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 Hashtable<Class<?>, FieldTypeDefinition> buildFieldTypes() { |
| final Hashtable<Class<?>, FieldTypeDefinition> fieldTypeMapping = new Hashtable<>(); |
| 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 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 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 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 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 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 ExpressionOperator createVarianceOperator() { |
| return ExpressionOperator.simpleAggregate(ExpressionOperator.Variance, "VAR", "variance"); |
| } |
| |
| /** |
| * Create the log operator for this platform |
| */ |
| private static ExpressionOperator createLogOperator() { |
| ExpressionOperator result = new ExpressionOperator(); |
| result.setSelector(ExpressionOperator.Log); |
| List<String> v = new ArrayList<>(2); |
| v.add("LOG(10,"); |
| v.add(")"); |
| 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 ExpressionOperator createNullifOperator() { |
| ExpressionOperator exOperator = new ExpressionOperator(); |
| exOperator.setType(ExpressionOperator.FunctionOperator); |
| exOperator.setSelector(ExpressionOperator.NullIf); |
| List<String> v = new ArrayList<>(4); |
| v.add(" (CASE WHEN "); |
| v.add(" = "); |
| v.add(" THEN NULL ELSE "); |
| v.add(" END) "); |
| exOperator.printsAs(v); |
| exOperator.bePrefix(); |
| int[] indices = { 0, 1, 0 }; |
| exOperator.setArgumentIndices(indices); |
| exOperator.setNodeClass(ClassConstants.FunctionExpression_Class); |
| return exOperator; |
| } |
| |
| private static 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 boolean supportsNativeSequenceNumbers() { |
| return true; |
| } |
| |
| @Override |
| public ValueReadQuery buildSelectQueryForSequenceObject(final String sequenceName, final Integer size) { |
| return new ValueReadQuery("SELECT " + sequenceName + ".NEXTVAL FROM DUMMY"); |
| } |
| |
| @Override |
| public boolean supportsGlobalTempTables() { |
| return false; |
| } |
| |
| @Override |
| protected 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 int getMaxFieldNameSize() { |
| return 120; |
| } |
| |
| @Override |
| public boolean supportsLocalTempTables() { |
| return true; |
| } |
| |
| @Override |
| public boolean shouldAlwaysUseTempStorageForModifyAll() { |
| return false; |
| } |
| |
| @Override |
| public boolean shouldBindLiterals() { |
| return false; |
| } |
| |
| @Override |
| public boolean shouldPrintOuterJoinInWhereClause() { |
| return false; |
| } |
| |
| @Override |
| public 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 true; |
| } |
| |
| /** |
| * 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 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 "; |
| } |
| } |