blob: 616641248275b27a421901593b8f762aef3aaecb [file] [log] [blame]
/*
* 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;
}
}