blob: b58b55ee2f056ad67193c2a0a823596e2d758f45 [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.io.InputStream;
import java.io.StringReader;
import java.lang.reflect.Field;
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.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.openjpa.jdbc.identifier.DBIdentifier;
import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
import org.apache.openjpa.jdbc.kernel.JDBCStore;
import org.apache.openjpa.jdbc.kernel.exps.FilterValue;
import org.apache.openjpa.jdbc.meta.JavaSQLTypes;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.ForeignKey;
import org.apache.openjpa.jdbc.schema.ForeignKey.FKMapKey;
import org.apache.openjpa.jdbc.schema.Index;
import org.apache.openjpa.jdbc.schema.PrimaryKey;
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.meta.JavaTypes;
import org.apache.openjpa.util.StoreException;
import org.apache.openjpa.util.UserException;
/**
* 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;
/**
* This variable was used prior to 2.1.x to indicate that OpenJPA should attempt to use
* a Reader-based JDBC 4.0 method to set Clob or XML data. It allowed XMLType and
* Clob values larger than 4000 bytes to be used. For 2.1.x+, code was added to allow
* said functionality by default (see OPENJPA-1691). For forward compatibility, this
* variable should not be removed.
*/
@Deprecated
public boolean supportsSetClob = false;
/**
* If a user sets the previous variable (supportsSetClob) to true, we should log a
* warning indicating that the variable no longer has an effect due to the code changes
* of OPENJPA-1691. We only want to log the warning once per instance, thus this
* variable will be used to indicate if the warning should be printed or not.
*/
@Deprecated
private boolean logSupportsSetClobWarning = true;
/**
* Type constructor for XML column, used in INSERT and UPDATE statements.
*/
public String xmlTypeMarker = "XMLType(?)";
// 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;
// cache some native Oracle classes and methods
private Class oraclePreparedStatementClass = null;
private Field oraclePreparedStatementFormNvarcharField = null;
private Method oracleClob_empty_lob_Method = null;
private Method oracleBlob_empty_lob_Method = null;
private Method oracleClob_isEmptyLob_Method = 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";
timeWithZoneTypeName = "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",
}));
// reservedWordSet subset that CANNOT be used as valid column names
// (i.e., without surrounding them with double-quotes)
invalidColumnWordSet.addAll(Arrays.asList(new String[]{
"ACCESS", "ADD", "ALL", "ALTER", "AND", "ANY", "AS", "ASC", "AUDIT",
"BETWEEN", "BY", "CHAR", "CHECK", "CLUSTER", "COLUMN", "COMMENT",
"COMPRESS", "CONNECT", "CREATE", "CURRENT", "DATE", "DECIMAL",
"DEFAULT", "DELETE", "DESC", "DISTINCT", "DROP", "ELSE", "END-EXEC",
"EXCLUSIVE", "EXISTS", "FILE", "FLOAT", "FOR", "FROM", "GRANT",
"GROUP", "HAVING", "IDENTIFIED", "IMMEDIATE", "IN", "INCREMENT",
"INDEX", "INITIAL", "INSERT", "INTEGER", "INTERSECT", "INTO",
"IS", "LEVEL", "LIKE", "LOCK", "LONG", "MAXEXTENTS", "MINUS",
"MODE", "NOAUDIT", "NOCOMPRESS", "NOT", "NOWAIT", "NULL", "NUMBER",
"OF", "OFFLINE", "ON", "ONLINE", "OPTION", "OR", "ORDER", "PCTFREE",
"PRIOR", "PRIVILEGES", "PUBLIC", "REVOKE", "ROW", "ROWS", "SELECT",
"SESSION", "SET", "SIZE", "SMALLINT", "TABLE", "THEN", "TO",
"UNION", "UNIQUE", "UPDATE", "USER", "VALUES", "VARCHAR", "VIEW",
"WHENEVER", "WHERE", "WITH",
}));
substringFunctionName = "SUBSTR";
super.setBatchLimit(defaultBatchLimit);
selectWordSet.add("WITH");
reportsSuccessNoInfoOnBatchUpdates = true;
try {
oraclePreparedStatementClass = Class.forName("oracle.jdbc.OraclePreparedStatement");
try {
oraclePreparedStatementFormNvarcharField = oraclePreparedStatementClass.getField("FORM_NCHAR");
oraclePreparedStatementFormNvarcharField.setAccessible(true);
}
catch (NoSuchFieldException e) {
log.warn("OraclePreparedStatement without FORM_NCHAR field found");
}
}
catch (ClassNotFoundException e) {
// all fine
}
oracleClob_empty_lob_Method = getMethodByReflection("oracle.sql.CLOB", "getEmptyCLOB");
oracleBlob_empty_lob_Method = getMethodByReflection("oracle.sql.BLOB", "getEmptyBLOB");
oracleClob_isEmptyLob_Method = getMethodByReflection("oracle.sql.CLOB", "isEmptyLob");
}
private Method getMethodByReflection(String className, String methodName, Class<?>... paramTypes) {
try {
return Class.forName(className,true,
AccessController.doPrivileged(J2DoPrivHelper
.getContextClassLoaderAction())).
getMethod(methodName, paramTypes);
}
catch (Exception e) {
// all fine
}
return null;
}
@Override
public void endConfiguration() {
super.endConfiguration();
if (useTriggersForAutoAssign)
supportsAutoAssign = true;
}
@Override
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)) {
int jdbcMajor = meta.getDriverMajorVersion();
int jdbcMinor = meta.getDriverMinorVersion();
driverVendor = VENDOR_ORACLE + jdbcMajor + jdbcMinor;
int jdbcVersion = jdbcMajor * 1000 + jdbcMinor;
if( jdbcVersion >= 11002) {
maxEmbeddedBlobSize = -1;
maxEmbeddedClobSize = -1;
}
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()" (for values <= 4000 bytes only)
// or ".getClobVal()" suffix. eg. t0.xmlcol.getClobVal()
getStringVal = ".getClobVal()";
} 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);
guessJDBCVersion(conn);
}
/**
* Cache constant for drivers with behaviors we have to deal with.
*/
private void cacheDriverBehavior(String driverVendor) {
if (_driverBehavior != -1)
return;
driverVendor = driverVendor.toLowerCase(Locale.ENGLISH);
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) {
}
}
}
@Override
public boolean supportsLocking(Select sel) {
if (!super.supportsLocking(sel))
return false;
return !requiresSubselectForRange(sel.getStartIndex(),
sel.getEndIndex(), sel.isDistinct(), sel.getOrdering());
}
@Override
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();
String asString = null;
if (alias instanceof SQLBuffer) {
asString = ((SQLBuffer) alias).getSQL();
selectSQL.appendParamOnly((SQLBuffer) alias);
} else {
asString = alias.toString();
}
selectSQL.append(asString);
if (asString.indexOf(" AS ") == -1)
selectSQL.append(" AS c").append(String.valueOf(i));
if (itr.hasNext())
selectSQL.append(", ");
}
return selectSQL;
}
@Override
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;
}
@Override
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;
}
@Override
protected SQLBuffer toSelect(SQLBuffer select, JDBCFetchConfiguration fetch,
SQLBuffer tables, SQLBuffer where, SQLBuffer group,
SQLBuffer having, SQLBuffer order,
boolean distinct, boolean forUpdate, long start, long end,
boolean subselect, Select sel) {
return toSelect(select, fetch, tables, where, group, having, order,
distinct, forUpdate, start, end, sel);
}
@Override
protected 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 (!isUsingRange(start, end)) {
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 (!isUsingOffset(start)) {
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))
.append(" * FROM (SELECT r.*, ROWNUM RNUM FROM (");
buf.append(newsel);
buf.append(") r");
if (isUsingLimit(end))
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 (!isUsingRange(start, end)) {
return false;
}
return isUsingOffset(start) || distinct || isUsingOrderBy(order);
}
/**
* Check to see if we have set the {@link #SELECT_HINT} in the
* fetch configuration, and if so, append the Oracle hint after the
* "SELECT" part of the query.
*/
@Override
public String getSelectOperation(JDBCFetchConfiguration fetch) {
Object hint = fetch == null ? null : fetch.getHint(SELECT_HINT);
String select = "SELECT";
if (hint != null)
select += " " + hint;
return select;
}
@Override
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.getTypeIdentifier().getName();
if (useSetFormOfUseForUnicode && typeName != null &&
(typeName.toLowerCase(Locale.ENGLISH).startsWith("nvarchar") ||
typeName.toLowerCase(Locale.ENGLISH).startsWith("nchar") ||
typeName.toLowerCase(Locale.ENGLISH).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[]{
Integer.valueOf(idx),
oraclePreparedStatementFormNvarcharField.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 {
Method setFixedCharMethod = inner.getClass().getMethod("setFixedCHAR",
new Class[]{int.class, String.class});
if (!setFixedCharMethod.isAccessible()) {
setFixedCharMethod.setAccessible(true);
}
setFixedCharMethod.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);
}
@Override
public void setBinaryStream(PreparedStatement stmnt, int idx,
InputStream val, int length, Column col)
throws SQLException {
if (length == 0)
stmnt.setBlob(idx, getEmptyBlob());
else {
super.setBinaryStream(stmnt, idx, val, length, col);
}
}
@Override
public void setClobString(PreparedStatement stmnt, int idx, String val,
Column col)
throws SQLException {
//We need a place to detect if the user is setting the 'supportsSetClob' property.
//While in previous releases this property had meaning, it is no longer useful
//given the code added via OPENJPA-1691. As such, we need to warn user's the
//property no longer has meaning. While it would be nice to have a better way
//to detect if the supportsSetClob property has been set, the best we can do
//is detect the variable in this code path as this is the path a user's code
//would go down if they are still executing code which actually made use of
//the support provided via setting supportsSetClob.
if (supportsSetClob && logSupportsSetClobWarning){
log.warn(_loc.get("oracle-set-clob-warning"));
logSupportsSetClobWarning=false;
}
if (col.isXML()) {
if (isJDBC4) {
// This JDBC 4 method handles values longer than 4000 bytes.
stmnt.setClob(idx, new StringReader(val), val.length());
} else {
// This method is limited to 4000 bytes.
setCharacterStream(stmnt, idx, new StringReader(val), val.length(), col);
}
return;
}
if (!useSetStringForClobs && val.length() == 0)
stmnt.setClob(idx, getEmptyClob());
else {
super.setClobString(stmnt, idx, val, col);
}
}
@Override
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", toDBName(col
.getFullDBIdentifier())));
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 && !DBIdentifier.isNull(col.getTypeIdentifier()))
stmnt.setNull(idx, Types.STRUCT, col.getTypeIdentifier().getName());
// 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);
}
@Override
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 (oracleClob_isEmptyLob_Method != null && clob.getClass().getName().equals("oracle.sql.CLOB")) {
try {
if (((Boolean) oracleClob_isEmptyLob_Method.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());
}
@Override
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;
}
@Override
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());
}
}
@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++) {
String typeName = cols[i].getTypeIdentifier().getName();
if (typeName == null)
continue;
if (typeName.toUpperCase(Locale.ENGLISH).startsWith("TIMESTAMP"))
cols[i].setType(Types.TIMESTAMP);
else if ("BLOB".equalsIgnoreCase(typeName))
cols[i].setType(Types.BLOB);
else if ("CLOB".equalsIgnoreCase(typeName)
|| "NCLOB".equalsIgnoreCase(typeName))
cols[i].setType(Types.CLOB);
else if ("FLOAT".equalsIgnoreCase(typeName))
cols[i].setType(Types.FLOAT);
else if ("NVARCHAR".equalsIgnoreCase(typeName))
cols[i].setType(Types.VARCHAR);
else if ("NCHAR".equalsIgnoreCase(typeName))
cols[i].setType(Types.CHAR);
else if ("XMLTYPE".equalsIgnoreCase(typeName)) {
cols[i].setXML(true);
}
}
return cols;
}
/**
* Oracle JDBC is still Java7 at most :(
*/
@Override
public int getPreferredType(int type) {
switch (type) {
case Types.TIME_WITH_TIMEZONE:
return Types.TIME;
case Types.TIMESTAMP_WITH_TIMEZONE:
return Types.TIMESTAMP;
default:
return type;
}
}
@Override
public PrimaryKey[] getPrimaryKeys(DatabaseMetaData meta,
String catalog, String schemaName, String tableName, Connection conn)
throws SQLException {
return getPrimaryKeys(meta,
DBIdentifier.newCatalog(catalog),
DBIdentifier.newSchema(schemaName),
DBIdentifier.newTable(tableName), conn);
}
@Override
public PrimaryKey[] getPrimaryKeys(DatabaseMetaData meta,
DBIdentifier catalog, DBIdentifier schemaName, DBIdentifier tableName, Connection conn)
throws SQLException {
StringBuilder buf = new StringBuilder();
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 (!DBIdentifier.isNull(schemaName))
buf.append(" AND t0.OWNER = ?");
if (!DBIdentifier.isNull(tableName))
buf.append(" AND t0.TABLE_NAME = ?");
PreparedStatement stmnt = conn.prepareStatement(buf.toString());
ResultSet rs = null;
try {
int idx = 1;
if (!DBIdentifier.isNull(schemaName)) {
setString(stmnt, idx++, convertSchemaCase(schemaName), null);
}
if (!DBIdentifier.isNull(tableName)) {
setString(stmnt, idx++, convertSchemaCase(tableName.getUnqualifiedName()), null);
}
setTimeouts(stmnt, conf, false);
rs = stmnt.executeQuery();
List<PrimaryKey> pkList = new ArrayList<>();
while (rs != null && rs.next()) {
pkList.add(newPrimaryKey(rs));
}
return pkList.toArray(new PrimaryKey[pkList.size()]);
} finally {
if (rs != null)
try {
rs.close();
} catch (Exception e) {
// ignore cleanup exception
}
try {
stmnt.close();
} catch (Exception e) {
// ignore cleanup exception
}
}
}
@Override
public Index[] getIndexInfo(DatabaseMetaData meta, String catalog,
String schemaName, String tableName, boolean unique, boolean approx,
Connection conn)
throws SQLException {
return getIndexInfo(meta,
DBIdentifier.newCatalog(catalog),
DBIdentifier.newSchema(schemaName),
DBIdentifier.newTable(tableName), unique, approx, conn);
}
@Override
public Index[] getIndexInfo(DatabaseMetaData meta, DBIdentifier catalog,
DBIdentifier schemaName, DBIdentifier tableName, boolean unique, boolean approx,
Connection conn)
throws SQLException {
StringBuilder buf = new StringBuilder();
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 (!DBIdentifier.isNull(schemaName))
buf.append(" AND t0.TABLE_OWNER = ?");
if (!DBIdentifier.isNull(tableName))
buf.append(" AND t0.TABLE_NAME = ?");
PreparedStatement stmnt = conn.prepareStatement(buf.toString());
ResultSet rs = null;
try {
int idx = 1;
if (!DBIdentifier.isNull(schemaName))
setString(stmnt, idx++, convertSchemaCase(schemaName), null);
if (!DBIdentifier.isNull(tableName))
setString(stmnt, idx++, convertSchemaCase(tableName), null);
setTimeouts(stmnt, conf, false);
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) {
}
}
}
@Override
public ForeignKey[] getImportedKeys(DatabaseMetaData meta, String catalog,
String schemaName, String tableName, Connection conn, boolean partialKeys)
throws SQLException {
return getImportedKeys(meta,
DBIdentifier.newCatalog(catalog),
DBIdentifier.newSchema(schemaName),
DBIdentifier.newTable(tableName), conn, partialKeys);
}
@Override
public ForeignKey[] getImportedKeys(DatabaseMetaData meta, DBIdentifier catalog,
DBIdentifier schemaName, DBIdentifier tableName, Connection conn, boolean partialKeys)
throws SQLException {
StringBuilder delAction = new StringBuilder("DECODE(t1.DELETE_RULE").
append(", 'NO ACTION', ").append(DatabaseMetaData.importedKeyNoAction).
append(", 'RESTRICT', ").append(DatabaseMetaData.importedKeyRestrict).
append(", 'CASCADE', ").append(DatabaseMetaData.importedKeyCascade).
append(", 'SET NULL', ").append(DatabaseMetaData.importedKeySetNull).
append(", 'SET DEFAULT', ").append(DatabaseMetaData.importedKeySetDefault).
append(")");
StringBuilder buf = new StringBuilder();
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(DatabaseMetaData.importedKeyInitiallyDeferred).
append(", 'IMMEDIATE', ").
append(DatabaseMetaData.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 (!DBIdentifier.isNull(schemaName))
buf.append(" AND t0.OWNER = ?");
if (!DBIdentifier.isNull(tableName))
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 (!DBIdentifier.isNull(schemaName))
setString(stmnt, idx++, convertSchemaCase(schemaName), null);
if (!DBIdentifier.isNull(tableName))
setString(stmnt, idx++, convertSchemaCase(tableName), null);
setTimeouts(stmnt, conf, false);
rs = stmnt.executeQuery();
List<ForeignKey> fkList = new ArrayList<>();
Map<FKMapKey, ForeignKey> fkMap = new HashMap<>();
while (rs != null && rs.next()) {
ForeignKey nfk = newForeignKey(rs);
if (!partialKeys) {
ForeignKey fk = combineForeignKey(fkMap, nfk);
// Only add the fk to the import list if it is new
if (fk != nfk) {
continue;
}
}
fkList.add(nfk);
}
return (ForeignKey[]) fkList.toArray
(new ForeignKey[fkList.size()]);
} finally {
if (rs != null)
try {
rs.close();
} catch (Exception e) {
}
try {
stmnt.close();
} catch (Exception e) {
}
}
}
@Override
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 " + toDBName(table.getIdentifier())
+ " FOR EACH ROW BEGIN SELECT " + seq + ".nextval INTO "
+ ":new." + toDBName(cols[i].getIdentifier()) + " 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;
}
/**
* Return the preferred {@link Types} constant for the given
* {@link JavaTypes} or {@link JavaSQLTypes} constant.
*/
@Override
public int getJDBCType(int metaTypeCode, boolean lob, int precis,
int scale, boolean xml) {
return getJDBCType(metaTypeCode, lob || xml, precis, scale);
}
@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_OWNER AS SEQUENCE_SCHEMA, ").
append("SEQUENCE_NAME FROM ALL_SEQUENCES");
if (!DBIdentifier.isNull(schemaName) || !DBIdentifier.isNull(sequenceName))
buf.append(" WHERE ");
if (!DBIdentifier.isNull(schemaName)) {
buf.append("SEQUENCE_OWNER = ?");
if (!DBIdentifier.isNull(sequenceName))
buf.append(" AND ");
}
if (!DBIdentifier.isNull(sequenceName))
buf.append("SEQUENCE_NAME = ?");
return buf.toString();
}
@Override
public boolean isSystemSequence(String name, String schema,
boolean targetSchema) {
return isSystemSequence(DBIdentifier.newSequence(name),
DBIdentifier.newSchema(schema), targetSchema);
}
@Override
public boolean isSystemSequence(DBIdentifier name, DBIdentifier schema,
boolean targetSchema) {
if (super.isSystemSequence(name, schema, targetSchema))
return true;
// filter out generated sequences used for auto-assign
String strName = DBIdentifier.isNull(name) ? "" : name.getName();
return (autoAssignSequenceName != null
&& strName.equalsIgnoreCase(autoAssignSequenceName))
|| (autoAssignSequenceName == null
&& strName.toUpperCase(Locale.ENGLISH).startsWith("ST_"));
}
@Override
public Object getGeneratedKey(Column col, Connection conn)
throws SQLException {
if (!useTriggersForAutoAssign)
return 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 {
setTimeouts(stmnt, conf, false);
rs = stmnt.executeQuery();
rs.next();
return 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();
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));
}
/**
* 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.
*/
@Override
public void putBytes(Blob 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.
*/
@Override
public void putString(Clob 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.
*/
@Override
public void putChars(Clob 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[]{ 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;
if (oracleClob_empty_lob_Method == null)
return null;
try {
return EMPTY_CLOB = (Clob) oracleClob_empty_lob_Method.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;
if (oracleBlob_empty_lob_Method == null)
return null;
try {
return EMPTY_BLOB = (Blob) oracleBlob_empty_lob_Method.invoke(null, new Object[0]);
} catch (Exception e) {
throw new SQLException(e.getMessage());
}
}
private boolean isOraclePreparedStatement(Statement stmnt) {
return oraclePreparedStatementClass != null &&
oraclePreparedStatementClass.isInstance(stmnt);
}
/**
* 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
*/
@Override
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("')");
}
@Override
public void insertClobForStreamingLoad(Row row, Column col, Object ob)
throws SQLException {
if (ob == null) {
col.setType(Types.OTHER);
row.setNull(col);
} else {
row.setClob(col, getEmptyClob());
}
}
@Override
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;
}
@Override
public boolean isFatalException(int subtype, SQLException ex) {
String errorState = ex.getSQLState();
int errorCode = ex.getErrorCode();
if ((subtype == StoreException.LOCK)
&& (("61000".equals(errorState) && (errorCode == 54 ||
errorCode == 60 || errorCode == 4020 ||
errorCode == 4021 || errorCode == 4022))
|| ("42000".equals(errorState) && errorCode == 2049))) {
return false;
}
if ("72000".equals(errorState) && errorCode == 1013) {
return false;
}
return super.isFatalException(subtype, ex);
}
@Override
public void insertBlobForStreamingLoad(Row row, Column col,
JDBCStore store, Object ob, Select sel) throws SQLException {
if (ob == null) {
col.setType(Types.OTHER);
row.setNull(col);
} else {
row.setBlob(col, getEmptyBlob());
}
}
@Override
public boolean isImplicitJoin() {
return joinSyntax == SYNTAX_DATABASE;
}
/**
* Oracle requires special handling of XML column.
* Unless the value length is less or equal to 4000 bytes,
* the parameter marker must be decorated with type constructor.
*/
@Override
public String getMarkerForInsertUpdate(Column col, Object val) {
if (col.isXML() && val != RowImpl.NULL) {
return xmlTypeMarker;
}
return super.getMarkerForInsertUpdate(col, val);
}
/**
* Oracle drivers, at least in versions 10.2.0.4 and 11.2.0.1, incorrectly return a driver major version from
* {@link DatabaseMetaData#getJDBCMajorVersion()}.
*/
protected void guessJDBCVersion(Connection conn) {
if (_driverBehavior != BEHAVE_ORACLE) {
return;
}
isJDBC4 = true;
try {
conn.getClientInfo(); // Try to call a JDBC 4 method.
} catch (SQLException e) {
// OK, we are on JDBC 4.
} catch (Throwable t) {
// Most likely an AbstractMethodError from JDBC 3 driver.
isJDBC4 = false;
}
}
@Override
public String getIsNullSQL(String colAlias, int colType) {
switch(colType) {
case Types.BLOB:
case Types.CLOB:
return String.format("length (%s) = 0", colAlias);
}
return super.getIsNullSQL(colAlias, colType);
}
@Override
public String getIsNotNullSQL(String colAlias, int colType) {
switch(colType) {
case Types.BLOB:
case Types.CLOB:
return String.format("length (%s) != 0 ", colAlias);
}
return super.getIsNotNullSQL(colAlias, colType);
}
@Override
public void indexOf(SQLBuffer buf, FilterValue str, FilterValue find,
FilterValue start) {
buf.append("INSTR(");
str.appendTo(buf);
buf.append(", ");
find.appendTo(buf);
if (start != null) {
buf.append(", ");
start.appendTo(buf);
}
buf.append(")");
}
}