| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.openjpa.jdbc.sql; |
| |
| import java.lang.reflect.Field; |
| import java.math.BigDecimal; |
| import java.sql.Connection; |
| import java.sql.DatabaseMetaData; |
| import java.sql.PreparedStatement; |
| import java.sql.SQLException; |
| import java.sql.Types; |
| import java.util.Arrays; |
| import java.util.Locale; |
| |
| import org.apache.openjpa.jdbc.identifier.DBIdentifier; |
| import org.apache.openjpa.jdbc.kernel.exps.FilterValue; |
| import org.apache.openjpa.jdbc.schema.Column; |
| import org.apache.openjpa.jdbc.schema.PrimaryKey; |
| import org.apache.openjpa.jdbc.schema.Table; |
| import org.apache.openjpa.jdbc.schema.Unique; |
| import org.apache.openjpa.lib.util.StringUtil; |
| import org.apache.openjpa.meta.JavaTypes; |
| import org.apache.openjpa.util.OpenJPAException; |
| import org.apache.openjpa.util.ReferentialIntegrityException; |
| |
| /** |
| * Dictionary for HyperSQL (HSQLDB) database. |
| */ |
| public class HSQLDictionary extends DBDictionary { |
| |
| /** |
| * Sets whether HSQL should use "CREATED CACHED TABLE" rather than |
| * "CREATE TABLE", which allows disk-based database operations. |
| */ |
| public boolean cacheTables = false; |
| |
| private int dbMajorVersion; |
| private int dbMinorVersion; |
| private int violation_of_unique_index_or_constraint; |
| |
| private SQLBuffer _oneBuffer = new SQLBuffer(this).append("1"); |
| |
| public HSQLDictionary() { |
| platform = "HSQL"; |
| validationSQL = "CALL 1"; |
| concatenateFunction = "CONCAT({0},{1})"; |
| closePoolSQL = "SHUTDOWN"; |
| |
| supportsAutoAssign = true; |
| lastGeneratedKeyQuery = "CALL IDENTITY()"; |
| autoAssignClause = "IDENTITY"; |
| autoAssignTypeName = "INTEGER"; |
| nextSequenceQuery = "SELECT NEXT VALUE FOR {0} FROM" |
| + " INFORMATION_SCHEMA.SYSTEM_SEQUENCES"; |
| crossJoinClause = "JOIN"; |
| requiresConditionForCrossJoin = true; |
| stringLengthFunction = "LENGTH({0})"; |
| trimLeadingFunction = "LTRIM({0})"; |
| trimTrailingFunction = "RTRIM({0})"; |
| trimBothFunction = "LTRIM(RTRIM({0}))"; |
| |
| supportsSelectForUpdate = false; |
| supportsSelectStartIndex = true; |
| supportsSelectEndIndex = true; |
| supportsDeferredConstraints = false; |
| |
| supportsNullTableForGetPrimaryKeys = false; |
| supportsNullTableForGetIndexInfo = false; |
| |
| requiresCastForMathFunctions = true; |
| requiresCastForComparisons = true; |
| |
| reservedWordSet.addAll(Arrays.asList(new String[]{ |
| "BEFORE", "BIGINT", "BINARY", "CACHED", "DATETIME", "LIMIT", |
| "LONGVARBINARY", "LONGVARCHAR", "OBJECT", "OTHER", |
| "SAVEPOINT", "TEMP", "TEXT", "TRIGGER", "TINYINT", |
| "VARBINARY", "VARCHAR_IGNORECASE", |
| })); |
| |
| fixedSizeTypeNameSet.addAll(Arrays.asList(new String[]{ |
| "TEXT" |
| })); |
| fixedSizeTypeNameSet.remove("NUMERIC"); |
| fixedSizeTypeNameSet.remove("DECIMAL"); |
| } |
| |
| /** |
| * Determine HSQLDB version and configure itself accordingly. |
| */ |
| @Override |
| public void connectedConfiguration(Connection conn) throws SQLException { |
| super.connectedConfiguration(conn); |
| |
| determineHSQLDBVersion(conn) ; |
| |
| if (dbMajorVersion == 1) { |
| blobTypeName = "VARBINARY"; |
| useGetObjectForBlobs = true; |
| rangePosition = RANGE_PRE_DISTINCT; |
| // HSQL 1.8.0 does support schema names in the table ("schema.table"), |
| // but doesn't support it for columns references ("schema.table.column") |
| useSchemaName = false; |
| } |
| if (dbMajorVersion > 1 && dbMinorVersion > 0) { |
| nextSequenceQuery += " LIMIT 1"; |
| } |
| String packageName; |
| String fieldName; |
| if (dbMajorVersion > 1) { |
| // default value for "X_23505" |
| violation_of_unique_index_or_constraint = 104; |
| packageName = "org.hsqldb.error.ErrorCode"; |
| fieldName = "X_23505"; |
| } else { |
| // default value for "VIOLATION_OF_UNIQUE_INDEX" |
| violation_of_unique_index_or_constraint = 9; |
| packageName = "org.hsqldb.Trace"; |
| fieldName = "VIOLATION_OF_UNIQUE_INDEX"; |
| } |
| try { |
| Class<?> cls = Class.forName(packageName); |
| Field fld = cls.getField(fieldName); |
| violation_of_unique_index_or_constraint = fld.getInt(null); |
| } catch (Exception e) { |
| } |
| } |
| |
| /** |
| * Determine HSQLDB version either by using JDBC 3 method or, if it |
| * is not available, by parsing the value returned by |
| * {@linkplain DatabaseMetaData#getDatabaseProductVersion()}. |
| */ |
| protected void determineHSQLDBVersion(Connection con) throws SQLException { |
| DatabaseMetaData metaData = con.getMetaData(); |
| |
| if (isJDBC3) { |
| dbMajorVersion = metaData.getDatabaseMajorVersion(); |
| dbMinorVersion = metaData.getDatabaseMinorVersion(); |
| } else { |
| // String is like "2.0.0" |
| String productVersion = metaData.getDatabaseProductVersion(); |
| String[] version = productVersion.split("\\.") ; |
| dbMajorVersion = Integer.parseInt(version[0]) ; |
| dbMinorVersion = Integer.parseInt(version[1]); |
| } |
| } |
| |
| |
| @Override |
| public int getPreferredType(int type) { |
| if (dbMajorVersion > 1) { |
| return super.getPreferredType(type); |
| } |
| switch (type) { |
| case Types.CLOB: |
| return Types.VARCHAR; |
| case Types.BLOB: |
| return Types.VARBINARY; |
| default: |
| return super.getPreferredType(type); |
| } |
| } |
| |
| @Override |
| public String[] getAddPrimaryKeySQL(PrimaryKey pk) { |
| return new String[0]; |
| } |
| |
| @Override |
| public String[] getDropPrimaryKeySQL(PrimaryKey pk) { |
| return new String[0]; |
| } |
| |
| @Override |
| public String[] getAddColumnSQL(Column column) { |
| return new String[]{ "ALTER TABLE " |
| + getFullName(column.getTable(), false) |
| + " ADD COLUMN " + getDeclareColumnSQL(column, true) }; |
| } |
| |
| @Override |
| public String[] getCreateTableSQL(Table table) { |
| StringBuilder buf = new StringBuilder(); |
| buf.append("CREATE "); |
| if (cacheTables) |
| buf.append("CACHED "); |
| buf.append("TABLE ").append(getFullName(table, false)).append(" ("); |
| |
| Column[] cols = table.getColumns(); |
| for (int i = 0; i < cols.length; i++) { |
| if (i > 0) |
| buf.append(", "); |
| buf.append(getDeclareColumnSQL(cols[i], false)); |
| } |
| |
| PrimaryKey pk = table.getPrimaryKey(); |
| String pkStr; |
| if (pk != null) { |
| pkStr = getPrimaryKeyConstraintSQL(pk); |
| if (!StringUtil.isEmpty(pkStr)) |
| buf.append(", ").append(pkStr); |
| } |
| |
| Unique[] unqs = table.getUniques(); |
| String unqStr; |
| for (Unique unq : unqs) { |
| unqStr = getUniqueConstraintSQL(unq); |
| if (unqStr != null) |
| buf.append(", ").append(unqStr); |
| } |
| |
| buf.append(")"); |
| return new String[]{ buf.toString() }; |
| } |
| |
| @Override |
| protected String getPrimaryKeyConstraintSQL(PrimaryKey pk) { |
| Column[] cols = pk.getColumns(); |
| if (cols.length == 1 && cols[0].isAutoAssigned()) |
| return null; |
| return super.getPrimaryKeyConstraintSQL(pk); |
| } |
| |
| @Override |
| public boolean isSystemIndex(String name, Table table) { |
| return name.toUpperCase(Locale.ENGLISH).startsWith("SYS_"); |
| } |
| |
| @Override |
| public boolean isSystemIndex(DBIdentifier name, Table table) { |
| if (DBIdentifier.isNull(name)) { |
| return false; |
| } |
| return name.getName().toUpperCase(Locale.ENGLISH).startsWith("SYS_"); |
| } |
| |
| @Override |
| protected String getSequencesSQL(String schemaName, String sequenceName) { |
| return getSequencesSQL(DBIdentifier.newSchema(schemaName), DBIdentifier.newSequence(sequenceName)); |
| } |
| |
| @Override |
| protected String getSequencesSQL(DBIdentifier schemaName, DBIdentifier sequenceName) { |
| StringBuilder buf = new StringBuilder(); |
| buf.append("SELECT SEQUENCE_SCHEMA, SEQUENCE_NAME FROM "). |
| append("INFORMATION_SCHEMA.SYSTEM_SEQUENCES"); |
| if (!DBIdentifier.isNull(schemaName) || !DBIdentifier.isNull(sequenceName)) |
| buf.append(" WHERE "); |
| if (!DBIdentifier.isNull(schemaName)) { |
| buf.append("SEQUENCE_SCHEMA = ?"); |
| if (!DBIdentifier.isNull(sequenceName)) |
| buf.append(" AND "); |
| } |
| if (!DBIdentifier.isNull(sequenceName)) |
| buf.append("SEQUENCE_NAME = ?"); |
| return buf.toString(); |
| } |
| |
| @Override |
| public SQLBuffer toOperation(String op, SQLBuffer selects, |
| SQLBuffer from, SQLBuffer where, SQLBuffer group, SQLBuffer having, |
| SQLBuffer order, boolean distinct, long start, long end, |
| String forUpdateClause) { |
| // hsql requires ordering when limit is used |
| if ((start != 0 || end != Long.MAX_VALUE) |
| && (order == null || order.isEmpty())) |
| order = _oneBuffer; |
| return super.toOperation(op, selects, from, where, group, having, |
| order, distinct, start, end, forUpdateClause); |
| } |
| |
| @Override |
| public Column[] getColumns(DatabaseMetaData meta, String catalog, |
| String schemaName, String tableName, String columnName, Connection conn) |
| throws SQLException { |
| return getColumns(meta, DBIdentifier.newCatalog(catalog), DBIdentifier.newSchema(schemaName), |
| DBIdentifier.newTable(tableName), DBIdentifier.newColumn(columnName), conn); |
| } |
| |
| @Override |
| public Column[] getColumns(DatabaseMetaData meta, DBIdentifier catalog, |
| DBIdentifier schemaName, DBIdentifier tableName, DBIdentifier columnName, Connection conn) |
| throws SQLException { |
| Column[] cols = super.getColumns(meta, catalog, schemaName, tableName, |
| columnName, conn); |
| |
| for (int i = 0; cols != null && i < cols.length; i++) |
| if ("BOOLEAN".equalsIgnoreCase(cols[i].getTypeIdentifier().getName())) |
| cols[i].setType(Types.BIT); |
| return cols; |
| } |
| |
| @Override |
| public void setDouble(PreparedStatement stmnt, int idx, double val, |
| Column col) |
| throws SQLException { |
| // HSQL has a bug where it cannot store a double if it is |
| // exactly the same as Long.MAX_VALUE or MIN_VALUE |
| if (val == Long.MAX_VALUE || val == Long.MIN_VALUE) { |
| stmnt.setLong(idx, (long) val); |
| } else { |
| super.setDouble(stmnt, idx, val, col); |
| } |
| } |
| |
| @Override |
| public void setBigDecimal(PreparedStatement stmnt, int idx, BigDecimal val, |
| Column col) |
| throws SQLException { |
| // hsql can't compare a BigDecimal equal to any other type, so try |
| // to set type based on column |
| int type = (val == null || col == null) ? JavaTypes.BIGDECIMAL |
| : col.getJavaType(); |
| switch (type) { |
| case JavaTypes.DOUBLE: |
| case JavaTypes.DOUBLE_OBJ: |
| setDouble(stmnt, idx, val.doubleValue(), col); |
| break; |
| case JavaTypes.FLOAT: |
| case JavaTypes.FLOAT_OBJ: |
| setDouble(stmnt, idx, val.floatValue(), col); |
| break; |
| default: |
| super.setBigDecimal(stmnt, idx, val, col); |
| } |
| } |
| |
| @Override |
| protected void appendSelectRange(SQLBuffer buf, long start, long end, |
| boolean subselect) { |
| if (dbMajorVersion > 1) { |
| if (start != 0) |
| buf.append(" OFFSET ").appendValue(start); |
| if (end != Long.MAX_VALUE) |
| buf.append(" LIMIT ").appendValue(end - start); |
| return; |
| } |
| // HSQL doesn't parameters in range |
| buf.append(" LIMIT ").append(String.valueOf(start)).append(" "); |
| if (end == Long.MAX_VALUE) |
| buf.append(String.valueOf(0)); |
| else |
| buf.append(String.valueOf(end - start)); |
| } |
| |
| @Override |
| public void indexOf(SQLBuffer buf, FilterValue str, FilterValue find, |
| FilterValue start) { |
| buf.append("LOCATE("); |
| find.appendTo(buf); |
| buf.append(", "); |
| str.appendTo(buf); |
| if (start != null) { |
| buf.append(", "); |
| start.appendTo(buf); |
| } |
| buf.append(")"); |
| } |
| |
| @Override |
| public String getPlaceholderValueString(Column col) { |
| String type = getTypeName(col.getType()); |
| int idx = type.indexOf("{0}"); |
| if (idx != -1) { |
| String pre = type.substring(0, idx); |
| if (type.length() > idx + 3) |
| type = pre + type.substring(idx + 3); |
| else |
| type = pre; |
| } |
| return "NULL AS " + type; |
| } |
| |
| @Override |
| public OpenJPAException newStoreException(String msg, SQLException[] causes, |
| Object failed) { |
| OpenJPAException ke = super.newStoreException(msg, causes, failed); |
| if (ke instanceof ReferentialIntegrityException |
| && causes[0].getErrorCode() == -violation_of_unique_index_or_constraint) { |
| ((ReferentialIntegrityException) ke).setIntegrityViolation |
| (ReferentialIntegrityException.IV_UNIQUE); |
| } |
| return ke; |
| } |
| } |