blob: 464bdcbd3847b8ad25f617089e56e5630547d839 [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.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.openjpa.jdbc.identifier.DBIdentifier;
import org.apache.openjpa.jdbc.kernel.exps.FilterValue;
import org.apache.openjpa.jdbc.kernel.exps.Lit;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.ForeignKey;
import org.apache.openjpa.jdbc.schema.Index;
import org.apache.openjpa.jdbc.schema.PrimaryKey;
import org.apache.openjpa.jdbc.schema.Schema;
import org.apache.openjpa.jdbc.schema.SchemaGroup;
import org.apache.openjpa.jdbc.schema.Sequence;
import org.apache.openjpa.jdbc.schema.Table;
import org.apache.openjpa.jdbc.schema.Unique;
import org.apache.openjpa.kernel.exps.Literal;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.util.StoreException;
import org.apache.openjpa.util.UserException;
/**
* Dictionary for SolidDB database.
*/
public class SolidDBDictionary
extends DBDictionary {
/**
* Sets whether tables are to be located in-memory or on disk.
* Creating in-memory tables should append "STORE MEMORY" to the
* "CREATE TABLE" statement. Creating disk-based tables should
* append "STORE DISK". Since cursor hold over commit can not apply
* to M-tables (which will cause SOLID Table Error 13187: The cursor
* cannot continue accessing M-tables after the transaction has committed
* or aborted. The statement must be re-executed.), the default is
* STORE DISK.
* The default concurrency control mechanism depends on the table type:
* Disk-based tables (D-tables) are by default optimistic.
* Main-memory tables (M-tables) are always pessimistic.
* Since OpenJPA applications expects lock waits (as usually is done with
* normal pessimistic databases), the server should be set to the pessimistic mode.
* The optimistic mode is about not waiting for the locks at all. That increases
* concurrency but requires more programming. The pessimistic mode with the
* READ COMMITTED isolation level (default) should get as much concurrency as one
* might need. The pessimistic locking mode can be set in solid.ini:
* [General]
* Pessimistic=yes
*
*
*/
public boolean storeIsMemory = false;
/**
* If true, then simulate auto-assigned values in SolidDB by
* using a trigger that inserts a sequence value into the
* primary key value when a row is inserted.
*/
public boolean useTriggersForAutoAssign = true;
/**
* The global sequence name to use for auto-assign simulation.
*/
public String autoAssignSequenceName = null;
/**
* Flag to use OpenJPA 0.3 style naming for auto assign sequence name and
* trigger name for backwards compatibility.
*/
public boolean openjpa3GeneratedKeyNames = false;
/**
* Possible values for LockingMode are "PESSIMISTIC" and "OPTIMISTIC"
*/
public String lockingMode = null;
private static final Localizer _loc = Localizer.forPackage
(SolidDBDictionary.class);
public SolidDBDictionary() {
platform = "SolidDB";
bitTypeName = "TINYINT";
blobTypeName = "LONG VARBINARY";
booleanTypeName = "TINYINT";
clobTypeName = "LONG VARCHAR";
doubleTypeName = "DOUBLE PRECISION";
allowsAliasInBulkClause = false;
useGetStringForClobs = true;
useSetStringForClobs = true;
supportsDeferredConstraints = false;
supportsNullUniqueColumn = false;
concatenateFunction = "CONCAT({0},{1})";
stringLengthFunction = "LENGTH({0})";
trimLeadingFunction = "LTRIM({0})";
trimTrailingFunction = "RTRIM({0})";
trimBothFunction = "TRIM({0})";
currentDateFunction = "CURDATE()";
currentTimeFunction = "CURTIME()";
currentTimestampFunction = "NOW()";
lastGeneratedKeyQuery = "SELECT {0}.CURRENT";
sequenceSQL = "SELECT SEQUENCE_SCHEMA, SEQUENCE_NAME FROM SYS_SEQUENCES";
sequenceSchemaSQL = "SEQSCHEMA = ?";
sequenceNameSQL = "SEQNAME = ?";
reservedWordSet.addAll(Arrays.asList(new String[]{
"BIGINT", "BINARY", "DATE", "TIME",
"TINYINT", "VARBINARY"
}));
}
@Override
public void endConfiguration() {
super.endConfiguration();
if (useTriggersForAutoAssign) {
supportsAutoAssign = true;
}
}
@Override
public String[] getCreateTableSQL(Table table, SchemaGroup group) {
StringBuilder buf = new StringBuilder();
buf.append("CREATE 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(") STORE ");
if (storeIsMemory)
buf.append("MEMORY");
else
buf.append("DISK");
String[] create = null;
if (lockingMode != null) {
StringBuilder buf1 = new StringBuilder();
if (lockingMode.equalsIgnoreCase("PESSIMISTIC")) {
buf1.append("ALTER TABLE ").append(getFullName(table, false)).
append(" SET PESSIMISTIC");
} else if (lockingMode.equalsIgnoreCase("OPTIMISTIC")){
buf1.append("ALTER TABLE ").append(getFullName(table, false)).
append(" SET OPTIMISTIC");
} else
throw new UserException(_loc.get("invalid-locking-mode", lockingMode));
create = new String[2];
create[0] = buf.toString();
create[1] = buf1.toString();
} else {
create = new String[1];
create[0] = buf.toString();
}
if (!useTriggersForAutoAssign)
return create;
List seqs = null;
String seq, trig;
for (int i = 0; cols != null && i < cols.length; i++) {
if (!cols[i].isAutoAssigned())
continue;
if (seqs == null)
seqs = new ArrayList(4);
seq = getAutoGenSeqName(cols[i]);
if (sequenceExists(table.getSchemaIdentifier().getName(), seq, group))
seqs.add("DROP SEQUENCE " + seq);
seqs.add("CREATE SEQUENCE " + seq);
if (openjpa3GeneratedKeyNames)
trig = getOpenJPA3GeneratedKeyTriggerName(cols[i]);
else
trig = getGeneratedKeyTriggerName(cols[i]);
// create the trigger that will insert new values into
// the table whenever a row is created
// CREATE TRIGGER TRIG01 ON table1
// BEFORE INSERT
// REFERENCING NEW COL1 AS NEW_COL1
// BEGIN
// EXEC SEQUENCE seq1 NEXT INTO NEW_COL1;
// END;
seqs.add("CREATE TRIGGER " + trig
+ " ON " + toDBName(table.getIdentifier())
+ " BEFORE INSERT REFERENCING NEW " + toDBName(cols[i].getIdentifier())
+ " AS NEW_COL1 BEGIN EXEC SEQUENCE " + seq + " NEXT INTO NEW_COL1; END");
}
if (seqs == null)
return create;
// combine create table sql and create sequences sql
String[] sql = new String[create.length + seqs.size()];
System.arraycopy(create, 0, sql, 0, create.length);
for (int i = 0; i < seqs.size(); i++)
sql[create.length + i] = (String) seqs.get(i);
return sql;
}
protected boolean sequenceExists(String schemaName, String seqName, SchemaGroup group) {
Schema[] schemas = group.getSchemas();
for (Schema schema : schemas) {
String dbSchemaName = schema.getIdentifier().getName();
if (schemaName != null && !schemaName.equalsIgnoreCase(dbSchemaName))
continue;
Sequence[] seqs = schema.getSequences();
for (Sequence seq : seqs) {
String dbSeqName = seq.getName();
if (dbSeqName != null && dbSeqName.equalsIgnoreCase(seqName))
return true;
}
}
return false;
}
/**
* Trigger name for simulating auto-assign values on the given column.
*/
protected String getGeneratedKeyTriggerName(Column col) {
// replace trailing _SEQ with _TRG
String seqName = getGeneratedKeySequenceName(col);
return seqName.substring(0, seqName.length() - 3) + "TRG";
}
/**
* Returns a OpenJPA 3-compatible name for an auto-assign sequence.
*/
protected String getOpenJPA3GeneratedKeySequenceName(Column col) {
Table table = col.getTable();
DBIdentifier sName = DBIdentifier.preCombine(table.getIdentifier(), "SEQ");
return toDBName(getNamingUtil().makeIdentifierValid(sName, table.getSchema().
getSchemaGroup(), maxTableNameLength, true));
}
/**
* Returns a OpenJPA 3-compatible name for an auto-assign trigger.
*/
protected String getOpenJPA3GeneratedKeyTriggerName(Column col) {
Table table = col.getTable();
DBIdentifier sName = DBIdentifier.preCombine(table.getIdentifier(), "TRIG");
return toDBName(getNamingUtil().makeIdentifierValid(sName, table.getSchema().
getSchemaGroup(), maxTableNameLength, true));
}
protected String getAutoGenSeqName(Column col) {
String seqName = autoAssignSequenceName;
if (seqName == null) {
if (openjpa3GeneratedKeyNames)
seqName = getOpenJPA3GeneratedKeySequenceName(col);
else
seqName = getGeneratedKeySequenceName(col);
}
return seqName;
}
@Override
protected String getGenKeySeqName(String query, Column col) {
return MessageFormat.format(query, new Object[]{getAutoGenSeqName(col)});
}
@Override
public String convertSchemaCase(DBIdentifier objectName) {
if (objectName != null && objectName.getName() == null)
return "";
return super.convertSchemaCase(objectName);
}
@Override
public void substring(SQLBuffer buf, FilterValue str, FilterValue start,
FilterValue length) {
if (length != null) {
super.substring(buf, str, start, length);
} else {
buf.append(substringFunctionName).append("(");
str.appendTo(buf);
buf.append(", ");
if (start.getValue() instanceof Number) {
buf.append(Long.toString(toLong(start)));
} else {
start.appendTo(buf);
}
buf.append(", ");
if (start.getValue() instanceof Number) {
long startLong = toLong(start);
long endLong = Integer.MAX_VALUE; //2G
buf.append(Long.toString(endLong - startLong));
} else {
buf.append(Integer.toString(Integer.MAX_VALUE));
buf.append(" - (");
start.appendTo(buf);
buf.append(")");
}
buf.append(")");
}
}
@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 boolean isSystemIndex(DBIdentifier name, Table table) {
// names starting with "$$" are reserved for SolidDB internal use
String strName = DBIdentifier.isNull(name) ? null : name.getName();
boolean startsWith$$ = false;
if (strName != null) {
startsWith$$ = name.isDelimited() ? strName.startsWith("\"$$") :
strName.startsWith("$$");
}
return super.isSystemIndex(name, table) || startsWith$$;
}
@Override
public boolean isSystemSequence(DBIdentifier name, DBIdentifier schema,
boolean targetSchema) {
if (super.isSystemSequence(name, schema, targetSchema))
return true;
String schemaName = DBIdentifier.isNull(schema) ? null : schema.getName();
boolean startsWith_SYSTEM = schema.isDelimited() ? schemaName.startsWith("\"_SYSTEM") :
schemaName.startsWith("_SYSTEM");
String seqName = DBIdentifier.isNull(name) ? null : name.getName();
boolean startsWithSYS_SEQ_ = name.isDelimited() ? seqName.startsWith("\"SYS_SEQ_") :
seqName.startsWith("SYS_SEQ_");
if (startsWith_SYSTEM && startsWithSYS_SEQ_)
return true;
return false;
}
@Override
public void setBigDecimal(PreparedStatement stmnt, int idx, BigDecimal val,
Column col) throws SQLException {
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:
setFloat(stmnt, idx, val.floatValue(), col);
break;
case JavaTypes.LONG:
case JavaTypes.LONG_OBJ:
setLong(stmnt, idx, val.longValue(), col);
break;
default:
super.setBigDecimal(stmnt, idx, val, col);
}
}
@Override
public void setDouble(PreparedStatement stmnt, int idx, double val,
Column col) throws SQLException {
int type = (col == null) ? JavaTypes.DOUBLE
: col.getJavaType();
switch (type) {
case JavaTypes.DOUBLE:
case JavaTypes.DOUBLE_OBJ:
super.setDouble(stmnt, idx, val, col);
break;
case JavaTypes.FLOAT:
case JavaTypes.FLOAT_OBJ:
setFloat(stmnt, idx, Double.valueOf(val).floatValue(), col);
break;
case JavaTypes.LONG:
case JavaTypes.LONG_OBJ:
setLong(stmnt, idx, Double.valueOf(val).longValue(), col);
break;
}
}
@Override
public boolean needsToCreateIndex(Index idx, Table table, Unique[] uniques) {
// SolidDB will automatically create a unique index for the
// constraint, so don't create another index again
PrimaryKey pk = table.getPrimaryKey();
if (pk != null && idx.columnsMatch(pk.getColumns()))
return false;
// If table1 has constraints on column (a, b), an explicit index on (a)
// will cause duplicate index error from SolidDB
Column[] icols = idx.getColumns();
boolean isDuplicate = false;
boolean mayBeDuplicate = false;
for (Unique unique : uniques) {
Column[] ucols = unique.getColumns();
if (ucols.length < icols.length)
continue;
for (int j = 0, k = 0; j < ucols.length && k < icols.length; j++, k++) {
if (mayBeDuplicate && ucols[j].getQualifiedPath().equals(icols[k].getQualifiedPath())) {
if (k == icols.length - 1) {
isDuplicate = true;
}
else {
mayBeDuplicate = true;
}
}
else
mayBeDuplicate = false;
}
if (isDuplicate)
break;
}
return isDuplicate;
}
@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(sequenceSQL);
if (!DBIdentifier.isNull(schemaName) || !DBIdentifier.isNull(sequenceName))
buf.append(" WHERE ");
if (!DBIdentifier.isNull(schemaName)) {
buf.append(sequenceSchemaSQL);
if (!DBIdentifier.isNull(sequenceName))
buf.append(" AND ");
}
if (!DBIdentifier.isNull(sequenceName))
buf.append(sequenceNameSQL);
return buf.toString();
}
@Override
protected void appendSelect(SQLBuffer selectSQL, Object alias, Select sel,
int idx) {
// if this is a literal value, add a cast...
Object val = sel.getSelects().get(idx);
boolean toCast = (val instanceof Lit) &&
((Lit)val).getParseType() != Literal.TYPE_DATE &&
((Lit)val).getParseType() != Literal.TYPE_TIME &&
((Lit)val).getParseType() != Literal.TYPE_TIMESTAMP;
if (toCast)
selectSQL.append("CAST(");
// ... and add the select per super's behavior...
super.appendSelect(selectSQL, alias, sel, idx);
// ... and finish the cast
if (toCast) {
Class c = ((Lit) val).getType();
int javaTypeCode = JavaTypes.getTypeCode(c);
int jdbcTypeCode = getJDBCType(javaTypeCode, false);
String typeName = getTypeName(jdbcTypeCode);
selectSQL.append(" AS " + typeName);
// if the literal is a string, use the default char col size
// in the cast statement.
if (String.class.equals(c))
selectSQL.append("(" + characterColumnSize + ")");
selectSQL.append(")");
}
}
/**
* Solid does not support deferred referential integrity checking.
*/
@Override
protected ForeignKey newForeignKey(ResultSet fkMeta)
throws SQLException {
ForeignKey fk = super.newForeignKey(fkMeta);
fk.setDeferred(false);
return fk;
}
@Override
public boolean isFatalException(int subtype, SQLException ex) {
String errorState = ex.getSQLState();
int errorCode = ex.getErrorCode();
if (subtype == StoreException.LOCK && errorCode == 14529 && "HY000".equals(errorState))
return false;
return super.isFatalException(subtype, ex);
}
}