| /* |
| * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 1998, 2015 IBM Corporation and/or its affiliates. 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: |
| // Oracle - initial API and implementation from Oracle TopLink |
| // 12/11/2014 - Dalia Abo Sheasha |
| // - 454917 : Wrong SQL statement generated for Informix when GenerationType.IDENTITY strategy is used |
| // 02/19/2015 - Rick Curtis |
| // - 458877 : Add national character support |
| package org.eclipse.persistence.platform.database; |
| |
| import java.io.IOException; |
| import java.io.StringWriter; |
| import java.io.Writer; |
| import java.sql.Connection; |
| import java.sql.SQLException; |
| import java.util.Calendar; |
| import java.util.Hashtable; |
| |
| import org.eclipse.persistence.exceptions.ValidationException; |
| import org.eclipse.persistence.internal.databaseaccess.FieldTypeDefinition; |
| import org.eclipse.persistence.internal.helper.Helper; |
| import org.eclipse.persistence.queries.ValueReadQuery; |
| import org.eclipse.persistence.tools.schemaframework.FieldDefinition; |
| |
| /** |
| * <p><b>Purpose</b>: Provides Informix specific behavior. |
| * <p><b>Responsibilities</b>:<ul> |
| * <li> Types for schema creation. |
| * <li> Native sequencing using @@SERIAL. |
| * </ul> |
| * |
| * @since TOPLink/Java 1.0.1 |
| */ |
| public class InformixPlatform extends org.eclipse.persistence.platform.database.DatabasePlatform { |
| |
| |
| @Override |
| public void initializeConnectionData(Connection connection) throws SQLException { |
| |
| // Wasn't able to find a driver that would support passing unicode values |
| this.driverSupportsNationalCharacterVarying = false; |
| } |
| |
| /** |
| * Answer a platform correct string representation of a Date, suitable for SQL generation. |
| * Native format: 'yyyy-mm-dd |
| */ |
| @Override |
| protected void appendDate(java.sql.Date date, Writer writer) throws IOException { |
| if (usesNativeSQL()) { |
| writer.write("'"); |
| writer.write(Helper.printDate(date)); |
| writer.write("'"); |
| } else { |
| super.appendDate(date, writer); |
| } |
| } |
| |
| /** |
| * Write a timestamp in Informix specific format (yyyy-mm-dd hh:mm:ss.fff). |
| */ |
| protected void appendInformixTimestamp(java.sql.Timestamp timestamp, Writer writer) throws IOException { |
| writer.write("'"); |
| writer.write(Helper.printTimestampWithoutNanos(timestamp)); |
| writer.write('.'); |
| |
| // Must truncate the nanos to three decimal places, |
| // it is actually a complex algorithm... |
| String nanoString = Integer.toString(timestamp.getNanos()); |
| int numberOfZeros = 0; |
| for (int num = Math.min(9 - nanoString.length(), 3); num > 0; num--) { |
| writer.write('0'); |
| numberOfZeros++; |
| } |
| if ((nanoString.length() + numberOfZeros) > 3) { |
| nanoString = nanoString.substring(0, (3 - numberOfZeros)); |
| } |
| writer.write(nanoString); |
| writer.write("'"); |
| } |
| |
| /** |
| * Answer a platform correct string representation of a Calendar, suitable for SQL generation. |
| * The date is printed in the ODBC platform independent format {d'YYYY-MM-DD'}. |
| */ |
| @Override |
| protected void appendCalendar(Calendar calendar, Writer writer) throws IOException { |
| if (usesNativeSQL()) { |
| appendInformixCalendar(calendar, writer); |
| } else { |
| super.appendCalendar(calendar, writer); |
| } |
| } |
| |
| /** |
| * Write a timestamp in Informix specific format ( yyyy-mm-dd hh:mm:ss.fff) |
| */ |
| protected void appendInformixCalendar(Calendar calendar, Writer writer) throws IOException { |
| writer.write("'"); |
| writer.write(Helper.printCalendar(calendar)); |
| writer.write("'"); |
| } |
| |
| /** |
| * Answer a platform correct string representation of a Time, suitable for SQL generation. |
| * The time is printed in the ODBC platform independent format {t'hh:mm:ss'}. |
| */ |
| @Override |
| protected void appendTime(java.sql.Time time, Writer writer) throws IOException { |
| if (usesNativeSQL()) { |
| writer.write("'"); |
| writer.write(Helper.printTime(time)); |
| writer.write("'"); |
| } else { |
| super.appendTime(time, writer); |
| } |
| } |
| |
| /** |
| * Answer a platform correct string representation of a Timestamp, suitable for SQL generation. |
| * The date is printed in the ODBC platform independent format {d'YYYY-MM-DD'}. |
| */ |
| @Override |
| protected void appendTimestamp(java.sql.Timestamp timestamp, Writer writer) throws IOException { |
| if (usesNativeSQL()) { |
| appendInformixTimestamp(timestamp, writer); |
| } else { |
| super.appendTimestamp(timestamp, writer); |
| } |
| } |
| |
| @Override |
| protected Hashtable buildFieldTypes() { |
| Hashtable fieldTypeMapping; |
| |
| fieldTypeMapping = new Hashtable(); |
| fieldTypeMapping.put(Boolean.class, new FieldTypeDefinition("SMALLINT default 0", false)); |
| |
| fieldTypeMapping.put(Integer.class, new FieldTypeDefinition("INTEGER", false)); |
| fieldTypeMapping.put(Long.class, new FieldTypeDefinition("NUMERIC", 19)); |
| fieldTypeMapping.put(Float.class, new FieldTypeDefinition("FLOAT(16)", false)); |
| // Bug 218183: Informix 11 FLOAT precision max is 16 - substitute DECIMAL(32) for FLOAT(32) |
| fieldTypeMapping.put(Double.class, new FieldTypeDefinition("DECIMAL(32)", false)); |
| fieldTypeMapping.put(Short.class, new FieldTypeDefinition("SMALLINT", false)); |
| fieldTypeMapping.put(Byte.class, new FieldTypeDefinition("SMALLINT", false)); |
| fieldTypeMapping.put(java.math.BigInteger.class, new FieldTypeDefinition("DECIMAL", 32)); |
| fieldTypeMapping.put(java.math.BigDecimal.class, new FieldTypeDefinition("DECIMAL", 32).setLimits(32, -19, 19)); |
| fieldTypeMapping.put(Number.class, new FieldTypeDefinition("DECIMAL", 32).setLimits(32, -19, 19)); |
| |
| if (getUseNationalCharacterVaryingTypeForString()) { |
| fieldTypeMapping.put(String.class, new FieldTypeDefinition("NVARCHAR", DEFAULT_VARCHAR_SIZE)); |
| } else { |
| fieldTypeMapping.put(String.class, new FieldTypeDefinition("VARCHAR", DEFAULT_VARCHAR_SIZE)); |
| } |
| fieldTypeMapping.put(Character.class, new FieldTypeDefinition("CHAR", 1)); |
| |
| fieldTypeMapping.put(Byte[].class, new FieldTypeDefinition("BYTE", false)); |
| fieldTypeMapping.put(Character[].class, new FieldTypeDefinition("TEXT", false)); |
| fieldTypeMapping.put(byte[].class, new FieldTypeDefinition("BYTE", false)); |
| fieldTypeMapping.put(char[].class, new FieldTypeDefinition("TEXT", false)); |
| fieldTypeMapping.put(java.sql.Blob.class, new FieldTypeDefinition("BYTE", false)); |
| fieldTypeMapping.put(java.sql.Clob.class, new FieldTypeDefinition("TEXT", false)); |
| |
| fieldTypeMapping.put(java.sql.Date.class, new FieldTypeDefinition("DATE", false)); |
| fieldTypeMapping.put(java.sql.Time.class, new FieldTypeDefinition("DATETIME HOUR TO SECOND", false)); |
| fieldTypeMapping.put(java.sql.Timestamp.class, new FieldTypeDefinition("DATETIME YEAR TO FRACTION(5)", false)); |
| |
| return fieldTypeMapping; |
| } |
| |
| /** |
| * INTERNAL: |
| * Build the identity query for native sequencing. |
| */ |
| @Override |
| public ValueReadQuery buildSelectQueryForIdentity() { |
| ValueReadQuery selectQuery = new ValueReadQuery(); |
| StringWriter writer = new StringWriter(); |
| writer.write("SELECT DISTINCT(DBINFO('sqlca.sqlerrd1')) FROM systables"); |
| selectQuery.setSQLString(writer.toString()); |
| return selectQuery; |
| } |
| |
| /** |
| * INTERNAL: |
| * returns the maximum number of characters that can be used in a field |
| * name on this platform. |
| */ |
| @Override |
| public int getMaxFieldNameSize() { |
| return 18; |
| } |
| |
| /** |
| * Informix seems to like this syntax instead of the OF * syntax. |
| */ |
| @Override |
| public String getSelectForUpdateString() { |
| return " FOR UPDATE"; |
| } |
| |
| @Override |
| public boolean isInformix() { |
| return true; |
| } |
| |
| /** |
| * Some database require outer joins to be given in the where clause, others require it in the from clause. |
| * Informix requires it in the from clause with no ON expression. |
| */ |
| @Override |
| public boolean isInformixOuterJoin() { |
| return true; |
| } |
| |
| /** |
| * Informix seemed to require this at some point. |
| * Not sure if it still does. |
| */ |
| @Override |
| public boolean shouldSelectIncludeOrderBy() { |
| return true; |
| } |
| |
| /** |
| * Builds a table of maximum numeric values keyed on java class. This is used for type testing but |
| * might also be useful to end users attempting to sanitize values. |
| * <p><b>NOTE</b>: BigInteger {@literal &} BigDecimal maximums are dependent upon their precision {@literal &} Scale |
| */ |
| @Override |
| public Hashtable maximumNumericValues() { |
| Hashtable values = new Hashtable(); |
| |
| values.put(Integer.class, Integer.valueOf(Integer.MAX_VALUE)); |
| values.put(Long.class, Long.valueOf(Long.MAX_VALUE)); |
| values.put(Double.class, Double.valueOf(Float.MAX_VALUE)); |
| values.put(Short.class, Short.valueOf(Short.MAX_VALUE)); |
| values.put(Byte.class, Byte.valueOf(Byte.MAX_VALUE)); |
| values.put(Float.class, Float.valueOf(Float.MAX_VALUE)); |
| values.put(java.math.BigInteger.class, new java.math.BigInteger("99999999999999999999999999999999999999")); |
| values.put(java.math.BigDecimal.class, new java.math.BigDecimal("9999999999999999999.9999999999999999999")); |
| return values; |
| } |
| |
| /** |
| * Builds a table of minimum numeric values keyed on java class. This is used for type testing but |
| * might also be useful to end users attempting to sanitize values. |
| * <p><b>NOTE</b>: BigInteger {@literal &} BigDecimal minimums are dependent upon their precision {@literal &} Scale |
| */ |
| @Override |
| public Hashtable minimumNumericValues() { |
| Hashtable values = new Hashtable(); |
| |
| values.put(Integer.class, Integer.valueOf(Integer.MIN_VALUE)); |
| values.put(Long.class, Long.valueOf(Long.MIN_VALUE)); |
| values.put(Double.class, Double.valueOf(1.4012984643247149E-44));// The double values are weird. They lose precision at E-45 |
| values.put(Short.class, Short.valueOf(Short.MIN_VALUE)); |
| values.put(Byte.class, Byte.valueOf(Byte.MIN_VALUE)); |
| values.put(Float.class, Float.valueOf(Float.MIN_VALUE)); |
| values.put(java.math.BigInteger.class, new java.math.BigInteger("-99999999999999999999999999999999999999")); |
| values.put(java.math.BigDecimal.class, new java.math.BigDecimal("-9999999999999999999.9999999999999999999")); |
| return values; |
| } |
| |
| /** |
| * Append the field type to a writer unless the field uses an Identity strategy to generate its value. |
| * In this case, the field type 'SERIAL' will be appended later. |
| */ |
| @Override |
| public void printFieldTypeSize(Writer writer, FieldDefinition field, |
| FieldTypeDefinition fieldType, boolean shouldPrintFieldIdentityClause) throws IOException { |
| if (!shouldPrintFieldIdentityClause) |
| printFieldTypeSize(writer, field, fieldType); |
| } |
| |
| /** |
| * Append the receiver's field serial constraint clause to a writer. |
| */ |
| @Override |
| public void printFieldIdentityClause(Writer writer) throws ValidationException { |
| try { |
| writer.write(" SERIAL"); |
| } catch (IOException ioException) { |
| throw ValidationException.fileError(ioException); |
| } |
| } |
| |
| /** |
| * Used for sp calls. |
| */ |
| @Override |
| public boolean requiresProcedureCallBrackets() { |
| return false; |
| } |
| |
| /** |
| * Some Platforms want the constraint name after the constraint definition. |
| */ |
| @Override |
| public boolean shouldPrintConstraintNameAfter() { |
| return true; |
| } |
| |
| |
| /** |
| * INTERNAL: |
| * Indicates whether the platform supports identity. |
| * Informix does this through SERIAL field types. |
| * This method is to be used *ONLY* by sequencing classes |
| */ |
| @Override |
| public boolean supportsIdentity() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Indicates whether the platform supports sequence objects. |
| * This method is to be used *ONLY* by sequencing classes |
| */ |
| @Override |
| public boolean supportsSequenceObjects() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns query used to read value generated by sequence object (like Oracle sequence). |
| * This method is called when sequence object NativeSequence is connected, |
| * the returned query used until the sequence is disconnected. |
| * If the platform supportsSequenceObjects then (at least) one of buildSelectQueryForSequenceObject |
| * methods should return non-null query. |
| */ |
| @Override |
| public ValueReadQuery buildSelectQueryForSequenceObject(String qualifiedSeqName, Integer size) { |
| return new ValueReadQuery("select " + qualifiedSeqName + ".nextval from systables where tabid = 1"); |
| } |
| |
| /** |
| * INTERNAL: |
| * Override this method if the platform supports sequence objects |
| * and it's possible to alter sequence object's increment in the database. |
| */ |
| @Override |
| public boolean isAlterSequenceObjectSupported() { |
| return true; |
| } |
| } |