blob: 046c1a45571f60af4055a72f5422443c2253a567 [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.ByteArrayInputStream;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Method;
import java.sql.Blob;
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.Types;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;
import java.util.StringTokenizer;
import javax.sql.DataSource;
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.kernel.exps.Lit;
import org.apache.openjpa.jdbc.kernel.exps.Param;
import org.apache.openjpa.jdbc.kernel.exps.Val;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.Index;
import org.apache.openjpa.jdbc.schema.Schema;
import org.apache.openjpa.jdbc.schema.Table;
import org.apache.openjpa.kernel.Filters;
import org.apache.openjpa.kernel.MixedLockLevels;
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.OpenJPAException;
import org.apache.openjpa.util.StoreException;
import org.apache.openjpa.util.UnsupportedException;
import org.apache.openjpa.util.UserException;
/**
* Dictionary for IBM DB2 database.
*/
public class DB2Dictionary
extends AbstractDB2Dictionary {
private static final Localizer _loc = Localizer.forPackage
(DB2Dictionary.class);
public static final String VENDOR_IBM = "ibm";
public String optimizeClause = "optimize for";
public String rowClause = "row";
protected int db2ServerType = 0;
public static final int db2ISeriesV5R3OrEarlier = 1;
public static final int db2UDBV81OrEarlier = 2;
public static final int db2ZOSV8xOrLater = 3;
public static final int db2UDBV82OrLater = 4;
public static final int db2ISeriesV5R4OrLater = 5;
protected static final String forUpdate = "FOR UPDATE";
protected static final String withURClause = "WITH UR";
protected static final String withCSClause = "WITH CS";
protected static final String withRSClause = "WITH RS";
protected static final String withRRClause = "WITH RR";
protected static final String useKeepShareLockClause = "USE AND KEEP SHARE LOCKS";
protected static final String useKeepUpdateLockClause = "USE AND KEEP UPDATE LOCKS";
protected static final String useKeepExclusiveLockClause = "USE AND KEEP EXCLUSIVE LOCKS";
protected static final String forReadOnlyClause = "FOR READ ONLY";
protected static final String defaultSequenceSQL
= "SELECT SEQSCHEMA AS SEQUENCE_SCHEMA, SEQNAME AS SEQUENCE_NAME FROM SYSCAT.SEQUENCES";
static final String SYSDUMMY = "SYSIBM.SYSDUMMY1";
private int defaultBatchLimit = 100;
public boolean appendExtendedExceptionText = true;
/**
* Affirms whether this dictionary uses {@code ROWNUM} feature.
* {@code ROWNUM} feature is used to construct {@code SQL SELECT} query
* that uses an offset or limits the number of resultant rows.
* <br>
* By default, this flag is set to {@code false}.
*/
public boolean supportsRowNum = false;
public DB2Dictionary() {
platform = "DB2";
validationSQL = "SELECT DISTINCT(CURRENT TIMESTAMP) FROM SYSIBM.SYSTABLES";
supportsSelectEndIndex = true;
nextSequenceQuery = "VALUES NEXTVAL FOR {0}";
sequenceSQL = defaultSequenceSQL;
sequenceSchemaSQL = "SEQSCHEMA = ?";
sequenceNameSQL = "SEQNAME = ?";
characterColumnSize = 254;
binaryTypeName = "BLOB(1M)";
longVarbinaryTypeName = "BLOB(1M)";
varbinaryTypeName = "BLOB(1M)";
clobTypeName = "CLOB(1M)";
longVarcharTypeName = "LONG VARCHAR";
datePrecision = MICRO;
storeCharsAsNumbers = false;
fixedSizeTypeNameSet.addAll(Arrays.asList(new String[]{
"LONG VARCHAR FOR BIT DATA", "LONG VARCHAR", "LONG VARGRAPHIC",
}));
systemSchemas = "SYSCAT,SYSIBM,SYSSTAT,SYSIBMADM,SYSTOOLS";
maxConstraintNameLength = 18;
maxIndexNameLength = 128;
maxColumnNameLength = 30;
supportsDeferredConstraints = false;
supportsDefaultDeleteAction = false;
supportsAlterTableWithDropColumn = false;
supportsLockingWithOrderClause = true;
supportsNullUniqueColumn = false;
supportsNullTableForGetColumns = false;
requiresCastForMathFunctions = true;
requiresCastForComparisons = true;
reservedWordSet.addAll(Arrays.asList(new String[]{
"AFTER", "ALIAS", "ALLOW", "APPLICATION", "ASSOCIATE", "ASUTIME",
"AUDIT", "AUX", "AUXILIARY", "BEFORE", "BINARY", "BUFFERPOOL",
"CACHE", "CALL", "CALLED", "CAPTURE", "CARDINALITY", "CCSID",
"CLUSTER", "COLLECTION", "COLLID", "COMMENT", "CONCAT",
"CONDITION", "CONTAINS", "COUNT_BIG", "CURRENT_LC_CTYPE",
"CURRENT_PATH", "CURRENT_SERVER", "CURRENT_TIMEZONE", "CYCLE",
"DATABASE", "DAYS", "DB2GENERAL", "DB2GENRL", "DB2SQL",
"DBINFO", "DEFAULTS", "DEFINITION", "DETERMINISTIC", "DISALLOW",
"DO", "DSNHATTR", "DSSIZE", "DYNAMIC", "EACH", "EDITPROC", "ELSEIF",
"ENCODING", "END-EXEC1", "ERASE", "EXCLUDING", "EXIT", "FENCED",
"FIELDPROC", "FILE", "FINAL", "FREE", "FUNCTION", "GENERAL",
"GENERATED", "GRAPHIC", "HANDLER", "HOLD", "HOURS", "IF",
"INCLUDING", "INCREMENT", "INDEX", "INHERIT", "INOUT", "INTEGRITY",
"ISOBID", "ITERATE", "JAR", "JAVA", "LABEL", "LC_CTYPE", "LEAVE",
"LINKTYPE", "LOCALE", "LOCATOR", "LOCATORS", "LOCK", "LOCKMAX",
"LOCKSIZE", "LONG", "LOOP", "MAXVALUE", "MICROSECOND",
"MICROSECONDS", "MINUTES", "MINVALUE", "MODE", "MODIFIES", "MONTHS",
"NEW", "NEW_TABLE", "NOCACHE", "NOCYCLE", "NODENAME", "NODENUMBER",
"NOMAXVALUE", "NOMINVALUE", "NOORDER", "NULLS", "NUMPARTS", "OBID",
"OLD", "OLD_TABLE", "OPTIMIZATION", "OPTIMIZE", "OUT", "OVERRIDING",
"PACKAGE", "PARAMETER", "PART", "PARTITION", "PATH", "PIECESIZE",
"PLAN", "PRIQTY", "PROGRAM", "PSID", "QUERYNO", "READS", "RECOVERY",
"REFERENCING", "RELEASE", "RENAME", "REPEAT", "RESET", "RESIGNAL",
"RESTART", "RESULT", "RESULT_SET_LOCATOR", "RETURN", "RETURNS",
"ROUTINE", "ROW", "RRN", "RUN", "SAVEPOINT", "SCRATCHPAD",
"SECONDS", "SECQTY", "SECURITY", "SENSITIVE", "SIGNAL", "SIMPLE",
"SOURCE", "SPECIFIC", "SQLID", "STANDARD", "START", "STATIC",
"STAY", "STOGROUP", "STORES", "STYLE", "SUBPAGES", "SYNONYM",
"SYSFUN", "SYSIBM", "SYSPROC", "SYSTEM", "TABLESPACE", "TRIGGER",
"UNDO", "UNTIL", "VALIDPROC", "VARIABLE", "VARIANT", "VCAT",
"VOLUMES", "WHILE", "WLM", "YEARS",
}));
// reservedWordSet subset that CANNOT be used as valid column names
// (i.e., without surrounding them with double-quotes)
invalidColumnWordSet.addAll(Arrays.asList(new String[] {
"CONSTRAINT", "END-EXEC", "END-EXEC1",
}));
super.setBatchLimit(defaultBatchLimit);
selectWordSet.add("WITH");
}
@Override
public boolean supportsRandomAccessResultSet(Select sel,
boolean forUpdate) {
return !forUpdate
&& super.supportsRandomAccessResultSet(sel, forUpdate);
}
@Override
protected void appendSelectRange(SQLBuffer buf, long start, long end,
boolean subselect) {
// appends the literal range string, since DB2 is unable to handle
// a bound parameter for it
// do not generate FETCH FIRST clause for subselect
if (!subselect)
buf.append(" FETCH FIRST ").append(Long.toString(end)).
append(" ROWS ONLY");
}
@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("(" + getCastStringColumnSize(val) + ")");
selectSQL.append(")");
}
}
@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
public Connection decorate(Connection conn)
throws SQLException {
// some versions of the DB2 driver seem to default to
// READ_UNCOMMITTED, which will prevent locking from working
// (multiple SELECT ... FOR UPDATE statements are allowed on
// the same instance); if we have not overridden the
// transaction isolation in the configuration, default to
// TRANSACTION_READ_COMMITTED
conn = super.decorate(conn);
if (conf.getTransactionIsolationConstant() == -1 &&
conn.getTransactionIsolation() <
Connection.TRANSACTION_READ_COMMITTED)
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
return conn;
}
@Override
public void connectedConfiguration(Connection conn) throws SQLException {
super.connectedConfiguration(conn);
DatabaseMetaData metaData = conn.getMetaData();
String driverName = metaData.getDriverName();
if (driverName != null && driverName.startsWith("IBM DB2"))
driverVendor = VENDOR_IBM;
else
driverVendor = VENDOR_OTHER;
// Determine the type of DB2 database
// First check for AS/400
getProductVersionMajorMinorForISeries();
if (versionLaterThan(0)) {
if (isDB2ISeriesV5R3OrEarlier())
db2ServerType = db2ISeriesV5R3OrEarlier;
else if (isDB2ISeriesV5R4OrLater())
db2ServerType = db2ISeriesV5R4OrLater;
}
if (db2ServerType == 0) {
if (isJDBC3) {
setMajorVersion(metaData.getDatabaseMajorVersion());
setMinorVersion(metaData.getDatabaseMinorVersion());
}
else
getProductVersionMajorMinor();
// Determine the type of DB2 database for ZOS & UDB
if (isDB2UDBV81OrEarlier())
db2ServerType = db2UDBV81OrEarlier;
else if (isDB2ZOSV8xOrLater())
db2ServerType = db2ZOSV8xOrLater;
else if (isDB2UDBV82OrLater())
db2ServerType = db2UDBV82OrLater;
}
// verify that database product is supported
if (db2ServerType == 0 || getMajorVersion() < 0)
throw new UnsupportedException(_loc.get("db-not-supported",
new Object[] {databaseProductName, databaseProductVersion }));
if (versionEqualOrLaterThan(9, 2)) {
supportsLockingWithMultipleTables = true;
supportsLockingWithInnerJoin = true;
supportsLockingWithOuterJoin = true;
forUpdateClause = "WITH RR USE AND KEEP UPDATE LOCKS";
supportsXMLColumn = versionEqualOrLaterThan(9, 0);
}
// platform specific settings
switch (db2ServerType) {
case db2UDBV82OrLater:
lastGeneratedKeyQuery = "SELECT IDENTITY_VAL_LOCAL() FROM " + SYSDUMMY;
break;
case db2ZOSV8xOrLater:
// DB2 Z/OS
characterColumnSize = 255;
lastGeneratedKeyQuery = "SELECT IDENTITY_VAL_LOCAL() FROM " + SYSDUMMY;
nextSequenceQuery = "SELECT NEXTVAL FOR {0} FROM " + SYSDUMMY;
// allow users to set a non default sequenceSQL.
if (defaultSequenceSQL.equals(sequenceSQL)){
sequenceSQL = "SELECT SCHEMA AS SEQUENCE_SCHEMA, "
+ "NAME AS SEQUENCE_NAME FROM SYSIBM.SYSSEQUENCES";
if (log.isTraceEnabled())
log.trace(_loc.get("sequencesql-override", new Object[] {defaultSequenceSQL, sequenceSQL}));
}
sequenceSchemaSQL = "SCHEMA = ?";
sequenceNameSQL = "NAME = ?";
if (getMajorVersion() == 8) {
// DB2 Z/OS Version 8: no bigint support, hence map Java
// long to decimal
bigintTypeName = "DECIMAL(31,0)";
}
break;
case db2ISeriesV5R3OrEarlier:
case db2ISeriesV5R4OrLater:
lastGeneratedKeyQuery = "SELECT IDENTITY_VAL_LOCAL() FROM " + SYSDUMMY;
nextSequenceQuery = "SELECT NEXTVAL FOR {0} FROM " + SYSDUMMY;
validationSQL = "SELECT DISTINCT(CURRENT TIMESTAMP) FROM "
+ "QSYS2.SYSTABLES";
// allow users to set a non default sequenceSQL.
if (defaultSequenceSQL.equals(sequenceSQL)){
sequenceSQL = "SELECT SEQUENCE_SCHEMA, "
+ "SEQUENCE_NAME FROM QSYS2.SYSSEQUENCES";
if (log.isTraceEnabled())
log.trace(_loc.get("sequencesql-override", new Object[] {defaultSequenceSQL, sequenceSQL}));
}
sequenceSchemaSQL = "SEQUENCE_SCHEMA = ?";
sequenceNameSQL = "SEQUENCE_NAME = ?";
// V5R4 and earlier systems do not support retrieval of generated keys
if (isDB2ISeriesV5R4OrEarlier()) {
supportsGetGeneratedKeys = false;
}
break;
}
}
@Override
public boolean supportsIsolationForUpdate() {
return true;
}
/**
* Get the update clause for the query based on the
* isolationLevel hints if it is for update.
* It also handles the UR hint when it is not for update.
*/
@Override
protected String getForUpdateClause(JDBCFetchConfiguration fetch,
boolean isForUpdate, Select sel) {
int isolationLevel;
// For db2UDBV81OrEarlier and db2ISeriesV5R3OrEarlier:
// "optimize for" clause appears before "for update" clause.
StringBuilder forUpdateString = new StringBuilder(getOptimizeClause(sel));
// Determine the isolationLevel; the fetch
// configuration data overrides the persistence.xml value
if (fetch != null && fetch.getIsolation() != -1)
isolationLevel = fetch.getIsolation();
else
isolationLevel = conf.getTransactionIsolationConstant();
if (fetch != null && fetch.getReadLockLevel() >=
MixedLockLevels.LOCK_PESSIMISTIC_WRITE)
isolationLevel = Connection.TRANSACTION_SERIALIZABLE;
if (isForUpdate) {
switch (db2ServerType) {
case db2ISeriesV5R3OrEarlier:
case db2UDBV81OrEarlier:
if (isolationLevel == Connection.TRANSACTION_SERIALIZABLE)
forUpdateString.append(" ").append(forUpdateClause);
else
forUpdateString.append(" ").append(forUpdate).append(" ")
.append(withRSClause);
break;
case db2ZOSV8xOrLater:
case db2UDBV82OrLater:
if (isolationLevel == Connection.TRANSACTION_SERIALIZABLE) {
forUpdateString.append(" ").append(forReadOnlyClause)
.append(" ").append(withRRClause).append(" ").append(
useKeepUpdateLockClause);
} else {
forUpdateString.append(" ").append(forReadOnlyClause)
.append(" ").append(withRSClause).append(" ").append(
useKeepUpdateLockClause);
}
break;
case db2ISeriesV5R4OrLater:
if (isolationLevel == Connection.TRANSACTION_SERIALIZABLE) {
forUpdateString.append(" ").append(forReadOnlyClause)
.append(" ").append(withRRClause).append(" ").append(
useKeepExclusiveLockClause);
} else {
forUpdateString.append(" ").append(forReadOnlyClause)
.append(" ").append(withRSClause).append(" ").append(
useKeepExclusiveLockClause);
}
break;
}
} else {
if ( fetch != null && fetch.getIsolation() == Connection.TRANSACTION_READ_UNCOMMITTED
&& sel != null && sel.getParent() == null) { // i.e. not a subquery
forUpdateString.append(" ").append(forReadOnlyClause)
.append(" ").append(withURClause);
}
}
return forUpdateString.toString();
}
public boolean isDB2UDBV82OrLater() {
return (databaseProductVersion.indexOf("SQL") != -1
|| databaseProductName.indexOf("DB2/") != -1)
&& versionEqualOrLaterThan(8, 2);
}
public boolean isDB2ZOSV8xOrLater() {
return (databaseProductVersion.indexOf("DSN") != -1
|| databaseProductName.indexOf("DB2/") == -1)
&& versionLaterThan(7);
}
public boolean isDB2ISeriesV5R3OrEarlier() {
return databaseProductName.indexOf("AS") != -1
&& versionEqualOrEarlierThan(5, 3);
}
public boolean isDB2ISeriesV5R4OrLater() {
return databaseProductName.indexOf("AS") != -1
&& versionEqualOrLaterThan(5, 4);
}
public boolean isDB2ISeriesV5R4OrEarlier() {
return databaseProductName.indexOf("AS") != -1
&& versionEqualOrEarlierThan(5, 4);
}
public boolean isDB2UDBV81OrEarlier() {
return (databaseProductVersion.indexOf("SQL") != -1
|| databaseProductName.indexOf("DB2/") != -1)
&& versionEqualOrEarlierThan(8,1);
}
/** Get the version Major/Minor for the ISeries
*/
private void getProductVersionMajorMinorForISeries() {
// ISeries DBProdName DB2 UDB for AS/400
// (Toolbox)DBProdVersion 05.04.0000 V5R4m0
// ISeries DB2 UDB for AS/400
// (Native) V5R4M0
// new jcc DBProdVersion QSQ05040 or QSQ06010
if (databaseProductName.indexOf("AS") != -1) {
// default to V5R4
setMajorVersion(5);
setMinorVersion(4);
int index = databaseProductVersion.indexOf('V');
if (index != -1) {
String s = databaseProductVersion.substring(index);
s = s.toUpperCase(Locale.ENGLISH);
StringTokenizer stringtokenizer = new StringTokenizer(s, "VRM"
, false);
if (stringtokenizer.countTokens() == 3) {
String s1 = stringtokenizer.nextToken();
setMajorVersion(Integer.parseInt(s1));
String s2 = stringtokenizer.nextToken();
setMinorVersion(Integer.parseInt(s2));
}
} else {
index = databaseProductVersion.indexOf('0');
if (index != -1) {
String s = databaseProductVersion.substring(index);
s = s.toUpperCase(Locale.ENGLISH);
StringTokenizer stringtokenizer = new StringTokenizer(s, "0"
, false);
if (stringtokenizer.countTokens() == 2) {
String s1 = stringtokenizer.nextToken();
setMajorVersion(Integer.parseInt(s1));
String s2 = stringtokenizer.nextToken();
setMinorVersion(Integer.parseInt(s2));
}
}
}
}
}
private void getProductVersionMajorMinor() {
// In case JDBC driver version is lower than 3
// use following info to determine Major and Minor
// CLI vs JCC
// ZDBV8 DBProdName DB2 DB2
// DBProdVersion 08.01.0005 DSN08015
// ZDBV9 DB2 DB2
// 09.01.0005 DSN09015
// WinV9 DB2/NT DB2/NT
// 09.01.0000 SQL09010
// SolarisV9 DB2/SUN64
// SQL0901
// Linux DB2/LINUX DB2/LINUX
// 09.01.0000 SQL0901
if (databaseProductVersion.indexOf("09") != -1) {
setMajorVersion(9);
if (databaseProductVersion.indexOf("01") != -1) {
setMinorVersion(1);
}
} else if (databaseProductVersion.indexOf("08") != -1) {
setMajorVersion(8);
setMinorVersion(2);
if (databaseProductVersion.indexOf("01") != -1) {
setMinorVersion(1);
}
}
}
protected String getOptimizeClause(Select sel) {
if (sel != null && sel.getExpectedResultCount() > 0) {
StringBuilder buf = new StringBuilder();
buf.append(" ").append(optimizeClause).append(" ")
.append(sel.getExpectedResultCount())
.append(" ").append(rowClause);
return buf.toString();
}
return "";
}
@Override
public OpenJPAException newStoreException(String msg, SQLException[] causes, Object failed) {
if (appendExtendedExceptionText && causes != null && causes.length > 0) {
msg = appendExtendedExceptionMsg(msg, causes[0]);
}
return super.newStoreException(msg, causes, failed);
}
/**
* Append exception information from SQLCA to the existing
* exception message
*/
private String appendExtendedExceptionMsg(String msg, SQLException sqle){
final String GETSQLCA ="getSqlca";
try {
Method sqlcaM2 = sqle.getNextException().getClass()
.getMethod(GETSQLCA,null);
Object sqlca = sqlcaM2.invoke(sqle.getNextException(),
new Object[] {});
Method getSqlErrpMethd = sqlca.getClass().
getMethod("getSqlErrp", null);
Method getSqlWarnMethd = sqlca.getClass().
getMethod("getSqlWarn", null);
Method getSqlErrdMethd = sqlca.getClass().
getMethod("getSqlErrd", null);
StringBuilder errdStr = new StringBuilder();
int[] errds = (int[]) getSqlErrdMethd.invoke(sqlca, new Object[]{});
for (int errd : errds)
errdStr.append(errdStr.length() > 0 ? ", " : "").
append(errd);
StringBuilder exceptionMsg = new StringBuilder();
exceptionMsg.append("SQLCA OUTPUT");
exceptionMsg.append("[Errp=");
exceptionMsg.append(getSqlErrpMethd.invoke(sqlca, new Object[]{}));
exceptionMsg.append(", Errd=");
exceptionMsg.append(errdStr);
String Warn = new String((char[]) getSqlWarnMethd.
invoke(sqlca, new Object[]{}));
if (Warn.trim().length() != 0) {
exceptionMsg.append(", Warn=");
exceptionMsg.append(Warn);
exceptionMsg.append("]");
} else {
exceptionMsg.append("]");
}
msg = msg.concat(exceptionMsg.toString());
// for batched execution failures, SQLExceptions are nested
SQLException sqle2 = sqle.getNextException();
while (sqle2 != null) {
msg = msg.concat("\n" + sqle2.getMessage());
sqle2 = sqle2.getNextException();
}
return msg;
} catch (Throwable t) {
return sqle.getMessage();
}
}
public int getDb2ServerType() {
return db2ServerType;
}
@Override
protected void appendLength(SQLBuffer buf, int type) {
if (type == Types.VARCHAR)
buf.append("(").append(Integer.toString(characterColumnSize)).
append(")");
}
/**
* 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
* @param lhsxml indicates whether the left operand maps to xml
* @param rhsxml indicates whether the right operand maps to xml
*/
@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) {
boolean castrhs = false;
Class<?> rc = Filters.wrap(rhs.getType());
int type = 0;
if (rhs.isConstant()) {
type = getJDBCType(JavaTypes.getTypeCode(rc), false);
castrhs = true;
}
appendXmlExists(buf, lhs);
buf.append(" ").append(op).append(" ");
buf.append("$");
if (castrhs)
buf.append("Parm");
else
rhs.appendTo(buf);
buf.append("]' PASSING ");
appendXmlVar(buf, lhs);
buf.append(", ");
if (castrhs)
appendCast(buf, rhs, type);
else
rhs.appendTo(buf);
buf.append(" AS \"");
if (castrhs)
buf.append("Parm");
else
rhs.appendTo(buf);
buf.append("\")");
}
/**
* 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) {
appendXmlExists(buf, lhs);
buf.append(" ").append(op).append(" ");
buf.append("$").append(rhs.getColumnAlias(
rhs.getFieldMapping().getColumns()[0])).
append("/*/");
rhs.appendTo(buf);
buf.append("]' PASSING ");
appendXmlVar(buf, lhs);
buf.append(", ");
appendXmlVar(buf, rhs);
buf.append(")");
}
private void appendXmlVar(SQLBuffer buf, FilterValue val) {
buf.append(val.getColumnAlias(
val.getFieldMapping().getColumns()[0])).
append(" AS ").
append("\"").append(val.getColumnAlias(
val.getFieldMapping().getColumns()[0])).
append("\"");
}
private void appendXmlExists(SQLBuffer buf, FilterValue val) {
buf.append("XMLEXISTS('");
buf.append("$").append(val.getColumnAlias(
val.getFieldMapping().getColumns()[0])).
append("/*[");
val.appendTo(buf);
}
/**
* add CAST for a scalar function where operand is a param
*
* @param func original string
* @param target substring to look for
* @param asString
* @return updated string (func)
*/
private String addCastAsString(String func, String target,
String asString) {
String fstring = func;
if (func.indexOf(target) != -1) {
fstring = StringUtil.replace(func, target, "CAST(" + target + asString + ")");
}
return fstring;
}
/**
* add CAST for a function operator where operand is a param
*
* @param func function name
* @param val type
* @return updated string (func)
*/
@Override
public String addCastAsType(String func, Val val) {
String fstring = null;
String type = getTypeName(getJDBCType(JavaTypes.getTypeCode(val
.getType()), false));
if (String.class.equals(val.getType()))
type = type + "(" + getCastStringColumnSize(val) + ")";
fstring = "CAST(? AS " + type + ")";
return fstring;
}
/**
* Return the batch limit. If the batchLimit is -1, change it to 100 for
* best performance
*/
@Override
public int getBatchLimit() {
int limit = super.getBatchLimit();
if (limit == UNLIMITED) {
limit = defaultBatchLimit;
if (log.isTraceEnabled())
log.trace(_loc.get("batch_unlimit", String.valueOf(limit)));
}
return limit;
}
/**
* Return the correct CAST function syntax
*
* @param val operand of cast
* @param func original string
* @return a String with the correct CAST function syntax
*/
@Override
public String getCastFunction(Val val, String func) {
if (val instanceof Lit || val instanceof Param) {
if (func.indexOf("VARCHAR") == -1) {
func = addCastAsString(func, "{0}", " AS VARCHAR(" + varcharCastLength + ")");
}
}
return func;
}
/**
* Return the correct CAST function syntax
*
* @param val operand of cast
* @param func original string
* @param col database column
* @return a String with the correct CAST function syntax
*/
@Override
public String getCastFunction(Val val, String func, Column col) {
boolean doCast = false;
if (val instanceof Lit || val instanceof Param) {
doCast = true;
}
// cast anything not already a VARCHAR to VARCHAR
if (col.getType() != Types.VARCHAR) {
doCast = true;
}
if (doCast) {
if (func.indexOf("VARCHAR") == -1) {
func = addCastAsString(func, "{0}", " AS VARCHAR(" + varcharCastLength + ")");
}
}
return func;
}
@Override
public void indexOf(SQLBuffer buf, FilterValue str, FilterValue find,
FilterValue start) {
if (find.getValue() != null) { // non constants
buf.append("LOCATE(CAST((");
find.appendTo(buf);
buf.append(") AS VARCHAR(1000)), ");
} else {
// this is a constant
buf.append("LOCATE(");
find.appendTo(buf);
buf.append(", ");
}
if (str.getValue() != null) {
buf.append("CAST((");
str.appendTo(buf);
buf.append(") AS VARCHAR(1000))");
} else {
str.appendTo(buf);
}
if (start != null) {
if (start.getValue() != null) {
buf.append(", CAST((");
start.appendTo(buf);
buf.append(") AS INTEGER)");
} else {
buf.append(", ");
start.appendTo(buf);
}
}
buf.append(")");
}
/**
* Cast the specified value to the specified type.
*
* @param buf the buffer to append the cast to
* @param val the value to cast
* @param type the type of the case, e.g. {@link Types#NUMERIC}
*/
public void appendCast(SQLBuffer buf, FilterValue val, int type) {
// Convert the cast function: "CAST({0} AS {1})"
int firstParam = castFunction.indexOf("{0}");
String pre = castFunction.substring(0, firstParam); // "CAST("
String mid = castFunction.substring(firstParam + 3);
int secondParam = mid.indexOf("{1}");
String post;
if (secondParam > -1) {
post = mid.substring(secondParam + 3); // ")"
mid = mid.substring(0, secondParam); // " AS "
} else
post = "";
// No need to add CAST if the value is a constant
if (val instanceof Lit || val instanceof Param) {
buf.append(pre);
val.appendTo(buf);
buf.append(mid);
buf.append(getTypeName(type));
appendLength(buf, type);
buf.append(post);
} else {
val.appendTo(buf);
String sqlString = buf.getSQL(false);
if (sqlString.endsWith("?")) {
// case "(?" - convert to "CAST(? AS type"
String typeName = getTypeName(type);
if (String.class.equals(val.getType()))
typeName = typeName + "(" + getCastStringColumnSize(val) + ")";
String str = "CAST(? AS " + typeName + ")";
buf.replaceSqlString(sqlString.length() - 1,
sqlString.length(), str);
}
}
}
/**
* Create an index if necessary for some database tables
*/
@Override
public void createIndexIfNecessary(Schema schema, String table,
Column pkColumn) {
createIndexIfNecessary(schema, DBIdentifier.newTable(table),
pkColumn);
}
@Override
public void createIndexIfNecessary(Schema schema, DBIdentifier table,
Column pkColumn) {
if (db2ServerType == db2ZOSV8xOrLater) {
// build the index for the sequence tables
// the index name will be the fully qualified table name + _IDX
Table tab = schema.getTable(table);
DBIdentifier fullIdxId = tab.getFullIdentifier().clone();
DBIdentifier unQualifiedName = DBIdentifier.append(fullIdxId.getUnqualifiedName(), "IDX");
fullIdxId.setName(getValidIndexName(unQualifiedName, tab));
Index idx = tab.addIndex(fullIdxId);
idx.setUnique(true);
idx.addColumn(pkColumn);
}
}
@Override
public boolean isFatalException(int subtype, SQLException ex) {
String errorState = ex.getSQLState();
int errorCode = ex.getErrorCode();
if (errorCode == -952 && "57014".equals(errorState))
return false;
/*
* Check if this Exception was generated by a lock timeout expiration.
* The following criteria are used to determine this:
*
* DB2 LUW Infocenter: SQLSTATE=57033 with reason code "80" indicates
* the statement failed due to timeout. DB2 for z/OS Stored Procedures:
* Through the CALL and Beyond, page 188: An ErrorCode of -913 with
* SQLERR 00C9008E means a timeout has occurred.
*/
if (subtype == StoreException.LOCK && "57033".equals(errorState)
&& ((ex.getMessage().indexOf("80") != -1)
|| (errorCode == -913 && ex.getMessage().contains("00C9008E")))) {
return false;
}
if ((subtype == StoreException.QUERY && "57014".equals(errorState) &&
(errorCode == -952 || errorCode == -905))) {
return false;
}
return super.isFatalException(subtype, ex);
}
@Override
protected void setDelimitedCase(DatabaseMetaData metaData) {
delimitedCase = SCHEMA_CASE_PRESERVE;
}
/**
* The Type 2 JDBC Driver may throw an SQLException when provided a non-
* zero timeout if we're connected to Z/OS. The SQLException should be
* logged but not thrown.
*/
@Override
public void setQueryTimeout(PreparedStatement stmnt, int timeout)
throws SQLException {
if (db2ServerType == db2ZOSV8xOrLater) {
try {
super.setQueryTimeout(stmnt, timeout);
}
catch (SQLException e) {
if (log.isTraceEnabled()) {
log.trace(_loc.get("error-setting-query-timeout", timeout,
e.getMessage()), e);
}
}
}
else {
super.setQueryTimeout(stmnt, timeout);
}
}
/**
* Set the given value as a parameter to the statement.
*/
@Override
public void setBytes(PreparedStatement stmnt, int idx, byte[] val,
Column col)
throws SQLException {
// for DB2, if the column was defined as CHAR for BIT DATA, then
// we want to use the setBytes in stead of the setBinaryStream
if (useSetBytesForBlobs
|| (!DBIdentifier.isNull(col.getTypeIdentifier()) &&
col.getTypeIdentifier().getName().contains("BIT DATA"))) {
stmnt.setBytes(idx, val);
} else {
setBinaryStream(stmnt, idx, new ByteArrayInputStream(val), val.length, col);
}
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
@Override
public byte[] getBytes(ResultSet rs, int column)
throws SQLException {
if (useGetBytesForBlobs) {
return rs.getBytes(column);
}
if (useGetObjectForBlobs) {
return (byte[]) rs.getObject(column);
}
int type = rs.getMetaData().getColumnType(column);
switch (type) {
case Types.BLOB:
Blob blob = getBlob(rs, column);
if (blob == null) {
return null;
}
int length = (int) blob.length();
if (length == 0) {
return null;
}
return blob.getBytes(1, length);
case Types.BINARY:
default:
return rs.getBytes(column);
}
}
private int getCastStringColumnSize(Object val) {
int colSize = characterColumnSize;
if (val instanceof Lit) {
String literal = (String) ((Lit) val).getValue();
if (literal != null) {
int literalLen = literal.length();
if (literalLen > characterColumnSize) {
colSize = literalLen;
}
}
}
return colSize;
}
@Override
public void insertBlobForStreamingLoad(Row row, Column col,
JDBCStore store, Object ob, Select sel) throws SQLException {
if (ob != null) {
row.setBinaryStream(col, (InputStream)ob, -1);
} else {
row.setNull(col);
}
}
@Override
public void insertClobForStreamingLoad(Row row, Column col, Object ob)
throws SQLException {
if (ob != null) {
row.setCharacterStream(col, (Reader)ob, -1);
} else {
row.setNull(col);
}
}
@Override
public void updateBlob(Select sel, JDBCStore store, InputStream is)
throws SQLException {
//NO-OP
}
@Override
public void updateClob(Select sel, JDBCStore store, Reader reader)
throws SQLException {
//NO-OP
}
/**
* Set the given date value as a parameter to the statement.
*/
@Override
public void setDate(PreparedStatement stmnt, int idx, Date val, Column col)
throws SQLException {
// When column metadata is not available, DB2 on z/OS does not like the value produced
// by the default dictionary - java.util.Date is converted to java.sql.Timestamp.
if (db2ServerType == db2ZOSV8xOrLater) {
if (col == null && val != null && "java.util.Date".equals(val.getClass().getName())) {
setDate(stmnt, idx, new java.sql.Date(val.getTime()), null, col);
return;
}
}
super.setDate(stmnt, idx, val, col);
}
public int getDB2MajorVersion() {
return getMajorVersion();
}
public int getDB2MinorVersion() {
return getMinorVersion();
}
@Override
public String getDefaultSchemaName() {
if (defaultSchemaName == null) {
Connection conn = null;
Statement stmnt = null;
ResultSet rs = null;
try {
String str = "SELECT CURRENT SCHEMA FROM " + SYSDUMMY;
conn = getConnection();
if (conn != null) {
stmnt = conn.createStatement();
rs = stmnt.executeQuery(str);
if (rs.next()) {
String currSchema = rs.getString(1);
if (currSchema != null) {
setDefaultSchemaName(currSchema.trim());
}
}
} else {
if (log.isTraceEnabled()) {
log.trace(_loc.get("can_not_get_current_schema", "Unable to obtain a datasource"));
}
}
} catch (SQLException e) {
if (log.isTraceEnabled()) {
log.trace(_loc.get("can_not_get_current_schema", e.getMessage()));
}
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException se) {
// ignore
}
}
if (stmnt != null) {
try {
stmnt.close();
} catch (SQLException se) {
// ignore
}
}
if (conn != null) {
try {
conn.close();
}
catch(SQLException se) {
// ignore
}
}
}
}
return defaultSchemaName;
}
/**
* Obtain a connection from the configuration. Tries to use the jta-data-source first but falls back on the
* non-jta-data-source if no jta-data-source has been defined.
*
* In practice this method is only called by getDefaultSchemaName which in turn is only used by the schema tool.
*
* @throws SQLException If neither datasource is available.
* @return A connection which may be used to obtain the default schema name.
*/
private Connection getConnection() throws SQLException {
DataSource ds = null;
try {
// try to obtain a connection from the primary datasource
ds = conf.getDataSource(null);
} catch (UserException uex) {
}
if (ds==null) {
try {
// use datasource 2 if available
ds = conf.getDataSource2(null);
} catch (UserException uex2) {
}
}
if (ds != null) {
return ds.getConnection();
}
return null;
}
@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 (!supportsRowNum) {
return super.toSelect(select, fetch, tables, where, group, having, order,
distinct, forUpdate, start, end, sel);
}
// 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);
}
}