/******************************************************************************* * Copyright (c) 1998, 2015 Oracle, 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 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: * 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; } }