blob: 2714a84482e17fc4b69d6b9b2f1be8a467bd6987 [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.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
import org.apache.openjpa.jdbc.kernel.exps.FilterValue;
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.Sequence;
import org.apache.openjpa.jdbc.schema.Table;
import org.apache.openjpa.lib.jdbc.DelegatingDatabaseMetaData;
import org.apache.openjpa.lib.jdbc.DelegatingPreparedStatement;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.util.StoreException;
import org.apache.openjpa.util.UserException;
import serp.util.Numbers;
/**
* Dictionary for Oracle.
*/
public class OracleDictionary
extends DBDictionary {
public static final String SELECT_HINT = "openjpa.hint.OracleSelectHint";
public static final String VENDOR_ORACLE = "oracle";
private static final int BEHAVE_OTHER = 0;
private static final int BEHAVE_ORACLE = 1;
private static final int BEHAVE_DATADIRECT31 = 2;
private static Blob EMPTY_BLOB = null;
private static Clob EMPTY_CLOB = null;
private static final Localizer _loc = Localizer.forPackage
(OracleDictionary.class);
/**
* If true, then simulate auto-assigned values in Oracle by
* using a trigger that inserts a sequence value into the
* primary key value when a row is inserted.
*/
public boolean useTriggersForAutoAssign = false;
/**
* The global sequence name to use for autoassign 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;
/**
* If true, then OpenJPA will attempt to use the special
* OraclePreparedStatement.setFormOfUse method to
* configure statements that it detects are operating on unicode fields.
*/
public boolean useSetFormOfUseForUnicode = true;
// some oracle drivers have problems with select for update; warn the
// first time locking is attempted
private boolean _checkedUpdateBug = false;
private boolean _warnedCharColumn = false;
private boolean _warnedNcharColumn = false;
private int _driverBehavior = -1;
// cache lob methods
private Method _putBytes = null;
private Method _putString = null;
private Method _putChars = null;
// batch limit
private int defaultBatchLimit = 100;
public OracleDictionary() {
platform = "Oracle";
validationSQL = "SELECT SYSDATE FROM DUAL";
nextSequenceQuery = "SELECT {0}.NEXTVAL FROM DUAL";
stringLengthFunction = "LENGTH({0})";
joinSyntax = SYNTAX_DATABASE;
maxTableNameLength = 30;
maxColumnNameLength = 30;
maxIndexNameLength = 30;
maxConstraintNameLength = 30;
maxEmbeddedBlobSize = 4000;
maxEmbeddedClobSize = 4000;
inClauseLimit = 1000;
supportsDeferredConstraints = true;
supportsLockingWithDistinctClause = false;
supportsSelectStartIndex = true;
supportsSelectEndIndex = true;
systemSchemaSet.addAll(Arrays.asList(new String[]{
"CTXSYS", "MDSYS", "SYS", "SYSTEM", "WKSYS", "WMSYS", "XDB",
}));
supportsXMLColumn = true;
xmlTypeName = "XMLType";
bigintTypeName = "NUMBER{0}";
bitTypeName = "NUMBER{0}";
decimalTypeName = "NUMBER{0}";
doubleTypeName = "NUMBER{0}";
integerTypeName = "NUMBER{0}";
numericTypeName = "NUMBER{0}";
smallintTypeName = "NUMBER{0}";
tinyintTypeName = "NUMBER{0}";
longVarcharTypeName = "LONG";
binaryTypeName = "BLOB";
varbinaryTypeName = "BLOB";
longVarbinaryTypeName = "BLOB";
timeTypeName = "DATE";
varcharTypeName = "VARCHAR2{0}";
fixedSizeTypeNameSet.addAll(Arrays.asList(new String[]{
"LONG RAW", "RAW", "LONG", "REF",
}));
reservedWordSet.addAll(Arrays.asList(new String[]{
"ACCESS", "AUDIT", "CLUSTER", "COMMENT", "COMPRESS", "EXCLUSIVE",
"FILE", "IDENTIFIED", "INCREMENT", "INDEX", "INITIAL", "LOCK",
"LONG", "MAXEXTENTS", "MINUS", "MODE", "NOAUDIT", "NOCOMPRESS",
"NOWAIT", "OFFLINE", "ONLINE", "PCTFREE", "ROW",
}));
substringFunctionName = "SUBSTR";
super.setBatchLimit(defaultBatchLimit);
selectWordSet.add("WITH");
reportsSuccessNoInfoOnBatchUpdates = true;
}
public void endConfiguration() {
super.endConfiguration();
if (useTriggersForAutoAssign)
supportsAutoAssign = true;
}
public void connectedConfiguration(Connection conn)
throws SQLException {
super.connectedConfiguration(conn);
if (driverVendor == null) {
DatabaseMetaData meta = conn.getMetaData();
String url = (meta.getURL() == null) ? "" : meta.getURL();
String driverName = meta.getDriverName();
String metadataClassName;
if (meta instanceof DelegatingDatabaseMetaData)
metadataClassName = ((DelegatingDatabaseMetaData) meta).
getInnermostDelegate().getClass().getName();
else
metadataClassName = meta.getClass().getName();
// check both the driver class name and the URL for known patterns
if (metadataClassName.startsWith("oracle.")
|| url.indexOf("jdbc:oracle:") != -1
|| "Oracle JDBC driver".equals(driverName)) {
driverVendor = VENDOR_ORACLE + meta.getDriverMajorVersion()
+ meta.getDriverMinorVersion();
String productVersion = meta.getDatabaseProductVersion()
.split("Release ",0)[1].split("\\.",0)[0];
int release = Integer.parseInt(productVersion);
// warn sql92
if (release <= 8) {
if (joinSyntax == SYNTAX_SQL92 && log.isWarnEnabled())
log.warn(_loc.get("oracle-syntax"));
joinSyntax = SYNTAX_DATABASE;
dateTypeName = "DATE"; // added oracle 9
timestampTypeName = "DATE"; // added oracle 9
supportsXMLColumn = false;
}
// select of an xml column requires ".getStringVal()"
// suffix. eg. t0.xmlcol.getStringVal()
getStringVal = ".getStringVal()";
} else if (metadataClassName.startsWith("com.ddtek.")
|| url.indexOf("jdbc:datadirect:oracle:") != -1
|| "Oracle".equals(driverName)) {
driverVendor = VENDOR_DATADIRECT + meta.getDriverMajorVersion()
+ meta.getDriverMinorVersion();
} else
driverVendor = VENDOR_OTHER;
}
cacheDriverBehavior(driverVendor);
}
/**
* Cache constant for drivers with behaviors we have to deal with.
*/
private void cacheDriverBehavior(String driverVendor) {
if (_driverBehavior != -1)
return;
driverVendor = driverVendor.toLowerCase();
if (driverVendor.startsWith(VENDOR_ORACLE))
_driverBehavior = BEHAVE_ORACLE;
else if (driverVendor.equals(VENDOR_DATADIRECT + "30")
|| driverVendor.equals(VENDOR_DATADIRECT + "31"))
_driverBehavior = BEHAVE_DATADIRECT31;
else
_driverBehavior = BEHAVE_OTHER;
}
/**
* Ensure that the driver vendor has been set, and if not, set it now.
*/
public void ensureDriverVendor() {
if (driverVendor != null) {
cacheDriverBehavior(driverVendor);
return;
}
if (log.isInfoEnabled())
log.info(_loc.get("oracle-connecting-for-driver"));
Connection conn = null;
try {
conn = conf.getDataSource2(null).getConnection();
connectedConfiguration(conn);
} catch (SQLException se) {
throw SQLExceptions.getStore(se, this);
} finally {
if (conn != null)
try {
conn.close();
} catch (SQLException se) {
}
}
}
public boolean supportsLocking(Select sel) {
if (!super.supportsLocking(sel))
return false;
return !requiresSubselectForRange(sel.getStartIndex(),
sel.getEndIndex(), sel.isDistinct(), sel.getOrdering());
}
protected SQLBuffer getSelects(Select sel, boolean distinctIdentifiers,
boolean forUpdate) {
// if range doesn't require a subselect can use super
if (!requiresSubselectForRange(sel.getStartIndex(),
sel.getEndIndex(), sel.isDistinct(), sel.getOrdering()))
return super.getSelects(sel, distinctIdentifiers, forUpdate);
// if there are no joins involved or we're using a from select so
// that all cols already have unique aliases, can use super
if (sel.getFromSelect() != null || sel.getTableAliases().size() < 2)
return super.getSelects(sel, distinctIdentifiers, forUpdate);
// since none of the conditions above were met, we're dealing with
// a select that uses joins and requires subselects to select the
// proper range; alias all column values so that they are unique within
// the subselect
SQLBuffer selectSQL = new SQLBuffer(this);
List aliases;
if (distinctIdentifiers)
aliases = sel.getIdentifierAliases();
else
aliases = sel.getSelectAliases();
Object alias;
int i = 0;
for (Iterator itr = aliases.iterator(); itr.hasNext(); i++) {
alias = itr.next();
if (alias instanceof SQLBuffer)
selectSQL.append((SQLBuffer) alias);
else
selectSQL.append(alias.toString());
selectSQL.append(" AS c").append(String.valueOf(i));
if (itr.hasNext())
selectSQL.append(", ");
}
return selectSQL;
}
public boolean canOuterJoin(int syntax, ForeignKey fk) {
if (!super.canOuterJoin(syntax, fk))
return false;
if (fk != null && syntax == SYNTAX_DATABASE) {
if (fk.getConstants().length > 0)
return false;
if (fk.getPrimaryKeyConstants().length > 0)
return false;
}
return true;
}
public SQLBuffer toNativeJoin(Join join) {
if (join.getType() != Join.TYPE_OUTER)
return toTraditionalJoin(join);
ForeignKey fk = join.getForeignKey();
if (fk == null)
return null;
boolean inverse = join.isForeignKeyInversed();
Column[] from = (inverse) ? fk.getPrimaryKeyColumns()
: fk.getColumns();
Column[] to = (inverse) ? fk.getColumns()
: fk.getPrimaryKeyColumns();
// do column joins
SQLBuffer buf = new SQLBuffer(this);
int count = 0;
for (int i = 0; i < from.length; i++, count++) {
if (count > 0)
buf.append(" AND ");
buf.append(join.getAlias1()).append(".").append(from[i]);
buf.append(" = ");
buf.append(join.getAlias2()).append(".").append(to[i]);
buf.append("(+)");
}
// check constant joins
if (fk.getConstantColumns().length > 0)
throw new StoreException(_loc.get("oracle-constant",
join.getTable1(), join.getTable2())).setFatal(true);
if (fk.getConstantPrimaryKeyColumns().length > 0)
throw new StoreException(_loc.get("oracle-constant",
join.getTable1(), join.getTable2())).setFatal(true);
return buf;
}
public SQLBuffer toSelect(SQLBuffer select, JDBCFetchConfiguration fetch,
SQLBuffer tables, SQLBuffer where, SQLBuffer group,
SQLBuffer having, SQLBuffer order,
boolean distinct, boolean forUpdate, long start, long end,
Select sel) {
if (!_checkedUpdateBug) {
ensureDriverVendor();
if (forUpdate && _driverBehavior == BEHAVE_DATADIRECT31)
log.warn(_loc.get("dd-lock-bug"));
_checkedUpdateBug = true;
}
// if no range, use standard select
if (start == 0 && end == Long.MAX_VALUE)
return super.toSelect(select, fetch, tables, where, group, having,
order, distinct, forUpdate, 0, Long.MAX_VALUE, sel);
// if no skip, ordering, or distinct can use rownum directly
SQLBuffer buf = new SQLBuffer(this);
if (!requiresSubselectForRange(start, end, distinct, order)) {
if (where != null && !where.isEmpty())
buf.append(where).append(" AND ");
buf.append("ROWNUM <= ").appendValue(end);
return super.toSelect(select, fetch, tables, buf, group, having,
order, distinct, forUpdate, 0, Long.MAX_VALUE, sel);
}
// if there is ordering, skip, or distinct we have to use subselects
SQLBuffer newsel = super.toSelect(select, fetch, tables, where,
group, having, order, distinct, forUpdate, 0, Long.MAX_VALUE,
sel);
// if no skip, can use single nested subselect
if (start == 0) {
buf.append(getSelectOperation(fetch) + " * FROM (");
buf.append(newsel);
buf.append(") WHERE ROWNUM <= ").appendValue(end);
return buf;
}
// with a skip, we have to use a double-nested subselect to put
// where conditions on the rownum
buf.append(getSelectOperation(fetch)
+ " * FROM (SELECT r.*, ROWNUM RNUM FROM (");
buf.append(newsel);
buf.append(") r");
if (end != Long.MAX_VALUE)
buf.append(" WHERE ROWNUM <= ").appendValue(end);
buf.append(") WHERE RNUM > ").appendValue(start);
return buf;
}
/**
* Return true if the select with the given parameters needs a
* subselect to apply a range.
*/
private boolean requiresSubselectForRange(long start, long end,
boolean distinct, SQLBuffer order) {
if (start == 0 && end == Long.MAX_VALUE)
return false;
return start != 0 || distinct || (order != null && !order.isEmpty());
}
/**
* Check to see if we have set the {@link #SELECT_HINT} in the
* fetch configuraiton, and if so, append the Orache hint after the
* "SELECT" part of the query.
*/
public String getSelectOperation(JDBCFetchConfiguration fetch) {
Object hint = fetch == null ? null : fetch.getHint(SELECT_HINT);
String select = "SELECT";
if (hint != null)
select += " " + hint;
return select;
}
public void setString(PreparedStatement stmnt, int idx, String val,
Column col)
throws SQLException {
// oracle NCHAR/NVARCHAR/NCLOB unicode columns require some
// special handling to configure them correctly; see:
// http://www.oracle.com/technology/sample_code/tech/java/
// sqlj_jdbc/files/9i_jdbc/NCHARsupport4UnicodeSample/Readme.html
String typeName = (col == null) ? null : col.getTypeName();
if (useSetFormOfUseForUnicode && typeName != null &&
(typeName.toLowerCase().startsWith("nvarchar") ||
typeName.toLowerCase().startsWith("nchar") ||
typeName.toLowerCase().startsWith("nclob"))) {
Statement inner = stmnt;
if (inner instanceof DelegatingPreparedStatement)
inner = ((DelegatingPreparedStatement) inner).
getInnermostDelegate();
if (isOraclePreparedStatement(inner)) {
try {
inner.getClass().getMethod("setFormOfUse",
new Class[]{ int.class, short.class }).
invoke(inner,
new Object[]{
new Integer(idx),
Class.forName
("oracle.jdbc.OraclePreparedStatement").
getField("FORM_NCHAR").get(null)
});
} catch (Exception e) {
log.warn(e);
}
} else if (!_warnedNcharColumn && log.isWarnEnabled()) {
_warnedNcharColumn = true;
log.warn(_loc.get("unconfigured-nchar-cols"));
}
}
// call setFixedCHAR for fixed width character columns to get padding
// semantics
if (col != null && col.getType() == Types.CHAR
&& val != null && val.length() != col.getSize()) {
Statement inner = stmnt;
if (inner instanceof DelegatingPreparedStatement)
inner = ((DelegatingPreparedStatement) inner).
getInnermostDelegate();
if (isOraclePreparedStatement(inner)) {
try {
inner.getClass().getMethod("setFixedCHAR",
new Class[]{ int.class, String.class }).
invoke(inner, new Object[]{ new Integer(idx), val });
return;
} catch (Exception e) {
log.warn(e);
}
}
if (!_warnedCharColumn && log.isWarnEnabled()) {
_warnedCharColumn = true;
log.warn(_loc.get("unpadded-char-cols"));
}
}
super.setString(stmnt, idx, val, col);
}
public void setNull(PreparedStatement stmnt, int idx, int colType,
Column col)
throws SQLException {
if ((colType == Types.CLOB || colType == Types.BLOB) && col.isNotNull())
throw new UserException(_loc.get("null-blob-in-not-nullable", col
.getFullName()));
if (colType == Types.BLOB && _driverBehavior == BEHAVE_ORACLE)
stmnt.setBlob(idx, getEmptyBlob());
else if (colType == Types.CLOB && _driverBehavior == BEHAVE_ORACLE
&& !col.isXML())
stmnt.setClob(idx, getEmptyClob());
else if ((colType == Types.STRUCT || colType == Types.OTHER)
&& col != null && col.getTypeName() != null)
stmnt.setNull(idx, Types.STRUCT, col.getTypeName());
// some versions of the Oracle JDBC driver will fail if calling
// setNull with DATE; see bug #1171
else if (colType == Types.DATE)
super.setNull(stmnt, idx, Types.TIMESTAMP, col);
// the Oracle driver does not support Types.OTHER with setNull
else if (colType == Types.OTHER || col.isXML())
super.setNull(stmnt, idx, Types.NULL, col);
else
super.setNull(stmnt, idx, colType, col);
}
public String getClobString(ResultSet rs, int column)
throws SQLException {
if (_driverBehavior != BEHAVE_ORACLE)
return super.getClobString(rs, column);
Clob clob = getClob(rs, column);
if (clob == null)
return null;
if (clob.getClass().getName().equals("oracle.sql.CLOB")) {
try {
if (((Boolean) Class.forName("oracle.sql.CLOB").
getMethod("isEmptyLob", new Class[0]).
invoke(clob, new Object[0])).
booleanValue())
return null;
} catch (Exception e) {
// possibly different version of the driver
}
}
if (clob.length() == 0)
return null;
// unlikely that we'll have strings over 4 billion chars
return clob.getSubString(1, (int) clob.length());
}
public Timestamp getTimestamp(ResultSet rs, int column, Calendar cal)
throws SQLException {
if (cal == null) {
try {
return super.getTimestamp(rs, column, cal);
} catch (ArrayIndexOutOfBoundsException ae) {
// CR295604: issue a warning this this bug can be gotten
// around with SupportsTimestampNanos=false
log.warn(_loc.get("oracle-timestamp-bug"), ae);
throw ae;
}
}
// handle Oracle bug where nanos not returned from call with Calendar
// parameter
Timestamp ts = rs.getTimestamp(column, cal);
if (ts != null && ts.getNanos() == 0)
ts.setNanos(rs.getTimestamp(column).getNanos());
return ts;
}
public Object getObject(ResultSet rs, int column, Map map)
throws SQLException {
// recent oracle drivers return oracle-specific types for timestamps
// and dates
Object obj = super.getObject(rs, column, map);
if (obj == null)
return null;
if ("oracle.sql.DATE".equals(obj.getClass().getName()))
obj = convertFromOracleType(obj, "dateValue");
else if ("oracle.sql.TIMESTAMP".equals(obj.getClass().getName()))
obj = convertFromOracleType(obj, "timestampValue");
return obj;
}
/**
* Convert an object from its proprietary Oracle type to the standard
* Java type.
*/
private static Object convertFromOracleType(Object obj,
String convertMethod)
throws SQLException {
try {
Method m = obj.getClass().getMethod(convertMethod, (Class[]) null);
return m.invoke(obj, (Object[]) null);
} catch (Throwable t) {
if (t instanceof InvocationTargetException)
t = ((InvocationTargetException) t).getTargetException();
if (t instanceof SQLException)
throw(SQLException) t;
throw new SQLException(t.getMessage());
}
}
public Column[] getColumns(DatabaseMetaData meta, String catalog,
String schemaName, String tableName, String 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 (cols[i].getTypeName() == null)
continue;
if (cols[i].getTypeName().toUpperCase().startsWith("TIMESTAMP"))
cols[i].setType(Types.TIMESTAMP);
else if ("BLOB".equalsIgnoreCase(cols[i].getTypeName()))
cols[i].setType(Types.BLOB);
else if ("CLOB".equalsIgnoreCase(cols[i].getTypeName())
|| "NCLOB".equalsIgnoreCase(cols[i].getTypeName()))
cols[i].setType(Types.CLOB);
else if ("FLOAT".equalsIgnoreCase(cols[i].getTypeName()))
cols[i].setType(Types.FLOAT);
else if ("NVARCHAR".equalsIgnoreCase(cols[i].getTypeName()))
cols[i].setType(Types.VARCHAR);
else if ("NCHAR".equalsIgnoreCase(cols[i].getTypeName()))
cols[i].setType(Types.CHAR);
}
return cols;
}
public PrimaryKey[] getPrimaryKeys(DatabaseMetaData meta,
String catalog, String schemaName, String tableName, Connection conn)
throws SQLException {
StringBuffer buf = new StringBuffer();
buf.append("SELECT t0.OWNER AS TABLE_SCHEM, ").
append("t0.TABLE_NAME AS TABLE_NAME, ").
append("t0.COLUMN_NAME AS COLUMN_NAME, ").
append("t0.CONSTRAINT_NAME AS PK_NAME ").
append("FROM ALL_CONS_COLUMNS t0, ALL_CONSTRAINTS t1 ").
append("WHERE t0.OWNER = t1.OWNER ").
append("AND t0.CONSTRAINT_NAME = t1.CONSTRAINT_NAME ").
append("AND t1.CONSTRAINT_TYPE = 'P'");
if (schemaName != null)
buf.append(" AND t0.OWNER = ?");
if (tableName != null)
buf.append(" AND t0.TABLE_NAME = ?");
PreparedStatement stmnt = conn.prepareStatement(buf.toString());
ResultSet rs = null;
try {
int idx = 1;
if (schemaName != null)
setString(stmnt, idx++, schemaName.toUpperCase(), null);
if (tableName != null)
setString(stmnt, idx++, tableName.toUpperCase(), null);
rs = stmnt.executeQuery();
List pkList = new ArrayList();
while (rs != null && rs.next())
pkList.add(newPrimaryKey(rs));
return (PrimaryKey[]) pkList.toArray
(new PrimaryKey[pkList.size()]);
} finally {
if (rs != null)
try {
rs.close();
} catch (Exception e) {
}
try {
stmnt.close();
} catch (Exception e) {
}
}
}
public Index[] getIndexInfo(DatabaseMetaData meta, String catalog,
String schemaName, String tableName, boolean unique, boolean approx,
Connection conn)
throws SQLException {
StringBuffer buf = new StringBuffer();
buf.append("SELECT t0.INDEX_OWNER AS TABLE_SCHEM, ").
append("t0.TABLE_NAME AS TABLE_NAME, ").
append("DECODE(t1.UNIQUENESS, 'UNIQUE', 0, 'NONUNIQUE', 1) ").
append("AS NON_UNIQUE, ").
append("t0.INDEX_NAME AS INDEX_NAME, ").
append("t0.COLUMN_NAME AS COLUMN_NAME ").
append("FROM ALL_IND_COLUMNS t0, ALL_INDEXES t1 ").
append("WHERE t0.INDEX_OWNER = t1.OWNER ").
append("AND t0.INDEX_NAME = t1.INDEX_NAME");
if (schemaName != null)
buf.append(" AND t0.TABLE_OWNER = ?");
if (tableName != null)
buf.append(" AND t0.TABLE_NAME = ?");
PreparedStatement stmnt = conn.prepareStatement(buf.toString());
ResultSet rs = null;
try {
int idx = 1;
if (schemaName != null)
setString(stmnt, idx++, schemaName.toUpperCase(), null);
if (tableName != null)
setString(stmnt, idx++, tableName.toUpperCase(), null);
rs = stmnt.executeQuery();
List idxList = new ArrayList();
while (rs != null && rs.next())
idxList.add(newIndex(rs));
return (Index[]) idxList.toArray(new Index[idxList.size()]);
} finally {
if (rs != null)
try {
rs.close();
} catch (Exception e) {
}
try {
stmnt.close();
} catch (Exception e) {
}
}
}
public ForeignKey[] getImportedKeys(DatabaseMetaData meta, String catalog,
String schemaName, String tableName, Connection conn)
throws SQLException {
StringBuffer delAction = new StringBuffer("DECODE(t1.DELETE_RULE").
append(", 'NO ACTION', ").append(meta.importedKeyNoAction).
append(", 'RESTRICT', ").append(meta.importedKeyRestrict).
append(", 'CASCADE', ").append(meta.importedKeyCascade).
append(", 'SET NULL', ").append(meta.importedKeySetNull).
append(", 'SET DEFAULT', ").append(meta.importedKeySetDefault).
append(")");
StringBuffer buf = new StringBuffer();
buf.append("SELECT t2.OWNER AS PKTABLE_SCHEM, ").
append("t2.TABLE_NAME AS PKTABLE_NAME, ").
append("t2.COLUMN_NAME AS PKCOLUMN_NAME, ").
append("t0.OWNER AS FKTABLE_SCHEM, ").
append("t0.TABLE_NAME AS FKTABLE_NAME, ").
append("t0.COLUMN_NAME AS FKCOLUMN_NAME, ").
append("t0.POSITION AS KEY_SEQ, ").
append(delAction).append(" AS DELETE_RULE, ").
append("t0.CONSTRAINT_NAME AS FK_NAME, ").
append("DECODE(t1.DEFERRED, 'DEFERRED', ").
append(meta.importedKeyInitiallyDeferred).
append(", 'IMMEDIATE', ").
append(meta.importedKeyInitiallyImmediate).
append(") AS DEFERRABILITY ").
append("FROM ALL_CONS_COLUMNS t0, ALL_CONSTRAINTS t1, ").
append("ALL_CONS_COLUMNS t2 ").
append("WHERE t0.OWNER = t1.OWNER ").
append("AND t0.CONSTRAINT_NAME = t1.CONSTRAINT_NAME ").
append("AND t1.CONSTRAINT_TYPE = 'R' ").
append("AND t1.R_OWNER = t2.OWNER ").
append("AND t1.R_CONSTRAINT_NAME = t2.CONSTRAINT_NAME ").
append("AND t0.POSITION = t2.POSITION");
if (schemaName != null)
buf.append(" AND t0.OWNER = ?");
if (tableName != null)
buf.append(" AND t0.TABLE_NAME = ?");
buf.append(" ORDER BY t2.OWNER, t2.TABLE_NAME, t0.POSITION");
PreparedStatement stmnt = conn.prepareStatement(buf.toString());
ResultSet rs = null;
try {
int idx = 1;
if (schemaName != null)
setString(stmnt, idx++, schemaName.toUpperCase(), null);
if (tableName != null)
setString(stmnt, idx++, tableName.toUpperCase(), null);
rs = stmnt.executeQuery();
List fkList = new ArrayList();
while (rs != null && rs.next())
fkList.add(newForeignKey(rs));
return (ForeignKey[]) fkList.toArray
(new ForeignKey[fkList.size()]);
} finally {
if (rs != null)
try {
rs.close();
} catch (Exception e) {
}
try {
stmnt.close();
} catch (Exception e) {
}
}
}
public String[] getCreateTableSQL(Table table) {
// only override if we are simulating auto-incremenet with triggers
String[] create = super.getCreateTableSQL(table);
if (!useTriggersForAutoAssign)
return create;
Column[] cols = table.getColumns();
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 = autoAssignSequenceName;
if (seq == null) {
if (openjpa3GeneratedKeyNames)
seq = getOpenJPA3GeneratedKeySequenceName(cols[i]);
else
seq = getGeneratedKeySequenceName(cols[i]);
seqs.add("CREATE SEQUENCE " + seq + " START WITH 1");
}
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
seqs.add("CREATE OR REPLACE TRIGGER " + trig
+ " BEFORE INSERT ON " + table.getName()
+ " FOR EACH ROW BEGIN SELECT " + seq + ".nextval INTO "
+ ":new." + cols[i].getName() + " FROM DUAL; "
+ "END " + trig + ";");
}
if (seqs == null)
return create;
// combine create table sql and create seqences 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;
}
public String[] getCreateSequenceSQL(Sequence seq) {
String[] sql = super.getCreateSequenceSQL(seq);
if (seq.getAllocate() > 1)
sql[0] += " CACHE " + seq.getAllocate();
return sql;
}
protected String getSequencesSQL(String schemaName, String sequenceName) {
StringBuffer buf = new StringBuffer();
buf.append("SELECT SEQUENCE_OWNER AS SEQUENCE_SCHEMA, ").
append("SEQUENCE_NAME FROM ALL_SEQUENCES");
if (schemaName != null || sequenceName != null)
buf.append(" WHERE ");
if (schemaName != null) {
buf.append("SEQUENCE_OWNER = ?");
if (sequenceName != null)
buf.append(" AND ");
}
if (sequenceName != null)
buf.append("SEQUENCE_NAME = ?");
return buf.toString();
}
public boolean isSystemSequence(String name, String schema,
boolean targetSchema) {
if (super.isSystemSequence(name, schema, targetSchema))
return true;
// filter out generated sequences used for auto-assign
return (autoAssignSequenceName != null
&& name.equalsIgnoreCase(autoAssignSequenceName))
|| (autoAssignSequenceName == null
&& name.toUpperCase().startsWith("ST_"));
}
public Object getGeneratedKey(Column col, Connection conn)
throws SQLException {
if (!useTriggersForAutoAssign)
return Numbers.valueOf(0L);
// if we simulate auto-assigned columns using triggers and
// sequences, then return the current value of the sequence
// from autoAssignSequenceName
String seq = autoAssignSequenceName;
if (seq == null && openjpa3GeneratedKeyNames)
seq = getOpenJPA3GeneratedKeySequenceName(col);
else if (seq == null)
seq = getGeneratedKeySequenceName(col);
PreparedStatement stmnt = conn.prepareStatement("SELECT " + seq
+ ".currval FROM DUAL");
ResultSet rs = null;
try {
rs = stmnt.executeQuery();
rs.next();
return Numbers.valueOf(rs.getLong(1));
} finally {
if (rs != null)
try { rs.close(); } catch (SQLException se) {}
try { stmnt.close(); } catch (SQLException se) {}
}
}
/**
* 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();
return makeNameValid("SEQ_" + table.getName(), table.getSchema().
getSchemaGroup(), maxTableNameLength, NAME_ANY);
}
/**
* Returns a OpenJPA 3-compatible name for an auto-assign trigger.
*/
protected String getOpenJPA3GeneratedKeyTriggerName(Column col) {
Table table = col.getTable();
return makeNameValid("TRIG_" + table.getName(), table.getSchema().
getSchemaGroup(), maxTableNameLength, NAME_ANY);
}
/**
* Invoke Oracle's <code>putBytes</code> method on the given BLOB object.
* Uses reflection in case the blob is wrapped in another
* vendor-specific class; for example Weblogic wraps oracle thin driver
* lobs in its own interfaces with the same methods.
*/
public void putBytes(Object blob, byte[] data)
throws SQLException {
if (blob == null)
return;
if (_putBytes == null) {
try {
_putBytes = blob.getClass().getMethod("putBytes",
new Class[]{ long.class, byte[].class });
} catch (Exception e) {
throw new StoreException(e);
}
}
invokePutLobMethod(_putBytes, blob, data);
}
/**
* Invoke Oracle's <code>putString</code> method on the given CLOB object.
* Uses reflection in case the clob is wrapped in another
* vendor-specific class; for example Weblogic wraps oracle thin driver
* lobs in its own interfaces with the same methods.
*/
public void putString(Object clob, String data)
throws SQLException {
if (_putString == null) {
try {
_putString = clob.getClass().getMethod("putString",
new Class[]{ long.class, String.class });
} catch (Exception e) {
throw new StoreException(e);
}
}
invokePutLobMethod(_putString, clob, data);
}
/**
* Invoke Oracle's <code>putChars</code> method on the given CLOB
* object. Uses reflection in case the clob is wrapped in another
* vendor-specific class; for example Weblogic wraps oracle thin driver
* lobs in its own interfaces with the same methods.
*/
public void putChars(Object clob, char[] data)
throws SQLException {
if (_putChars == null) {
try {
_putChars = clob.getClass().getMethod("putChars",
new Class[]{ long.class, char[].class });
} catch (Exception e) {
throw new StoreException(e);
}
}
invokePutLobMethod(_putChars, clob, data);
}
/**
* Invoke the given LOB method on the given target with the given data.
*/
private static void invokePutLobMethod(Method method, Object target,
Object data)
throws SQLException {
try {
method.invoke(target, new Object[]{ Numbers.valueOf(1L), data });
} catch (InvocationTargetException ite) {
Throwable t = ite.getTargetException();
if (t instanceof SQLException)
throw(SQLException) t;
throw new StoreException(t);
} catch (Exception e) {
throw new StoreException(e);
}
}
private Clob getEmptyClob()
throws SQLException {
if (EMPTY_CLOB != null)
return EMPTY_CLOB;
try {
return EMPTY_CLOB = (Clob) Class.forName("oracle.sql.CLOB",true,
AccessController.doPrivileged(J2DoPrivHelper
.getContextClassLoaderAction())).
getMethod("empty_lob", new Class[0]).
invoke(null, new Object[0]);
} catch (Exception e) {
throw new SQLException(e.getMessage());
}
}
private Blob getEmptyBlob()
throws SQLException {
if (EMPTY_BLOB != null)
return EMPTY_BLOB;
try {
return EMPTY_BLOB = (Blob) Class.forName("oracle.sql.BLOB",true,
AccessController.doPrivileged(J2DoPrivHelper
.getContextClassLoaderAction())).
getMethod("empty_lob", new Class[0]).
invoke(null, new Object[0]);
} catch (Exception e) {
throw new SQLException(e.getMessage());
}
}
private static boolean isOraclePreparedStatement(Statement stmnt) {
try {
return Class.forName("oracle.jdbc.OraclePreparedStatement").
isInstance(stmnt);
} catch (Exception e) {
return false;
}
}
/**
* If this dictionary supports XML type,
* use this method to append xml predicate.
*
* @param buf the SQL buffer to write the comparison
* @param op the comparison operation to perform
* @param lhs the left hand side of the comparison
* @param rhs the right hand side of the comparison
*/
public void appendXmlComparison(SQLBuffer buf, String op, FilterValue lhs,
FilterValue rhs, boolean lhsxml, boolean rhsxml) {
super.appendXmlComparison(buf, op, lhs, rhs, lhsxml, rhsxml);
if (lhsxml && rhsxml)
appendXmlComparison2(buf, op, lhs, rhs);
else if (lhsxml)
appendXmlComparison1(buf, op, lhs, rhs);
else
appendXmlComparison1(buf, op, rhs, lhs);
}
/**
* Append an xml comparison predicate
*
* @param buf the SQL buffer to write the comparison
* @param op the comparison operation to perform
* @param lhs the left hand side of the comparison (maps to xml column)
* @param rhs the right hand side of the comparison
*/
private void appendXmlComparison1(SQLBuffer buf, String op,
FilterValue lhs, FilterValue rhs) {
appendXmlExtractValue(buf, lhs);
buf.append(" ").append(op).append(" ");
rhs.appendTo(buf);
}
/**
* Append an xml comparison predicate (both operands map to xml column)
*
* @param buf the SQL buffer to write the comparison
* @param op the comparison operation to perform
* @param lhs the left hand side of the comparison (maps to xml column)
* @param rhs the right hand side of the comparison (maps to xml column)
*/
private void appendXmlComparison2(SQLBuffer buf, String op,
FilterValue lhs, FilterValue rhs) {
appendXmlExtractValue(buf, lhs);
buf.append(" ").append(op).append(" ");
appendXmlExtractValue(buf, rhs);
}
private void appendXmlExtractValue(SQLBuffer buf, FilterValue val) {
buf.append("extractValue(").
append(val.getColumnAlias(
val.getFieldMapping().getColumns()[0])).
append(",'/*/");
val.appendTo(buf);
buf.append("')");
}
public void insertBlobForStreamingLoad(Row row, Column col, Object ob)
throws SQLException {
if (ob == null)
col.setType(Types.OTHER);
row.setNull(col);
}
public void insertClobForStreamingLoad(Row row, Column col, Object ob)
throws SQLException {
if (ob == null)
col.setType(Types.OTHER);
row.setNull(col);
}
public int getBatchUpdateCount(PreparedStatement ps) throws SQLException {
int updateSuccessCnt = 0;
if (batchLimit != 0 && ps != null) {
updateSuccessCnt = ps.getUpdateCount();
if (log.isTraceEnabled())
log.trace(_loc.get("batch-update-success-count",
updateSuccessCnt));
}
return updateSuccessCnt;
}
}