blob: 903f3b66cda3e2f2d678d3a082f3c43dd9f771fb [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.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.CharArrayReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.MessageFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javax.sql.DataSource;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.identifier.ColumnDefIdentifierRule;
import org.apache.openjpa.jdbc.identifier.DBIdentifier;
import org.apache.openjpa.jdbc.identifier.DBIdentifier.DBIdentifierType;
import org.apache.openjpa.jdbc.identifier.DBIdentifierRule;
import org.apache.openjpa.jdbc.identifier.DBIdentifierUtil;
import org.apache.openjpa.jdbc.identifier.Normalizer;
import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
import org.apache.openjpa.jdbc.kernel.JDBCStore;
import org.apache.openjpa.jdbc.kernel.exps.ExpContext;
import org.apache.openjpa.jdbc.kernel.exps.ExpState;
import org.apache.openjpa.jdbc.kernel.exps.FilterValue;
import org.apache.openjpa.jdbc.kernel.exps.Null;
import org.apache.openjpa.jdbc.kernel.exps.Val;
import org.apache.openjpa.jdbc.meta.ClassMapping;
import org.apache.openjpa.jdbc.meta.FieldMapping;
import org.apache.openjpa.jdbc.meta.JavaSQLTypes;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.DataSourceFactory;
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.NameSet;
import org.apache.openjpa.jdbc.schema.PrimaryKey;
import org.apache.openjpa.jdbc.schema.Schema;
import org.apache.openjpa.jdbc.schema.SchemaGroup;
import org.apache.openjpa.jdbc.schema.Sequence;
import org.apache.openjpa.jdbc.schema.Table;
import org.apache.openjpa.jdbc.schema.Unique;
import org.apache.openjpa.kernel.Filters;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.kernel.Seq;
import org.apache.openjpa.kernel.StateManagerImpl;
import org.apache.openjpa.kernel.exps.Path;
import org.apache.openjpa.lib.conf.Configurable;
import org.apache.openjpa.lib.conf.Configuration;
import org.apache.openjpa.lib.identifier.IdentifierConfiguration;
import org.apache.openjpa.lib.identifier.IdentifierRule;
import org.apache.openjpa.lib.identifier.IdentifierUtil;
import org.apache.openjpa.lib.jdbc.ConnectionDecorator;
import org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.Localizer.Message;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.meta.ValueStrategies;
import org.apache.openjpa.util.ExceptionInfo;
import org.apache.openjpa.util.GeneralException;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.InvalidStateException;
import org.apache.openjpa.util.LockException;
import org.apache.openjpa.util.ObjectExistsException;
import org.apache.openjpa.util.ObjectNotFoundException;
import org.apache.openjpa.util.OpenJPAException;
import org.apache.openjpa.util.OptimisticException;
import org.apache.openjpa.util.ProxyManager;
import org.apache.openjpa.util.QueryException;
import org.apache.openjpa.util.ReferentialIntegrityException;
import org.apache.openjpa.util.Serialization;
import org.apache.openjpa.util.StoreException;
import org.apache.openjpa.util.UnsupportedException;
import org.apache.openjpa.util.UserException;
/**
* Class which allows the creation of SQL dynamically, in a
* database agnostic fashion. Subclass for the nuances of different data stores.
*/
public class DBDictionary
implements Configurable, ConnectionDecorator, JoinSyntaxes,
LoggingConnectionDecorator.SQLWarningHandler, IdentifierConfiguration {
public static final String VENDOR_OTHER = "other";
public static final String VENDOR_DATADIRECT = "datadirect";
public static final String SCHEMA_CASE_UPPER = IdentifierUtil.CASE_UPPER;
public static final String SCHEMA_CASE_LOWER = IdentifierUtil.CASE_LOWER;
public static final String SCHEMA_CASE_PRESERVE = IdentifierUtil.CASE_PRESERVE;
public static final String CONS_NAME_BEFORE = "before";
public static final String CONS_NAME_MID = "mid";
public static final String CONS_NAME_AFTER = "after";
public int blobBufferSize = 50000;
public int clobBufferSize = 50000;
protected static final int RANGE_POST_SELECT = 0;
protected static final int RANGE_PRE_DISTINCT = 1;
protected static final int RANGE_POST_DISTINCT = 2;
protected static final int RANGE_POST_LOCK = 3;
protected static final int NANO = 1;
protected static final int MICRO = NANO * 1000;
protected static final int MILLI = MICRO * 1000;
protected static final int CENTI = MILLI * 10;
protected static final int DECI = MILLI * 100;
protected static final int SEC = MILLI * 1000;
protected static final int NAME_ANY = DBIdentifierUtil.ANY;
protected static final int NAME_TABLE = DBIdentifierUtil.TABLE;
protected static final int NAME_SEQUENCE = DBIdentifierUtil.SEQUENCE;
protected static final int UNLIMITED = -1;
protected static final int NO_BATCH = 0;
private static final String ZERO_DATE_STR = "'" + new java.sql.Date(0) + "'";
private static final String ZERO_TIME_STR = "'" + new Time(0) + "'";
private static final String ZERO_TIMESTAMP_STR = "'" + new Timestamp(0) + "'";
private static final Localizer _loc = Localizer.forPackage(DBDictionary.class);
// Database version info preferably set from Connection metadata
private int major;
private int minor;
// schema data
public String platform = "Generic";
public String databaseProductName = "";
public String databaseProductVersion = "";
public String driverVendor = null;
public boolean createPrimaryKeys = true;
public String constraintNameMode = CONS_NAME_BEFORE;
public int maxTableNameLength = 128;
public int maxColumnNameLength = 128;
public int maxConstraintNameLength = 128;
public int maxIndexNameLength = 128;
public int maxIndexesPerTable = Integer.MAX_VALUE;
public boolean supportsForeignKeys = true;
public boolean supportsParameterInSelect = true;
public boolean supportsForeignKeysComposite = true;
public boolean supportsUniqueConstraints = true;
public boolean supportsDeferredConstraints = true;
public boolean supportsRestrictDeleteAction = true;
public boolean supportsCascadeDeleteAction = true;
public boolean supportsNullDeleteAction = true;
public boolean supportsNullUniqueColumn = true;
public boolean supportsDefaultDeleteAction = true;
public boolean supportsRestrictUpdateAction = true;
public boolean supportsCascadeUpdateAction = true;
public boolean supportsNullUpdateAction = true;
public boolean supportsDefaultUpdateAction = true;
public boolean supportsAlterTableWithAddColumn = true;
public boolean supportsAlterTableWithDropColumn = true;
public boolean supportsComments = false;
public Boolean supportsGetGeneratedKeys = null;
public String reservedWords = null;
public String systemSchemas = null;
public String systemTables = null;
public String selectWords = null;
public String fixedSizeTypeNames = null;
public String schemaCase = SCHEMA_CASE_UPPER;
public boolean setStringRightTruncationOn = true;
public boolean fullResultCollectionInOrderByRelation = false;
public boolean disableSchemaFactoryColumnTypeErrors = false; //OPENJPA-2627
// sql
public boolean disableAlterSeqenceIncrementBy=false;
public String validationSQL = null;
public String closePoolSQL = null;
public String initializationSQL = null;
public int joinSyntax = SYNTAX_SQL92;
public String outerJoinClause = "LEFT OUTER JOIN";
public String innerJoinClause = "INNER JOIN";
public String crossJoinClause = "CROSS JOIN";
public boolean requiresConditionForCrossJoin = false;
public String forUpdateClause = "FOR UPDATE";
public String tableForUpdateClause = null;
public String distinctCountColumnSeparator = null;
public boolean supportsSelectForUpdate = true;
public boolean supportsLockingWithDistinctClause = true;
public boolean supportsLockingWithMultipleTables = true;
public boolean supportsLockingWithOrderClause = true;
public boolean supportsLockingWithOuterJoin = true;
public boolean supportsLockingWithInnerJoin = true;
public boolean supportsLockingWithSelectRange = true;
public boolean supportsQueryTimeout = true;
public boolean allowQueryTimeoutOnFindUpdate = false; //OPENJPA-2517
public boolean simulateLocking = false;
public boolean supportsSubselect = true;
public boolean supportsCorrelatedSubselect = true;
public boolean supportsHaving = true;
public boolean supportsSelectStartIndex = false;
public boolean supportsSelectEndIndex = false;
public int rangePosition = RANGE_POST_SELECT;
public boolean requiresAliasForSubselect = false;
public boolean requiresTargetForDelete = false;
public boolean allowsAliasInBulkClause = true;
public boolean supportsMultipleNontransactionalResultSets = true;
public boolean requiresSearchStringEscapeForLike = false;
public String searchStringEscape = "\\";
public boolean requiresCastForMathFunctions = false;
public boolean requiresCastForComparisons = false;
public boolean supportsModOperator = false;
public boolean supportsXMLColumn = false;
public boolean supportsCaseConversionForLob = false;
public boolean reportsSuccessNoInfoOnBatchUpdates = false;
public boolean supportsSelectFromFinalTable = false;
public boolean supportsSimpleCaseExpression = true;
public boolean supportsGeneralCaseExpression = true;
public boolean useWildCardForCount = false;
/**
* Some Databases append whitespace after the schema name
*/
public boolean trimSchemaName = false;
// functions
public String castFunction = "CAST({0} AS {1})";
public String toLowerCaseFunction = "LOWER({0})";
public String toUpperCaseFunction = "UPPER({0})";
public String stringLengthFunction = "CHAR_LENGTH({0})";
public String bitLengthFunction = "(OCTET_LENGTH({0}) * 8)";
public String trimLeadingFunction = "TRIM(LEADING {1} FROM {0})";
public String trimTrailingFunction = "TRIM(TRAILING {1} FROM {0})";
public String trimBothFunction = "TRIM(BOTH {1} FROM {0})";
public String concatenateFunction = "({0}||{1})";
public String concatenateDelimiter = "'OPENJPATOKEN'";
public String substringFunctionName = "SUBSTRING";
public String currentDateFunction = "CURRENT_DATE";
public String currentTimeFunction = "CURRENT_TIME";
public String currentTimestampFunction = "CURRENT_TIMESTAMP";
public String dropTableSQL = "DROP TABLE {0}";
// types
public boolean storageLimitationsFatal = false;
public boolean storeLargeNumbersAsStrings = false;
public boolean storeCharsAsNumbers = true;
public boolean trimStringColumns = false;
public boolean useGetBytesForBlobs = false;
public boolean useSetBytesForBlobs = false;
public boolean useGetObjectForBlobs = false;
public boolean useGetStringForClobs = false;
public boolean useSetStringForClobs = false;
public boolean useJDBC4SetBinaryStream = true;//OPENJPA-2067
public int maxEmbeddedBlobSize = -1;
public int maxEmbeddedClobSize = -1;
public int inClauseLimit = -1;
public int datePrecision = MILLI;
/**
* @deprecated Use 'dateMillisecondBehavior' instead.
*/
@Deprecated
public boolean roundTimeToMillisec = true;
/*
* This defines how the milliseconds of a Date field are handled
* when the Date is retrieved from the database, as follows:
*
* ROUND: This is the default. The
* Date will be rounded to the nearest millisecond.
* DROP: The milliseconds will be dropped, thus rounding is not
* performed. As an example, a date of '2010-01-01 12:00:00.687701'
* stored in the database will become '2010-01-01 12:00:00.000' in
* the Date field of the entity.
* RETAIN: The milliseconds will not be rounded and retained. As an
* example, a date of '2010-01-01 12:00:00.687701' stored in the
* database will become '2010-01-01 12:00:00.687' in the Date field
* of the entity.
*/
public enum DateMillisecondBehaviors { DROP, ROUND, RETAIN };
private DateMillisecondBehaviors dateMillisecondBehavior;
/**
* Defines how {@code Boolean} and {@code boolean} values get represented
* in OpenJPA. Default to {@code INT_10}.
* for backward compatibility.
*/
protected BooleanRepresentation booleanRepresentation = BooleanRepresentationFactory.INT_10;
public int characterColumnSize = 255;
public String arrayTypeName = "ARRAY";
public String bigintTypeName = "BIGINT";
public String binaryTypeName = "BINARY";
public String bitTypeName = "BIT";
public String blobTypeName = "BLOB";
public String booleanTypeName = "BOOLEAN";
public String charTypeName = "CHAR";
public String clobTypeName = "CLOB";
public String dateTypeName = "DATE";
public String decimalTypeName = "DECIMAL";
public String distinctTypeName = "DISTINCT";
public String doubleTypeName = "DOUBLE";
public String floatTypeName = "FLOAT";
public String integerTypeName = "INTEGER";
public String javaObjectTypeName = "JAVA_OBJECT";
public String longVarbinaryTypeName = "LONGVARBINARY";
public String longVarcharTypeName = "LONGVARCHAR";
public String nullTypeName = "NULL";
public String numericTypeName = "NUMERIC";
public String otherTypeName = "OTHER";
public String realTypeName = "REAL";
public String refTypeName = "REF";
public String smallintTypeName = "SMALLINT";
public String structTypeName = "STRUCT";
public String timeTypeName = "TIME";
public String timestampTypeName = "TIMESTAMP";
public String timeWithZoneTypeName = "TIME WITH TIME ZONE";
public String timestampWithZoneTypeName = "TIMESTAMP WITH TIME ZONE";
public String tinyintTypeName = "TINYINT";
public String varbinaryTypeName = "VARBINARY";
public String varcharTypeName = "VARCHAR";
public String xmlTypeName = "XML";
public String xmlTypeEncoding = "UTF-8";
public String getStringVal = "";
// schema metadata
public boolean useSchemaName = true;
public String tableTypes = "TABLE";
public boolean supportsSchemaForGetTables = true;
public boolean supportsSchemaForGetColumns = true;
public boolean supportsNullTableForGetColumns = true;
public boolean supportsNullTableForGetPrimaryKeys = false;
public boolean supportsNullTableForGetIndexInfo = false;
public boolean supportsNullTableForGetImportedKeys = false;
public boolean useGetBestRowIdentifierForPrimaryKeys = false;
public boolean requiresAutoCommitForMetaData = false;
public boolean tableLengthIncludesSchema = false;
// auto-increment
public int maxAutoAssignNameLength = 31;
public String autoAssignClause = null;
public String autoAssignTypeName = null;
public boolean supportsAutoAssign = false;
public String lastGeneratedKeyQuery = null;
public String nextSequenceQuery = null;
public String sequenceSQL = null;
public String sequenceSchemaSQL = null;
public String sequenceNameSQL = null;
// most native sequences can be run inside the business transaction
public int nativeSequenceType= Seq.TYPE_CONTIGUOUS;
/**
* This variable was used in 2.1.x and prior releases to indicate that
* OpenJPA should not use the CACHE clause when getting a native
* sequence; instead the INCREMENT BY clause gets its value equal to the
* allocationSize property. Post 2.1.x, code was added to allow
* said functionality by default (see OPENJPA-1376). For forward
* compatibility, this variable should not be removed.
*/
@Deprecated
public boolean useNativeSequenceCache = true;
/**
* If a user sets the previous variable (useNativeSequenceCache) to false, we should log a
* warning indicating that the variable no longer has an effect due to the code changes
* of OPENJPA-1376. 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 logNativeSequenceCacheWarning = true;
protected JDBCConfiguration conf = null;
protected Log log = null;
protected boolean connected = false;
protected boolean isJDBC3 = false;
protected boolean isJDBC4 = false;
protected final Set<String> reservedWordSet = new HashSet<>();
// reservedWordSet subset that CANNOT be used as valid column names
// (i.e., without surrounding them with double-quotes)
protected Set<String> invalidColumnWordSet = new HashSet<>();
protected final Set<String> systemSchemaSet = new HashSet<>();
protected final Set<String> systemTableSet = new HashSet<>();
protected final Set<String> fixedSizeTypeNameSet = new HashSet<>();
/**
* set of types that might have an optional fraction (sub-second) part.
* @{code @Column(scale=-1)} sets the fraction to zero.
* @{code @Column(scale=0)} will use the default behaviour (no fraction information generated)
*/
public final Set<String> fractionalTypeNameSet = new HashSet<>();
/**
* Default amount of digits for fractional Types.
* This is not supported/required by every database.
*
* This value is only being used if no explicit {@code @Column(scale=n)} is set or n is zero.
* Use {@code @Column(scale=-1)} to disable the explicit fraction part in the SQL generator.
* @see #fractionalTypeNameSet
* @see #getFractionLength(Column, String)
*/
public int defaultFractionLength = 6;
protected final Set<String> typeModifierSet = new HashSet<>();
// NamingConfiguration properties
private boolean delimitIdentifiers = false;
public Boolean supportsDelimitedIdentifiers = null;
public String leadingDelimiter = "\"";
public String trailingDelimiter = "\"";
public String nameConcatenator = "_";
public String delimitedCase = SCHEMA_CASE_PRESERVE;
public String catalogSeparator = ".";
protected String defaultSchemaName = null;
private String conversionKey = null;
// Naming utility and naming rules
private DBIdentifierUtil namingUtil = null;
private Map<String, IdentifierRule> namingRules = new HashMap<>();
private IdentifierRule defaultNamingRule = null; // cached for performance
/**
* If a native query begins with any of the values found here then it will
* be treated as a select statement.
*/
protected final Set<String> selectWordSet = new HashSet<>();
// when we store values that lose precision, track the types so that the
// first time it happens we can warn the user
private Set<Class<?>> _precisionWarnedTypes = null;
// batchLimit value:
// -1 = unlimited
// 0 = no batch
// any positive number = batch limit
public int batchLimit = NO_BATCH;
public final Map<Integer,Set<String>> sqlStateCodes =
new HashMap<>();
protected ProxyManager _proxyManager;
public DBDictionary() {
fixedSizeTypeNameSet.addAll(Arrays.asList(new String[]{
"BIGINT", "BIT", "BLOB", "CLOB", "DATE", "DECIMAL", "DISTINCT",
"DOUBLE", "FLOAT", "INTEGER", "JAVA_OBJECT", "NULL", "NUMERIC",
"OTHER", "REAL", "REF", "SMALLINT", "STRUCT", "TIME", "TIMESTAMP",
"TINYINT",
}));
selectWordSet.add("SELECT");
}
/**
* This method is called when the dictionary first sees any connection.
* It is used to initialize dictionary metadata if needed. If you
* override this method, be sure to call
* <code>super.connectedConfiguration</code>.
*/
public void connectedConfiguration(Connection conn)
throws SQLException {
if (!connected) {
DatabaseMetaData metaData = null;
try {
metaData = conn.getMetaData();
databaseProductName = nullSafe(metaData.getDatabaseProductName());
databaseProductVersion = nullSafe(metaData.getDatabaseProductVersion());
setMajorVersion(metaData.getDatabaseMajorVersion());
setMinorVersion(metaData.getDatabaseMinorVersion());
try {
// JDBC3-only method, so it might throw an
// AbstractMethodError
int JDBCMajorVersion = metaData.getJDBCMajorVersion();
isJDBC3 = JDBCMajorVersion >= 3;
isJDBC4 = JDBCMajorVersion >= 4;
} catch (Throwable t) {
// ignore if not JDBC3
}
} catch (Exception e) {
if (log.isTraceEnabled())
log.trace(e.toString(), e);
}
if (log.isTraceEnabled()) {
log.trace(DBDictionaryFactory.toString(metaData));
if (isJDBC3) {
try {
log.trace(_loc.get("connection-defaults", new Object[]{
conn.getAutoCommit(), conn.getHoldability(),
conn.getTransactionIsolation()}));
} catch (Throwable t) {
log.trace("Unable to trace connection settings", t);
}
}
}
// Configure the naming utility
if (supportsDelimitedIdentifiers == null) // not explicitly set
configureNamingUtil(metaData);
// Auto-detect generated keys retrieval support unless user specified it.
if (supportsGetGeneratedKeys == null) {
supportsGetGeneratedKeys = (isJDBC3) ? metaData.supportsGetGeneratedKeys() : false;
}
if (log.isInfoEnabled()) {
log.info(_loc.get("dict-info", new Object[] {
metaData.getDatabaseProductName(), getMajorVersion(), getMinorVersion(),
metaData.getDriverName(), metaData.getDriverVersion()}));
}
}
connected = true;
}
private void configureNamingUtil(DatabaseMetaData metaData) {
// Get the naming utility from the configuration
setSupportsDelimitedIdentifiers(metaData);
setDelimitedCase(metaData);
}
/**
* Configures the naming rules for this dictionary. Subclasses should
* override this method, providing their own naming rules.
*/
protected void configureNamingRules() {
// Add the default naming rule
DBIdentifierRule defRule = new DBIdentifierRule(DBIdentifierType.DEFAULT, reservedWordSet);
namingRules.put(defRule.getName(), defRule);
// Disable delimiting of column definition. DB platforms are very
// picky about delimiters in column definitions. Base column types
// do not require delimiters and will cause failures if delimited.
DBIdentifierRule cdRule = new ColumnDefIdentifierRule();
cdRule.setCanDelimit(false);
namingRules.put(cdRule.getName(), cdRule);
}
//////////////////////
// ResultSet wrappers
//////////////////////
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public Array getArray(ResultSet rs, int column)
throws SQLException {
return rs.getArray(column);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public InputStream getAsciiStream(ResultSet rs, int column)
throws SQLException {
return rs.getAsciiStream(column);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public BigDecimal getBigDecimal(ResultSet rs, int column)
throws SQLException {
if (storeLargeNumbersAsStrings) {
String str = getString(rs, column);
return (str == null) ? null : new BigDecimal(str);
}
return rs.getBigDecimal(column);
}
/**
* Returns the specified column value as an unknown numeric type;
* we try from the most generic to the least generic.
*/
public Number getNumber(ResultSet rs, int column)
throws SQLException {
// try from the most generic, and if errors occur, try
// less generic types; this enables us to handle values
// like Double.NaN without having to introspect on the
// ResultSetMetaData (bug #1053). Note that we handle
// generic exceptions, since some drivers may throw
// NumberFormatExceptions, whereas others may throw SQLExceptions
try {
return getBigDecimal(rs, column);
} catch (Exception e1) {
try {
return Double.valueOf(getDouble(rs, column));
} catch (Exception e2) {
try {
return Float.valueOf(getFloat(rs, column));
} catch (Exception e3) {
try {
return getLong(rs, column);
} catch (Exception e4) {
try {
return getInt(rs, column);
} catch (Exception e5) {
}
}
}
}
if (e1 instanceof RuntimeException)
throw(RuntimeException) e1;
if (e1 instanceof SQLException)
throw(SQLException) e1;
}
return null;
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public BigInteger getBigInteger(ResultSet rs, int column)
throws SQLException {
if (storeLargeNumbersAsStrings) {
String str = getString(rs, column);
return (str == null) ? null : new BigDecimal(str).toBigInteger();
}
BigDecimal bd = getBigDecimal(rs, column);
return (bd == null) ? null : bd.toBigInteger();
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public InputStream getBinaryStream(ResultSet rs, int column)
throws SQLException {
return rs.getBinaryStream(column);
}
public InputStream getLOBStream(JDBCStore store, ResultSet rs,
int column) throws SQLException {
return rs.getBinaryStream(column);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public Blob getBlob(ResultSet rs, int column)
throws SQLException {
return rs.getBlob(column);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public Object getBlobObject(ResultSet rs, int column, JDBCStore store)
throws SQLException {
InputStream in = null;
if (useGetBytesForBlobs || useGetObjectForBlobs) {
byte[] bytes = getBytes(rs, column);
if (bytes != null && bytes.length > 0)
in = new ByteArrayInputStream(bytes);
} else {
Blob blob = getBlob(rs, column);
if (blob != null && blob.length() > 0)
in = blob.getBinaryStream();
}
if (in == null)
return null;
try {
if (store == null)
return Serialization.deserialize(in, null);
return Serialization.deserialize(in, store.getContext());
} finally {
try {
in.close();
} catch (IOException ioe) {
}
}
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public boolean getBoolean(ResultSet rs, int column)
throws SQLException {
return booleanRepresentation.getBoolean(rs, column);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public byte getByte(ResultSet rs, int column)
throws SQLException {
return rs.getByte(column);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public byte[] getBytes(ResultSet rs, int column)
throws SQLException {
if (useGetBytesForBlobs)
return rs.getBytes(column);
if (useGetObjectForBlobs)
return (byte[]) rs.getObject(column);
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);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type. Converts the date from a {@link Timestamp} by default.
*/
public Calendar getCalendar(ResultSet rs, int column) throws SQLException {
Date d = getDate(rs, column);
if (d == null)
return null;
Calendar cal = (Calendar) getProxyManager().newCalendarProxy(GregorianCalendar.class, null);
cal.setTime(d);
return cal;
}
/**
* Retrieve the specified column of the SQL ResultSet to the proper
* {@link LocalDate} java type.
*/
public LocalDate getLocalDate(ResultSet rs, int column) throws SQLException {
java.sql.Date date = rs.getDate(column);
return date != null ? date.toLocalDate() : null;
}
/**
* Retrieve the specified column of the SQL ResultSet to the proper
* {@link LocalTime} java type.
*/
public LocalTime getLocalTime(ResultSet rs, int column) throws SQLException {
java.sql.Time time = rs.getTime(column);
return time != null ? time.toLocalTime() : null;
}
/**
* Retrieve the specified column of the SQL ResultSet to the proper
* {@link LocalDateTime} java type.
*/
public LocalDateTime getLocalDateTime(ResultSet rs, int column) throws SQLException {
Timestamp tst = rs.getTimestamp(column);
return tst != null ? tst.toLocalDateTime() : null;
}
/**
* Retrieve the specified column of the SQL ResultSet to the proper
* {@link OffsetTime} java type.
*/
public OffsetTime getOffsetTime(ResultSet rs, int column) throws SQLException {
java.sql.Time time = rs.getTime(column);
return time != null ? time.toLocalTime().atOffset(OffsetDateTime.now().getOffset()) : null;
}
/**
* Retrieve the specified column of the SQL ResultSet to the proper
* {@link OffsetDateTime} java type.
*/
public OffsetDateTime getOffsetDateTime(ResultSet rs, int column) throws SQLException {
Timestamp tst = rs.getTimestamp(column);
return tst != null ? tst.toLocalDateTime().atZone(ZoneId.systemDefault()).toOffsetDateTime() : null;
}
private ProxyManager getProxyManager() {
if (_proxyManager == null) {
_proxyManager = conf.getProxyManagerInstance();
}
return _proxyManager;
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public char getChar(ResultSet rs, int column)
throws SQLException {
if (storeCharsAsNumbers)
return (char) getInt(rs, column);
String str = getString(rs, column);
return (StringUtil.isEmpty(str)) ? 0 : str.charAt(0);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public Reader getCharacterStream(ResultSet rs, int column)
throws SQLException {
return rs.getCharacterStream(column);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public Clob getClob(ResultSet rs, int column)
throws SQLException {
return rs.getClob(column);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public String getClobString(ResultSet rs, int column)
throws SQLException {
if (useGetStringForClobs)
return rs.getString(column);
Clob clob = getClob(rs, column);
if (clob == null)
return null;
if (clob.length() == 0)
return "";
// unlikely that we'll have strings over Integer.MAX_VALUE chars
return clob.getSubString(1, (int) clob.length());
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type. Converts the date from a {@link Timestamp} by default.
*/
public Date getDate(ResultSet rs, int column)
throws SQLException {
Timestamp tstamp = getTimestamp(rs, column, null);
if (tstamp == null)
return null;
// get the fractional seconds component, rounding away anything beyond
// milliseconds
int fractional = 0;
switch (getMillisecondBehavior()) {
case DROP :
fractional = 0;
break;
case RETAIN :
fractional = (int) (tstamp.getNanos() / (double) MILLI);
break;
case ROUND :
fractional = (int) Math.round(tstamp.getNanos() / (double) MILLI);
break;
}
// get the millis component; some JDBC drivers round this to the
// nearest second, while others do not
long millis = (tstamp.getTime() / 1000L) * 1000L;
return new Date(millis + fractional);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public java.sql.Date getDate(ResultSet rs, int column, Calendar cal)
throws SQLException {
if (cal == null)
return rs.getDate(column);
return rs.getDate(column, cal);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public double getDouble(ResultSet rs, int column)
throws SQLException {
return rs.getDouble(column);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public float getFloat(ResultSet rs, int column)
throws SQLException {
return rs.getFloat(column);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public int getInt(ResultSet rs, int column)
throws SQLException {
return rs.getInt(column);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public Locale getLocale(ResultSet rs, int column)
throws SQLException {
String str = getString(rs, column);
if (StringUtil.isEmpty(str))
return null;
String[] params = StringUtil.split(str, "_", 3);
if (params.length < 3)
return null;
return new Locale(params[0], params[1], params[2]);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public long getLong(ResultSet rs, int column)
throws SQLException {
return rs.getLong(column);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public Object getObject(ResultSet rs, int column, Map map)
throws SQLException {
if (map == null)
return rs.getObject(column);
return rs.getObject(column, map);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public Ref getRef(ResultSet rs, int column, Map map)
throws SQLException {
return rs.getRef(column);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public short getShort(ResultSet rs, int column)
throws SQLException {
return rs.getShort(column);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public String getString(ResultSet rs, int column)
throws SQLException {
String res = rs.getString(column);
if ((res != null) && trimStringColumns) {
res = res.trim();
}
return res;
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public Time getTime(ResultSet rs, int column, Calendar cal)
throws SQLException {
if (cal == null)
return rs.getTime(column);
return rs.getTime(column, cal);
}
/**
* Convert the specified column of the SQL ResultSet to the proper
* java type.
*/
public Timestamp getTimestamp(ResultSet rs, int column, Calendar cal)
throws SQLException {
if (cal == null)
return rs.getTimestamp(column);
return rs.getTimestamp(column, cal);
}
//////////////////////////////
// PreparedStatement wrappers
//////////////////////////////
/**
* Set the given value as a parameter to the statement.
*/
public void setArray(PreparedStatement stmnt, int idx, Array val,
Column col)
throws SQLException {
stmnt.setArray(idx, val);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setAsciiStream(PreparedStatement stmnt, int idx, InputStream val, int length, Column col)
throws SQLException {
stmnt.setAsciiStream(idx, val, length);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setBigDecimal(PreparedStatement stmnt, int idx, BigDecimal val, Column col)
throws SQLException {
if ((col != null && col.isCompatible(Types.VARCHAR, null, 0, 0))
|| (col == null && storeLargeNumbersAsStrings)) {
setString(stmnt, idx, val.toString(), col);
}
else {
stmnt.setBigDecimal(idx, val);
}
}
/**
* Set the given value as a parameter to the statement.
*/
public void setBigInteger(PreparedStatement stmnt, int idx, BigInteger val, Column col)
throws SQLException {
if ((col != null && col.isCompatible(Types.VARCHAR, null, 0, 0))
|| (col == null && storeLargeNumbersAsStrings)) {
setString(stmnt, idx, val.toString(), col);
}
else {
setBigDecimal(stmnt, idx, new BigDecimal(val), col);
}
}
/**
* Set the given value as a parameter to the statement.
*/
public void setBinaryStream(PreparedStatement stmnt, int idx, InputStream val, int length, Column col)
throws SQLException {
//OPENJPA-2067: If the user has set the 'useJDBC4SetBinaryStream' property
//then lets use the JDBC 4.0 version of the setBinaryStream method.
if (useJDBC4SetBinaryStream) {
if (isJDBC4){
stmnt.setBinaryStream(idx, val);
return;
}
else {
log.trace(_loc.get("jdbc4-setbinarystream-unsupported"));
}
}
stmnt.setBinaryStream(idx, val, length);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setBlob(PreparedStatement stmnt, int idx, Blob val, Column col)
throws SQLException {
stmnt.setBlob(idx, val);
}
/**
* Set the given value as a parameter to the statement. Uses the
* {@link #serialize} method to serialize the value.
*/
public void setBlobObject(PreparedStatement stmnt, int idx, Object val, Column col, JDBCStore store)
throws SQLException {
setBytes(stmnt, idx, serialize(val, store), col);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setBoolean(PreparedStatement stmnt, int idx, boolean val, Column col)
throws SQLException {
booleanRepresentation.setBoolean(stmnt, idx, val);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setByte(PreparedStatement stmnt, int idx, byte val, Column col)
throws SQLException {
stmnt.setByte(idx, val);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setBytes(PreparedStatement stmnt, int idx, byte[] val,
Column col)
throws SQLException {
if (useSetBytesForBlobs)
stmnt.setBytes(idx, val);
else
setBinaryStream(stmnt, idx, new ByteArrayInputStream(val),
val.length, col);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setChar(PreparedStatement stmnt, int idx, char val, Column col)
throws SQLException {
if ((col != null && col.isCompatible(Types.INTEGER, null, 0, 0))
|| (col == null && storeCharsAsNumbers))
setInt(stmnt, idx, (int) val, col);
else
setString(stmnt, idx, String.valueOf(val), col);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setCharacterStream(PreparedStatement stmnt, int idx,
Reader val, int length, Column col)
throws SQLException {
stmnt.setCharacterStream(idx, val, length);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setClob(PreparedStatement stmnt, int idx, Clob val, Column col)
throws SQLException {
stmnt.setClob(idx, val);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setClobString(PreparedStatement stmnt, int idx, String val,
Column col)
throws SQLException {
if (useSetStringForClobs)
stmnt.setString(idx, val);
else {
// set reader from string
StringReader in = new StringReader(val);
setCharacterStream(stmnt, idx, in, val.length(), col);
}
}
/**
* Set the given value as a parameter to the statement.
*/
public void setDate(PreparedStatement stmnt, int idx, Date val, Column col)
throws SQLException {
if (col != null && col.getType() == Types.DATE)
setDate(stmnt, idx, new java.sql.Date(val.getTime()), null, col);
else if (col != null && col.getType() == Types.TIME)
setTime(stmnt, idx, new Time(val.getTime()), null, col);
else if (val instanceof Timestamp)
setTimestamp(stmnt, idx,(Timestamp) val, null, col);
else
setTimestamp(stmnt, idx, new Timestamp(val.getTime()), null, col);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setDate(PreparedStatement stmnt, int idx, java.sql.Date val, Calendar cal, Column col)
throws SQLException {
if (cal == null) {
stmnt.setDate(idx, val);
}
else {
stmnt.setDate(idx, val, cal);
}
}
/**
* Set the given value as a parameter to the statement.
*/
public void setCalendar(PreparedStatement stmnt, int idx, Calendar val, Column col)
throws SQLException {
// by default we merely delegate to the Date parameter
setDate(stmnt, idx, val.getTime(), col);
}
/**
* Set the given LocalDate value as a parameter to the statement.
*
*/
public void setLocalDate(PreparedStatement stmnt, int idx, LocalDate val, Column col)
throws SQLException {
setDate(stmnt, idx, java.sql.Date.valueOf(val), null, col);
}
/**
* Set the given LocalTime value as a parameter to the statement.
*
*/
public void setLocalTime(PreparedStatement stmnt, int idx, LocalTime val, Column col)
throws SQLException {
setTime(stmnt, idx, java.sql.Time.valueOf(val), null, col);
}
/**
* Set the given LocalTime value as a parameter to the statement.
*
*/
public void setLocalDateTime(PreparedStatement stmnt, int idx, LocalDateTime val, Column col)
throws SQLException {
setTimestamp(stmnt, idx, java.sql.Timestamp.valueOf(val), null, col);
}
/**
* Set the given LocalTime value as a parameter to the statement.
*
*/
public void setOffsetTime(PreparedStatement stmnt, int idx, OffsetTime val, Column col)
throws SQLException {
// adjust to the default timezone right now.
// This is an ugly hack and cries for troubles in case the daylight saving changes...
// Which is also the reason why we cannot cache the offset.
// According to the Oracle docs the JDBC driver always assumes 'local time' ...
LocalTime localTime = val.withOffsetSameInstant(OffsetDateTime.now().getOffset()).toLocalTime();
setLocalTime(stmnt, idx, localTime, col);
}
/**
* Set the given LocalTime value as a parameter to the statement.
*
*/
public void setOffsetDateTime(PreparedStatement stmnt, int idx, OffsetDateTime val, Column col)
throws SQLException {
LocalDateTime localdt = val.atZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime();
setLocalDateTime(stmnt, idx, localdt, col);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setDouble(PreparedStatement stmnt, int idx, double val, Column col)
throws SQLException {
stmnt.setDouble(idx, val);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setFloat(PreparedStatement stmnt, int idx, float val, Column col)
throws SQLException {
stmnt.setFloat(idx, val);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setInt(PreparedStatement stmnt, int idx, int val, Column col)
throws SQLException {
stmnt.setInt(idx, val);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setLong(PreparedStatement stmnt, int idx, long val, Column col)
throws SQLException {
stmnt.setLong(idx, val);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setLocale(PreparedStatement stmnt, int idx, Locale val, Column col)
throws SQLException {
setString(stmnt, idx, val.getLanguage() + "_" + val.getCountry()
+ "_" + val.getVariant(), col);
}
/**
* Set null as a parameter to the statement. The column
* type will come from {@link Types}.
*/
public void setNull(PreparedStatement stmnt, int idx, int colType, Column col)
throws SQLException {
stmnt.setNull(idx, colType);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setNumber(PreparedStatement stmnt, int idx, Number num, Column col)
throws SQLException {
// check for known floating point types to give driver a chance to
// handle special numbers like NaN and infinity; bug #1053
if (num instanceof Double)
setDouble(stmnt, idx, ((Double) num).doubleValue(), col);
else if (num instanceof Float)
setFloat(stmnt, idx, ((Float) num).floatValue(), col);
else
setBigDecimal(stmnt, idx, new BigDecimal(num.toString()), col);
}
/**
* Set the given value as a parameter to the statement. The column
* type will come from {@link Types}.
*/
public void setObject(PreparedStatement stmnt, int idx, Object val, int colType, Column col)
throws SQLException {
if (colType == -1 || colType == Types.OTHER)
stmnt.setObject(idx, val);
else
stmnt.setObject(idx, val, colType);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setRef(PreparedStatement stmnt, int idx, Ref val, Column col)
throws SQLException {
stmnt.setRef(idx, val);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setShort(PreparedStatement stmnt, int idx, short val, Column col)
throws SQLException {
stmnt.setShort(idx, val);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setString(PreparedStatement stmnt, int idx, String val, Column col)
throws SQLException {
stmnt.setString(idx, val);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setTime(PreparedStatement stmnt, int idx, Time val, Calendar cal, Column col)
throws SQLException {
if (cal == null)
stmnt.setTime(idx, val);
else
stmnt.setTime(idx, val, cal);
}
/**
* Set the given value as a parameter to the statement.
*/
public void setTimestamp(PreparedStatement stmnt, int idx, Timestamp val, Calendar cal, Column col)
throws SQLException {
val = StateManagerImpl.roundTimestamp(val, datePrecision);
if (cal == null)
stmnt.setTimestamp(idx, val);
else
stmnt.setTimestamp(idx, val, cal);
}
/**
* Set a column value into a prepared statement.
*
* @param stmnt the prepared statement to parameterize
* @param idx the index of the parameter in the prepared statement
* @param val the value of the column
* @param col the column being set
* @param type the field mapping type code for the value
* @param store the store manager for the current context
*/
public void setTyped(PreparedStatement stmnt, int idx, Object val, Column col, int type, JDBCStore store)
throws SQLException {
if (val == null) {
setNull(stmnt, idx, (col == null) ? Types.OTHER : col.getType(),
col);
return;
}
Sized s;
Calendard c;
switch (type) {
case JavaTypes.BOOLEAN:
case JavaTypes.BOOLEAN_OBJ:
setBoolean(stmnt, idx, ((Boolean) val).booleanValue(), col);
break;
case JavaTypes.BYTE:
case JavaTypes.BYTE_OBJ:
setByte(stmnt, idx, ((Number) val).byteValue(), col);
break;
case JavaTypes.CHAR:
case JavaTypes.CHAR_OBJ:
setChar(stmnt, idx, ((Character) val).charValue(), col);
break;
case JavaTypes.DOUBLE:
case JavaTypes.DOUBLE_OBJ:
setDouble(stmnt, idx, ((Number) val).doubleValue(), col);
break;
case JavaTypes.FLOAT:
case JavaTypes.FLOAT_OBJ:
setFloat(stmnt, idx, ((Number) val).floatValue(), col);
break;
case JavaTypes.INT:
case JavaTypes.INT_OBJ:
setInt(stmnt, idx, ((Number) val).intValue(), col);
break;
case JavaTypes.LONG:
case JavaTypes.LONG_OBJ:
setLong(stmnt, idx, ((Number) val).longValue(), col);
break;
case JavaTypes.SHORT:
case JavaTypes.SHORT_OBJ:
setShort(stmnt, idx, ((Number) val).shortValue(), col);
break;
case JavaTypes.STRING:
if (col != null && (col.getType() == Types.CLOB
|| col.getType() == Types.LONGVARCHAR))
setClobString(stmnt, idx, (String) val, col);
else {
if (val instanceof String)
setString(stmnt, idx, (String) val, col);
else
setString(stmnt, idx, val.toString(), col);
}
break;
case JavaTypes.OBJECT:
setBlobObject(stmnt, idx, val, col, store);
break;
case JavaTypes.DATE:
setDate(stmnt, idx, (Date) val, col);
break;
case JavaTypes.CALENDAR:
setCalendar(stmnt, idx, (Calendar) val, col);
break;
case JavaTypes.LOCAL_DATE:
setLocalDate(stmnt, idx, (LocalDate) val, col);
break;
case JavaTypes.LOCAL_TIME:
setLocalTime(stmnt, idx, (LocalTime) val, col);
break;
case JavaTypes.LOCAL_DATETIME:
setLocalDateTime(stmnt, idx, (LocalDateTime) val, col);
break;
case JavaTypes.OFFSET_TIME:
setOffsetTime(stmnt, idx, (OffsetTime) val, col);
break;
case JavaTypes.OFFSET_DATETIME:
setOffsetDateTime(stmnt, idx, (OffsetDateTime) val, col);
break;
case JavaTypes.BIGDECIMAL:
setBigDecimal(stmnt, idx, (BigDecimal) val, col);
break;
case JavaTypes.BIGINTEGER:
setBigInteger(stmnt, idx, (BigInteger) val, col);
break;
case JavaTypes.NUMBER:
setNumber(stmnt, idx, (Number) val, col);
break;
case JavaTypes.LOCALE:
setLocale(stmnt, idx, (Locale) val, col);
break;
case JavaSQLTypes.SQL_ARRAY:
setArray(stmnt, idx, (Array) val, col);
break;
case JavaSQLTypes.ASCII_STREAM:
s = (Sized) val;
setAsciiStream(stmnt, idx, (InputStream) s.value, s.size, col);
break;
case JavaSQLTypes.BINARY_STREAM:
s = (Sized) val;
setBinaryStream(stmnt, idx, (InputStream) s.value, s.size, col);
break;
case JavaSQLTypes.BLOB:
setBlob(stmnt, idx, (Blob) val, col);
break;
case JavaSQLTypes.BYTES:
setBytes(stmnt, idx, (byte[]) val, col);
break;
case JavaSQLTypes.CHAR_STREAM:
s = (Sized) val;
setCharacterStream(stmnt, idx, (Reader) s.value, s.size, col);
break;
case JavaSQLTypes.CLOB:
setClob(stmnt, idx, (Clob) val, col);
break;
case JavaSQLTypes.SQL_DATE:
if (val instanceof Calendard) {
c = (Calendard) val;
setDate(stmnt, idx, (java.sql.Date) c.value, c.calendar,
col);
} else
setDate(stmnt, idx, (java.sql.Date) val, null, col);
break;
case JavaSQLTypes.REF:
setRef(stmnt, idx, (Ref) val, col);
break;
case JavaSQLTypes.TIME:
if (val instanceof Calendard) {
c = (Calendard) val;
setTime(stmnt, idx, (Time) c.value, c.calendar, col);
} else
setTime(stmnt, idx, (Time) val, null, col);
break;
case JavaSQLTypes.TIMESTAMP:
if (val instanceof Calendard) {
c = (Calendard) val;
setTimestamp(stmnt, idx, (Timestamp) c.value, c.calendar,
col);
} else
setTimestamp(stmnt, idx, (Timestamp) val, null, col);
break;
default:
if (col != null && (col.getType() == Types.BLOB
|| col.getType() == Types.VARBINARY))
setBlobObject(stmnt, idx, val, col, store);
else
setObject(stmnt, idx, val, col.getType(), col);
}
}
/**
* Set a completely unknown parameter into a prepared statement.
*/
public void setUnknown(PreparedStatement stmt, int idx, Object val, Column col) throws SQLException {
if (val instanceof Object[]) {
Object[] valArray = (Object[])val;
for (Object object : valArray) {
setUnknown(stmt, idx, col, object);
}
} else {
setUnknown(stmt, idx, col, val);
}
}
/**
* Set a completely unknown parameter into a prepared statement.
*/
public void setUnknown(PreparedStatement stmnt, int idx, Column col, Object val) throws SQLException {
Sized sized = null;
Calendard cald = null;
if (val instanceof Sized) {
sized = (Sized) val;
val = sized.value;
} else if (val instanceof Calendard) {
cald = (Calendard) val;
val = cald.value;
}
if (val == null)
setNull(stmnt, idx, (col == null) ? Types.OTHER : col.getType(),
col);
else if (val instanceof String)
setString(stmnt, idx, val.toString(), col);
else if (val instanceof Integer)
setInt(stmnt, idx, ((Integer) val).intValue(), col);
else if (val instanceof Boolean)
setBoolean(stmnt, idx, ((Boolean) val).booleanValue(), col);
else if (val instanceof Long)
setLong(stmnt, idx, ((Long) val).longValue(), col);
else if (val instanceof Float)
setFloat(stmnt, idx, ((Float) val).floatValue(), col);
else if (val instanceof Double)
setDouble(stmnt, idx, ((Double) val).doubleValue(), col);
else if (val instanceof Byte)
setByte(stmnt, idx, ((Byte) val).byteValue(), col);
else if (val instanceof Character)
setChar(stmnt, idx, ((Character) val).charValue(), col);
else if (val instanceof Short)
setShort(stmnt, idx, ((Short) val).shortValue(), col);
else if (val instanceof Locale)
setLocale(stmnt, idx, (Locale) val, col);
else if (val instanceof BigDecimal)
setBigDecimal(stmnt, idx, (BigDecimal) val, col);
else if (val instanceof BigInteger)
setBigInteger(stmnt, idx, (BigInteger) val, col);
else if (val instanceof Array)
setArray(stmnt, idx, (Array) val, col);
else if (val instanceof Blob)
setBlob(stmnt, idx, (Blob) val, col);
else if (val instanceof byte[])
setBytes(stmnt, idx, (byte[]) val, col);
else if (val instanceof Clob)
setClob(stmnt, idx, (Clob) val, col);
else if (val instanceof Ref)
setRef(stmnt, idx, (Ref) val, col);
else if (val instanceof java.sql.Date)
setDate(stmnt, idx, (java.sql.Date) val,
(cald == null) ? null : cald.calendar, col);
else if (val instanceof Timestamp)
setTimestamp(stmnt, idx, (Timestamp) val,
(cald == null) ? null : cald.calendar, col);
else if (val instanceof Time)
setTime(stmnt, idx, (Time) val,
(cald == null) ? null : cald.calendar, col);
else if (val instanceof Date)
setDate(stmnt, idx, (Date) val, col);
else if (val instanceof Calendar)
setDate(stmnt, idx, ((Calendar) val).getTime(), col);
else if (val instanceof LocalDate) {
setLocalDate(stmnt, idx, (LocalDate) val, col);
}
else if (val instanceof LocalTime) {
setLocalTime(stmnt, idx, (LocalTime) val, col);
}
else if (val instanceof LocalDateTime) {
setLocalDateTime(stmnt, idx, (LocalDateTime) val, col);
}
else if (val instanceof Reader)
setCharacterStream(stmnt, idx, (Reader) val,
(sized == null) ? 0 : sized.size, col);
else
throw new UserException(_loc.get("bad-param", val.getClass()));
}
/**
* Return the serialized bytes for the given object.
*/
public byte[] serialize(Object val, JDBCStore store)
throws SQLException {
if (val == null)
return null;
if (val instanceof SerializedData)
return ((SerializedData) val).bytes;
return Serialization.serialize(val, store.getContext());
}
/**
* Invoke the JDK 1.4 <code>setBytes</code> method on the given BLOB object.
*/
public void putBytes(Blob blob, byte[] data)
throws SQLException {
blob.setBytes(1L, data);
}
/**
* Invoke the JDK 1.4 <code>setString</code> method on the given CLOB
* object.
*/
public void putString(Clob clob, String data)
throws SQLException {
clob.setString(1L, data);
}
/**
* Invoke the JDK 1.4 <code>setCharacterStream</code> method on the given
* CLOB object.
*/
public void putChars(Clob clob, char[] data)
throws SQLException {
Writer writer = clob.setCharacterStream(1L);
try {
writer.write(data);
writer.flush();
} catch (IOException ioe) {
throw new SQLException(ioe.toString());
}
}
/**
* Warn that a particular value could not be stored precisely.
* After the first warning for a particular type, messages
* will be turned into trace messages.
*/
protected void storageWarning(Object orig, Object converted) {
boolean warn;
synchronized (this) {
if (_precisionWarnedTypes == null)
_precisionWarnedTypes = new HashSet<>();
warn = _precisionWarnedTypes.add(orig.getClass());
}
if (storageLimitationsFatal || (warn && log.isWarnEnabled())
|| (!warn && log.isTraceEnabled())) {
Message msg = _loc.get("storage-restriction", new Object[]{
platform,
orig,
orig.getClass().getName(),
converted,
});
if (storageLimitationsFatal)
throw new StoreException(msg);
if (warn)
log.warn(msg);
else
log.trace(msg);
}
}
/////////
// Types
/////////
/**
* Return the preferred {@link Types} constant for the given
* {@link JavaTypes} or {@link JavaSQLTypes} constant.
*/
public int getJDBCType(int metaTypeCode, boolean lob) {
return getJDBCType(metaTypeCode, lob, 0, 0);
}
/**
* Return the preferred {@link Types} constant for the given
* {@link JavaTypes} or {@link JavaSQLTypes} constant.
*/
public int getJDBCType(int metaTypeCode, boolean lob, int precis,
int scale, boolean xml) {
return getJDBCType(metaTypeCode, lob, precis, scale);
}
/**
* Return the preferred {@link Types} constant for the given
* {@link JavaTypes} or {@link JavaSQLTypes} constant.
*/
public int getJDBCType(int metaTypeCode, boolean lob, int precis,
int scale) {
if (lob) {
switch (metaTypeCode) {
case JavaTypes.STRING:
case JavaSQLTypes.ASCII_STREAM:
case JavaSQLTypes.CHAR_STREAM:
return getPreferredType(Types.CLOB);
default:
return getPreferredType(Types.BLOB);
}
}
switch (metaTypeCode) {
case JavaTypes.BOOLEAN:
case JavaTypes.BOOLEAN_OBJ:
return getPreferredType(Types.BIT);
case JavaTypes.BYTE:
case JavaTypes.BYTE_OBJ:
return getPreferredType(Types.TINYINT);
case JavaTypes.CHAR:
case JavaTypes.CHAR_OBJ:
if (storeCharsAsNumbers)
return getPreferredType(Types.INTEGER);
return getPreferredType(Types.CHAR);
case JavaTypes.DOUBLE:
case JavaTypes.DOUBLE_OBJ:
if(precis > 0 || scale > 0) {
return getPreferredType(Types.NUMERIC);
}
else {
return getPreferredType(Types.DOUBLE);
}
case JavaTypes.FLOAT:
case JavaTypes.FLOAT_OBJ:
if(precis > 0 || scale > 0) {
return getPreferredType(Types.NUMERIC);
}
else {
return getPreferredType(Types.REAL);
}
case JavaTypes.INT:
case JavaTypes.INT_OBJ:
return getPreferredType(Types.INTEGER);
case JavaTypes.LONG:
case JavaTypes.LONG_OBJ:
return getPreferredType(Types.BIGINT);
case JavaTypes.SHORT:
case JavaTypes.SHORT_OBJ:
return getPreferredType(Types.SMALLINT);
case JavaTypes.STRING:
case JavaTypes.LOCALE:
case JavaSQLTypes.ASCII_STREAM:
case JavaSQLTypes.CHAR_STREAM:
return getPreferredType(Types.VARCHAR);
case JavaTypes.BIGINTEGER:
if (storeLargeNumbersAsStrings)
return getPreferredType(Types.VARCHAR);
return getPreferredType(Types.BIGINT);
case JavaTypes.BIGDECIMAL:
if (storeLargeNumbersAsStrings)
return getPreferredType(Types.VARCHAR);
return getPreferredType(Types.NUMERIC);
case JavaTypes.NUMBER:
if (storeLargeNumbersAsStrings)
return getPreferredType(Types.VARCHAR);
return getPreferredType(Types.NUMERIC);
case JavaTypes.CALENDAR:
case JavaTypes.DATE:
return getPreferredType(Types.TIMESTAMP);
case JavaTypes.LOCAL_DATE:
return getPreferredType(Types.DATE);
case JavaTypes.LOCAL_TIME:
return getPreferredType(Types.TIME);
case JavaTypes.LOCAL_DATETIME:
return getPreferredType(Types.TIMESTAMP);
case JavaTypes.OFFSET_TIME:
return getPreferredType(Types.TIME_WITH_TIMEZONE);
case JavaTypes.OFFSET_DATETIME:
return getPreferredType(Types.TIMESTAMP_WITH_TIMEZONE);
case JavaSQLTypes.SQL_ARRAY:
return getPreferredType(Types.ARRAY);
case JavaSQLTypes.BINARY_STREAM:
case JavaSQLTypes.BLOB:
case JavaSQLTypes.BYTES:
return getPreferredType(Types.BLOB);
case JavaSQLTypes.CLOB:
return getPreferredType(Types.CLOB);
case JavaSQLTypes.SQL_DATE:
return getPreferredType(Types.DATE);
case JavaSQLTypes.TIME:
return getPreferredType(Types.TIME);
case JavaSQLTypes.TIMESTAMP:
return getPreferredType(Types.TIMESTAMP);
default:
return getPreferredType(Types.BLOB);
}
}
/**
* Return the preferred {@link Types} type for the given one. Returns
* the given type by default.
*/
public int getPreferredType(int type) {
return type;
}
/**
* Return the preferred database type name for the given column's type
* from {@link Types}.
*/
public String getTypeName(Column col) {
if (!DBIdentifier.isEmpty(col.getTypeIdentifier()))
return appendSize(col, toDBName(col.getTypeIdentifier()));
if (col.isAutoAssigned() && autoAssignTypeName != null)
return appendSize(col, autoAssignTypeName);
return appendSize(col, getTypeName(col.getType()));
}
/**
* Returns the type name for the specific constant as defined
* by {@link java.sql.Types}.
*
* @param type the type
* @return the name for the type
*/
public String getTypeName(int type) {
switch (type) {
case Types.ARRAY:
return arrayTypeName;
case Types.BIGINT:
return bigintTypeName;
case Types.BINARY:
return binaryTypeName;
case Types.BIT:
return bitTypeName;
case Types.BLOB:
return blobTypeName;
case Types.BOOLEAN:
return booleanTypeName;
case Types.CHAR:
return charTypeName;
case Types.CLOB:
return clobTypeName;
case Types.DATE:
return dateTypeName;
case Types.DECIMAL:
return decimalTypeName;
case Types.DISTINCT:
return distinctTypeName;
case Types.DOUBLE:
return doubleTypeName;
case Types.FLOAT:
return floatTypeName;
case Types.INTEGER:
return integerTypeName;
case Types.JAVA_OBJECT:
return javaObjectTypeName;
case Types.LONGVARBINARY:
return longVarbinaryTypeName;
case Types.LONGVARCHAR:
return longVarcharTypeName;
case Types.NULL:
return nullTypeName;
case Types.NUMERIC:
return numericTypeName;
case Types.OTHER:
return otherTypeName;
case Types.REAL:
return realTypeName;
case Types.REF:
return refTypeName;
case Types.SMALLINT:
return smallintTypeName;
case Types.STRUCT:
return structTypeName;
case Types.TIME:
return timeTypeName;
case Types.TIMESTAMP:
return timestampTypeName;
case Types.TIME_WITH_TIMEZONE:
return timeWithZoneTypeName;
case Types.TIMESTAMP_WITH_TIMEZONE:
return timestampWithZoneTypeName;
case Types.TINYINT:
return tinyintTypeName;
case Types.VARBINARY:
return varbinaryTypeName;
case Types.VARCHAR:
return varcharTypeName;
default:
return otherTypeName;
}
}
/**
* Helper method to add size properties to the specified type.
* If present, the string "{0}" will be replaced with the size definition;
* otherwise the size definition will be appended to the type name.
* If your database has column types that don't allow size definitions,
* override this method to return the unaltered type name for columns of
* those types (or add the type names to the
* <code>fixedSizeTypeNameSet</code>).
*
* <P>Some databases support "type modifiers", for example the unsigned
* "modifier" in MySQL. In these cases the size should go between the type
* and the "modifier", instead of after the modifier. For example
* CREATE table FOO ( myint INT (10) UNSIGNED . . .) instead of
* CREATE table FOO ( myint INT UNSIGNED (10) . . .).
* Type modifiers should be added to <code>typeModifierSet</code> in
* subclasses.
*/
protected String appendSize(Column col, String typeName) {
String upperCaseTypeName = typeName.toUpperCase(Locale.ENGLISH);
if (fixedSizeTypeNameSet.contains(upperCaseTypeName)) {
return typeName;
}
if (typeName.indexOf('(') != -1) {
return typeName;
}
String size = null;
int colSize = col.getSize();
if (colSize<=0 && fractionalTypeNameSet.contains(upperCaseTypeName)){
// special handling for types with fractions
// Attention! We abuse @Column(scale=n)
// One can disable all fractions with @Column(scale=-1)
if (col.getDecimalDigits() != 0) { // the default
colSize = col.getDecimalDigits() == -1 ? 0 : col.getDecimalDigits();
}
else {
colSize = getFractionLength(col, typeName);
}
size = "(" + colSize + ")";
} else if (colSize > 0) {
StringBuilder buf = new StringBuilder(10);
buf.append("(").append(colSize);
if (col.getDecimalDigits() > 0)
buf.append(", ").append(col.getDecimalDigits());
buf.append(")");
size = buf.toString();
}
return insertSize(typeName, size);
}
/**
* Only get's used if no explicit scale is used (@{code @Column(scale=0)} which is the default).
* Attention! @{code @Column(scale=-1)} disables the scale of a column
* @return the fraction length of types which have a fraction
* @see #fractionalTypeNameSet
*/
protected int getFractionLength(Column col, String typeName) {
return defaultFractionLength;
}
/**
* Helper method that inserts a size clause for a given SQL type.
*
* @param typeName The SQL type e.g. INT
* @param size The size clause e.g. (10)
* @return The typeName + size clause. Usually the size clause will
* be appended to typeName. If the typeName contains a
* marker : {0} or if typeName contains a modifier the
* size clause will be inserted appropriately.
*/
protected String insertSize(String typeName, String size) {
if (StringUtil.isEmpty(size)) {
int idx = typeName.indexOf("{0}");
if (idx != -1) {
return typeName.substring(0, idx);
}
return typeName;
}
int idx = typeName.indexOf("{0}");
if (idx != -1) {
// replace '{0}' with size
String ret = typeName.substring(0, idx);
if (size != null)
ret = ret + size;
if (typeName.length() > idx + 3)
ret = ret + typeName.substring(idx + 3);
return ret;
}
if (!typeModifierSet.isEmpty()) {
String s;
idx = typeName.length();
int curIdx = -1;
for (Iterator<String> i = typeModifierSet.iterator(); i.hasNext();) {
s = i.next();
if (typeName.toUpperCase(Locale.ENGLISH).indexOf(s) != -1) {
curIdx = typeName.toUpperCase(Locale.ENGLISH).indexOf(s);
if (curIdx != -1 && curIdx < idx) {
idx = curIdx;
}
}
}
if(idx != typeName.length()) {
String ret = typeName.substring(0, idx);
ret = ret + size;
ret = ret + ' ' + typeName.substring(idx);
return ret;
}
}
return typeName + size;
}
///////////
// Selects
///////////
/**
* Set the name of the join syntax to use: sql92, traditional, database.
*/
public void setJoinSyntax(String syntax) {
if ("sql92".equals(syntax))
joinSyntax = SYNTAX_SQL92;
else if ("traditional".equals(syntax))
joinSyntax = SYNTAX_TRADITIONAL;
else if ("database".equals(syntax))
joinSyntax = SYNTAX_DATABASE;
else if (!StringUtil.isEmpty(syntax))
throw new IllegalArgumentException(syntax);
}
public boolean isImplicitJoin() {
return false;
}
/**
* Return a SQL string to act as a placeholder for the given column.
*/
public String getPlaceholderValueString(Column col) {
switch (col.getType()) {
case Types.BIGINT:
case Types.BIT:
case Types.INTEGER:
case Types.NUMERIC:
case Types.SMALLINT:
case Types.TINYINT:
return "0";
case Types.CHAR:
return (storeCharsAsNumbers) ? "0" : "' '";
case Types.CLOB:
case Types.LONGVARCHAR:
case Types.VARCHAR:
return "''";
case Types.DATE:
return ZERO_DATE_STR;
case Types.DECIMAL:
case Types.DOUBLE:
case Types.FLOAT:
case Types.REAL:
return "0.0";
case Types.TIME:
return ZERO_TIME_STR;
case Types.TIMESTAMP:
return ZERO_TIMESTAMP_STR;
default:
return "NULL";
}
}
/**
* Create a SELECT COUNT statement in the proper join syntax for the
* given instance.
*/
public SQLBuffer toSelectCount(Select sel) {
SQLBuffer selectSQL = new SQLBuffer(this);
SQLBuffer from;
sel.addJoinClassConditions();
if (sel.getFromSelect() != null)
from = getFromSelect(sel, false);
else
from = getFrom(sel, false);
SQLBuffer where = getWhere(sel, false);
// if no grouping and no range, we might be able to get by without
// a subselect
if (sel.getGrouping() == null && sel.getStartIndex() == 0
&& sel.getEndIndex() == Long.MAX_VALUE) {
// if the select has no identifier cols, use COUNT(*)
List aliases = (!sel.isDistinct()) ? Collections.EMPTY_LIST
: sel.getIdentifierAliases();
if (useWildCardForCount || aliases.isEmpty()) {
selectSQL.append("COUNT(*)");
return toSelect(selectSQL, null, from, where, null, null, null,
false, false, 0, Long.MAX_VALUE);
}
// if there is a single distinct col, use COUNT(DISTINCT col)
if (aliases.size() == 1) {
selectSQL.append("COUNT(DISTINCT ").
append(aliases.get(0).toString()).append(")");
return toSelect(selectSQL, null, from, where, null, null, null,
false, false, 0, Long.MAX_VALUE);
}
// can we combine distinct cols?
if (distinctCountColumnSeparator != null) {
selectSQL.append("COUNT(DISTINCT ");
for (int i = 0; i < aliases.size(); i++) {
if (i > 0) {
selectSQL.append(" ");
selectSQL.append(distinctCountColumnSeparator);
selectSQL.append(" ");
}
selectSQL.append(aliases.get(i).toString());
}
selectSQL.append(")");
return toSelect(selectSQL, null, from, where, null, null, null,
false, false, 0, Long.MAX_VALUE);
}
}
// since we can't combine distinct cols, we have to perform an outer
// COUNT(*) select using the original select as a subselect in the
// FROM clause
assertSupport(supportsSubselect, "SupportsSubselect");
SQLBuffer subSelect = getSelects(sel, true, false);
SQLBuffer subFrom = from;
from = new SQLBuffer(this);
from.append("(");
from.append(toSelect(subSelect, null, subFrom, where,
sel.getGrouping(), sel.getHaving(), null, sel.isDistinct(),
false, sel.getStartIndex(), sel.getEndIndex(), true, sel));
from.append(")");
if (requiresAliasForSubselect)
from.append(" ").append(Select.FROM_SELECT_ALIAS);
selectSQL.append("COUNT(*)");
return toSelect(selectSQL, null, from, null, null, null, null,
false, false, 0, Long.MAX_VALUE);
}
/**
* Create a DELETE statement for the specified Select. If the
* database does not support the bulk delete statement (such as
* cases where a subselect is required and the database doesn't support
* subselects), this method should return null.
*/
public SQLBuffer toDelete(ClassMapping mapping, Select sel,
Object[] params) {
return toBulkOperation(mapping, sel, null, params, null);
}
public SQLBuffer toUpdate(ClassMapping mapping, Select sel,
JDBCStore store, Object[] params, Map updates) {
return toBulkOperation(mapping, sel, store, params, updates);
}
/**
* Returns the SQL for a bulk operation, either a DELETE or an UPDATE.
*
* @param mapping the mapping against which we are operating
* @param sel the Select that will constitute the WHERE clause
* @param store the current store
* @param updateParams the Map that holds the update parameters; a null
* value indicates that this is a delete operation
* @return the SQLBuffer for the update, or <em>null</em> if it is not
* possible to perform the bulk update
*/
protected SQLBuffer toBulkOperation(ClassMapping mapping, Select sel,
JDBCStore store, Object[] params, Map updateParams) {
SQLBuffer sql = new SQLBuffer(this);
if (updateParams == null) {
if (requiresTargetForDelete) {
sql.append("DELETE ");
SQLBuffer deleteTargets = getDeleteTargets(sel);
sql.append(deleteTargets);
sql.append(" FROM ");
} else {
sql.append("DELETE FROM ");
}
}
else
sql.append("UPDATE ");
sel.addJoinClassConditions();
// if there is only a single table in the select, then we can
// just issue a single DELETE FROM TABLE WHERE <conditions>
// statement; otherwise, since SQL doesn't allow deleting
// from one of a multi-table select, we need to issue a subselect
// like DELETE FROM TABLE WHERE EXISTS
// (SELECT 1 FROM TABLE t0 WHERE t0.ID = TABLE.ID); also, some
// databases do not allow aliases in delete statements, which
// also causes us to use a subselect
Collection<String> selectedTables = getSelectTableAliases(sel);
if (selectedTables.size() == 1 && supportsSubselect
&& allowsAliasInBulkClause) {
SQLBuffer from;
if (sel.getFromSelect() != null)
from = getFromSelect(sel, false);
else
from = getFrom(sel, false);
sql.append(from);
appendUpdates(sel, store, sql, params, updateParams,
allowsAliasInBulkClause);
SQLBuffer where = sel.getWhere();
if (where != null && !where.isEmpty()) {
sql.append(" WHERE ");
sql.append(where);
}
return sql;
}
Table table = mapping.getTable();
String tableName = getFullName(table, false);
// only use a subselect if the where is not empty; otherwise
// an unqualified delete or update will work
if (sel.getWhere() == null || sel.getWhere().isEmpty()) {
sql.append(tableName);
appendUpdates(sel, store, sql, params, updateParams, false);
return sql;
}
// we need to use a subselect if we are to bulk delete where
// the select includes multiple tables; if the database
// doesn't support it, then we need to signal this by returning null
if (!supportsSubselect || !supportsCorrelatedSubselect)
return null;
Column[] pks = mapping.getPrimaryKeyColumns();
sel.clearSelects();
sel.setDistinct(true);
// if we have only a single PK, we can use a non-correlated
// subquery (using an IN statement), which is much faster than
// a correlated subquery (since a correlated subquery needs
// to be executed once for each row in the table)
if (pks.length == 1) {
sel.select(pks[0]);
sql.append(tableName);
appendUpdates(sel, store, sql, params, updateParams, false);
sql.append(" WHERE ").
append(pks[0]).append(" IN (").
append(sel.toSelect(false, null)).append(")");
} else {
sel.clearSelects();
sel.setDistinct(false);
// since the select is using a correlated subquery, we
// only need to select a bogus virtual column
sel.select("1", null);
// add in the joins to the table
Column[] cols = table.getPrimaryKey().getColumns();
SQLBuffer buf = new SQLBuffer(this);
buf.append("(");
for (int i = 0; i < cols.length; i++) {
if (i > 0)
buf.append(" AND ");
// add in "t0.PK = MYTABLE.PK"
buf.append(sel.getColumnAlias(cols[i])).append(" = ").
append(table).append(catalogSeparator).append(cols[i]);
}
buf.append(")");
sel.where(buf, null);
sql.append(tableName);
appendUpdates(sel, store, sql, params, updateParams, false);
sql.append(" WHERE EXISTS (").
append(sel.toSelect(false, null)).append(")");
}
return sql;
}
protected Collection<String> getSelectTableAliases(Select sel) {
return sel.getTableAliases();
}
protected SQLBuffer getDeleteTargets(Select sel) {
SQLBuffer deleteTargets = new SQLBuffer(this);
Collection<String> aliases = sel.getTableAliases();
// Assumes aliases are of the form "TABLENAME t0"
// or "\"TABLE NAME\" t0"
for (Iterator<String> itr = aliases.iterator(); itr.hasNext();) {
String tableAlias = itr.next();
String[] names = Normalizer.splitName(tableAlias, IdentifierUtil.SPACE);
if (names.length > 1) {
if (allowsAliasInBulkClause) {
deleteTargets.append(names[1]);
} else {
deleteTargets.append(toDBName(DBIdentifier.newTable(names[0])));
}
} else {
deleteTargets.append(toDBName(DBIdentifier.newTable(tableAlias)));
}
if (itr.hasNext())
deleteTargets.append(", ");
}
return deleteTargets;
}
protected void appendUpdates(Select sel, JDBCStore store, SQLBuffer sql,
Object[] params, Map updateParams, boolean allowAlias) {
if (updateParams == null || updateParams.size() == 0)
return;
// manually build up the SET clause for the UPDATE statement
sql.append(" SET ");
ExpContext ctx = new ExpContext(store, params,
store.getFetchConfiguration());
// If the updates map contains any version fields, assume that the
// optimistic lock version data is being handled properly by the
// caller. Otherwise, give the version indicator an opportunity to
// add more update clauses as needed.
boolean augmentUpdates = true;
for (Iterator i = updateParams.entrySet().iterator(); i.hasNext();) {
Map.Entry next = (Map.Entry) i.next();
Path path = (Path) next.getKey();
FieldMapping fmd = (FieldMapping) path.last();
if (fmd.isVersion())
augmentUpdates = false;
Val val = (Val) next.getValue();
if (val == null)
val = new Null();
Column col = fmd.getColumns()[0];
if (allowAlias) {
sql.append(sel.getColumnAlias(col));
} else {
sql.append(toDBName(col.getIdentifier()));
}
sql.append(" = ");
ExpState state = val.initialize(sel, ctx, 0);
// JDBC Paths are always PCPaths; PCPath implements Val
ExpState pathState = ((Val) path).initialize(sel, ctx, 0);
calculateValue(val, sel, ctx, state, path, pathState);
// append the value with a null for the Select; i
// indicates that the
int length = val.length(sel, ctx, state);
for (int j = 0; j < length; j++)
val.appendTo((allowAlias) ? sel : null, ctx, state, sql, j);
if (i.hasNext())
sql.append(", ");
}
if (augmentUpdates) {
Path path = (Path) updateParams.keySet().iterator().next();
FieldMapping fm = (FieldMapping) path.last();
ClassMapping meta = fm.getDefiningMapping();
Map<Column,?> updates = meta.getVersion().getBulkUpdateValues();
for (Map.Entry e : updates.entrySet()) {
Column col = (Column) e.getKey();
Object val = e.getValue();
sql.append(", ").append(toDBName(col.getIdentifier())).append(" = ");
// Version update value for Numeric version is encoded in a String
// to make SQL such as version = version+1 while Time stamp version is parameterized
if (val instanceof String) {
sql.append((String)val);
} else {
sql.appendValue(val);
}
}
}
}
/**
* Create SQL to delete the contents of the specified tables.
* The default implementation drops all non-deferred RESTRICT foreign key
* constraints involving the specified tables, issues DELETE statements
* against the tables, and then adds the dropped constraints back in.
* Databases with more optimal ways of deleting the contents of several
* tables should override this method.
*/
public String[] getDeleteTableContentsSQL(Table[] tables,Connection conn) {
Collection<String> sql = new ArrayList<>();
// collect and drop non-deferred physical restrict constraints, and
// collect the DELETE FROM statements
Collection<String> deleteSQL = new ArrayList<>(tables.length);
Collection<ForeignKey> restrictConstraints =
new LinkedHashSet<>();
for (int i = 0; i < tables.length; i++) {
ForeignKey[] fks = tables[i].getForeignKeys();
for (int j = 0; j < fks.length; j++) {
if (!fks[j].isLogical() && !fks[j].isDeferred()
&& fks[j].getDeleteAction() == ForeignKey.ACTION_RESTRICT)
restrictConstraints.add(fks[j]);
}
deleteSQL.add("DELETE FROM " +
toDBName(tables[i].getFullIdentifier()));
}
for(ForeignKey fk : restrictConstraints) {
String[] constraintSQL = getDropForeignKeySQL(fk,conn);
sql.addAll(Arrays.asList(constraintSQL));
}
// add the delete statements after all the constraint mutations
sql.addAll(deleteSQL);
// add the deleted constraints back to the schema
for (ForeignKey fk : restrictConstraints) {
String[] constraintSQL = getAddForeignKeySQL(fk);
sql.addAll(Arrays.asList(constraintSQL));
}
return (String[]) sql.toArray(new String[sql.size()]);
}
/**
* Create a SELECT statement in the proper join syntax for the given
* instance.
*/
public SQLBuffer toSelect(Select sel, boolean forUpdate,
JDBCFetchConfiguration fetch) {
sel.addJoinClassConditions();
boolean update = forUpdate && sel.getFromSelect() == null;
SQLBuffer select = getSelects(sel, false, update);
SQLBuffer ordering = null;
if (!sel.isAggregate() || sel.getGrouping() != null)
ordering = sel.getOrdering();
SQLBuffer from;
if (sel.getFromSelect() != null)
from = getFromSelect(sel, forUpdate);
else
from = getFrom(sel, update);
SQLBuffer where = getWhere(sel, update);
return toSelect(select, fetch, from, where, sel.getGrouping(),
sel.getHaving(), ordering, sel.isDistinct(), forUpdate,
sel.getStartIndex(), sel.getEndIndex(), sel);
}
/**
* Return the portion of the select statement between the FROM keyword
* and the WHERE keyword.
*/
protected SQLBuffer getFrom(Select sel, boolean forUpdate) {
SQLBuffer fromSQL = new SQLBuffer(this);
Collection aliases = sel.getTableAliases();
if (aliases.size() < 2 || sel.getJoinSyntax() != SYNTAX_SQL92) {
for (Iterator itr = aliases.iterator(); itr.hasNext();) {
fromSQL.append(itr.next().toString());
if (forUpdate && tableForUpdateClause != null)
fromSQL.append(" ").append(tableForUpdateClause);
if (itr.hasNext())
fromSQL.append(", ");
}
if (aliases.size() < 2 && sel.getParent() != null) {
// subquery may contain correlated joins
Iterator itr = sel.getJoinIterator();
while (itr.hasNext()) {
Join join = (Join) itr.next();
// append where clause
if (join.isCorrelated() && join.getForeignKey() != null) {
SQLBuffer where = new SQLBuffer(this);
where.append("(").append(toTraditionalJoin(join)).append(")");
sel.where(where.getSQL());
}
}
}
} else {
Iterator itr = sel.getJoinIterator();
boolean first = true;
while (itr.hasNext()) {
Join join = (Join) itr.next();
if (correlatedJoinCondition(join, sel))
continue;
if (join.isCorrelated())
toCorrelatedJoin(sel, join, forUpdate, first);
else
fromSQL.append(toSQL92Join(sel, join, forUpdate,
first));
first = false;
if (itr.hasNext() && join.isCorrelated()) {
if (fromSQL.getSQL().length() > 0)
fromSQL.append(", ");
first = true;
}
}
for (Iterator itr2 = aliases.iterator(); itr2.hasNext();) {
String tableAlias = itr2.next().toString();
if (fromSQL.getSQL().indexOf(tableAlias) == -1) {
if (!first && fromSQL.getSQL().length() > 0)
fromSQL.append(", ");
fromSQL.append(tableAlias);
if (forUpdate && tableForUpdateClause != null)
fromSQL.append(" ").append(tableForUpdateClause);
first = false;
}
}
}
return fromSQL;
}
private boolean correlatedJoinCondition(Join join, Select sel) {
if (!join.isCorrelated())
return false;
Iterator itr = sel.getJoinIterator();
boolean skip = false;
//if table1 in join is in the main query, table2 is in
//subquery, and table2 participates in other joins
//in subquery, the join condition can only be placed in
//the where clause in the subquery
while (itr.hasNext()) {
Join join1 = (Join) itr.next();
if (join == join1 && !join.isForeignKeyInversed()) {
continue;
}
if (join.getIndex2() == join1.getIndex1() ||
join.getIndex2() == join1.getIndex2()) {
skip = true;
if (join.getForeignKey() != null){
SQLBuffer where = new SQLBuffer(this);
where.append("(").append(toTraditionalJoin(join)).append(")");
sel.where(where.getSQL());
}
break;
}
}
return skip;
}
/**
* Return the FROM clause for a select that selects from a tmp table
* created by an inner select.
*/
protected SQLBuffer getFromSelect(Select sel, boolean forUpdate) {
SQLBuffer fromSQL = new SQLBuffer(this);
fromSQL.append("(");
fromSQL.append(toSelect(sel.getFromSelect(), forUpdate, null));
fromSQL.append(")");
if (requiresAliasForSubselect)
fromSQL.append(" ").append(Select.FROM_SELECT_ALIAS);
return fromSQL;
}
/**
* Return the WHERE portion of the select statement, or null if no where
* conditions.
*/
protected SQLBuffer getWhere(Select sel, boolean forUpdate) {
Joins joins = sel.getJoins();
if (sel.getJoinSyntax() == SYNTAX_SQL92
|| joins == null || joins.isEmpty())
return sel.getWhere();
SQLBuffer where = new SQLBuffer(this);
if (sel.getWhere() != null)
where.append(sel.getWhere());
if (joins != null)
sel.append(where, joins);
return where;
}
/**
* Use the given join instance to create SQL joining its tables in
* the traditional style.
*/
public SQLBuffer toTraditionalJoin(Join 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]);
}
// do constant joins
Column[] constCols = fk.getConstantColumns();
for (int i = 0; i < constCols.length; i++, count++) {
if (count > 0)
buf.append(" AND ");
if (inverse)
buf.appendValue(fk.getConstant(constCols[i]), constCols[i]);
else
buf.append(join.getAlias1()).append(".").
append(constCols[i]);
buf.append(" = ");
if (inverse)
buf.append(join.getAlias2()).append(".").
append(constCols[i]);
else
buf.appendValue(fk.getConstant(constCols[i]), constCols[i]);
}
Column[] constColsPK = fk.getConstantPrimaryKeyColumns();
for (int i = 0; i < constColsPK.length; i++, count++) {
if (count > 0)
buf.append(" AND ");
if (inverse)
buf.append(join.getAlias1()).append(".").
append(constColsPK[i]);
else
buf.appendValue(fk.getPrimaryKeyConstant(constColsPK[i]),
constColsPK[i]);
buf.append(" = ");
if (inverse)
buf.appendValue(fk.getPrimaryKeyConstant(constColsPK[i]),
constColsPK[i]);
else
buf.append(join.getAlias2()).append(".").
append(constColsPK[i]);
}
return buf;
}
/**
* Use the given join instance to create SQL joining its tables in
* the SQL92 style.
*/
public SQLBuffer toSQL92Join(Select sel, Join join, boolean forUpdate,
boolean first) {
SQLBuffer buf = new SQLBuffer(this);
if (first) {
buf.append(join.getTable1()).append(" ").
append(join.getAlias1());
if (forUpdate && tableForUpdateClause != null)
buf.append(" ").append(tableForUpdateClause);
}
buf.append(" ");
if (join.getType() == Join.TYPE_OUTER)
buf.append(outerJoinClause);
else if (join.getType() == Join.TYPE_INNER)
buf.append(innerJoinClause);
else // cross
buf.append(crossJoinClause);
buf.append(" ");
buf.append(join.getTable2()).append(" ").append(join.getAlias2());
if (forUpdate && tableForUpdateClause != null)
buf.append(" ").append(tableForUpdateClause);
if (join.getForeignKey() != null)
buf.append(" ON ").append(toTraditionalJoin(join));
else if (requiresConditionForCrossJoin &&
join.getType() == Join.TYPE_CROSS)
buf.append(" ON (1 = 1)");
return buf;
}
private SQLBuffer toCorrelatedJoin(Select sel, Join join, boolean forUpdate,
boolean first) {
if (join.getForeignKey() != null){
SQLBuffer where = new SQLBuffer(this);
where.append("(").append(toTraditionalJoin(join)).append(")");
sel.where(where.getSQL());
}
return null;
}
/**
* Use the given join instance to create SQL joining its tables in
* the database's native syntax. Throws an exception by default.
*/
public SQLBuffer toNativeJoin(Join join) {
throw new UnsupportedException();
}
/**
* Returns if the given foreign key can be eagerly loaded using other joins.
*/
public boolean canOuterJoin(int syntax, ForeignKey fk) {
return syntax != SYNTAX_TRADITIONAL;
}
/**
* Combine the given components into a SELECT statement.
*/
public SQLBuffer toSelect(SQLBuffer selects, JDBCFetchConfiguration fetch,
SQLBuffer from, SQLBuffer where, SQLBuffer group,
SQLBuffer having, SQLBuffer order,
boolean distinct, boolean forUpdate, long start, long end) {
return toOperation(getSelectOperation(fetch), selects, from, where,
group, having, order, distinct, start, end,
getForUpdateClause(fetch, forUpdate, null));
}
/**
* Combine the given components into a SELECT statement.
*/
protected SQLBuffer toSelect(SQLBuffer selects,
JDBCFetchConfiguration fetch,
SQLBuffer from, SQLBuffer where, SQLBuffer group,
SQLBuffer having, SQLBuffer order,
boolean distinct, boolean forUpdate, long start, long end,
boolean subselect, Select sel) {
return toOperation(getSelectOperation(fetch), selects, from, where,
group, having, order, distinct, start, end,
getForUpdateClause(fetch, forUpdate, null), subselect);
}
public SQLBuffer toSelect(SQLBuffer selects, JDBCFetchConfiguration fetch,
SQLBuffer from, SQLBuffer where, SQLBuffer group,
SQLBuffer having, SQLBuffer order,
boolean distinct, boolean forUpdate, long start, long end,
boolean subselect, boolean checkTableForUpdate) {
return toOperation(getSelectOperation(fetch), selects, from, where,
group, having, order, distinct, start, end,
getForUpdateClause(fetch, forUpdate, null), subselect,
checkTableForUpdate);
}
/**
* Combine the given components into a SELECT statement.
*/
protected SQLBuffer toSelect(SQLBuffer selects,
JDBCFetchConfiguration fetch,
SQLBuffer from, SQLBuffer where, SQLBuffer group,
SQLBuffer having, SQLBuffer order,
boolean distinct, boolean forUpdate, long start, long end,
Select sel) {
return toOperation(getSelectOperation(fetch), selects, from, where,
group, having, order, distinct, start, end,
getForUpdateClause(fetch, forUpdate, sel));
}
/**
* Get the update clause for the query based on the
* updateClause and isolationLevel hints
*/
protected String getForUpdateClause(JDBCFetchConfiguration fetch,
boolean isForUpdate, Select sel) {
if (fetch != null && fetch.getIsolation() != -1) {
throw new InvalidStateException(_loc.get(
"isolation-level-config-not-supported", getClass().getName()));
} else if (isForUpdate && !simulateLocking) {
assertSupport(supportsSelectForUpdate, "SupportsSelectForUpdate");
return forUpdateClause;
} else {
return null;
}
}
/**
* Return true if the dictionary uses isolation level to compute the
* returned getForUpdateClause() SQL clause.
*/
public boolean supportsIsolationForUpdate() {
return false;
}
/**
* Return the "SELECT" operation clause, adding any available hints, etc.
*/
public String getSelectOperation(JDBCFetchConfiguration fetch) {
return "SELECT";
}
/**
* Return the SQL for the given selecting operation.
*/
public SQLBuffer toOperation(String op, SQLBuffer selects,
SQLBuffer from, SQLBuffer where, SQLBuffer group, SQLBuffer having,
SQLBuffer order, boolean distinct, long start, long end,
String forUpdateClause) {
return toOperation(op, selects, from, where, group, having, order,
distinct, start, end, forUpdateClause, false);
}
/**
* Return the SQL for the given selecting operation.
*/
public SQLBuffer toOperation(String op, SQLBuffer selects,
SQLBuffer from, SQLBuffer where, SQLBuffer group, SQLBuffer having,
SQLBuffer order, boolean distinct, long start, long end,
String forUpdateClause, boolean subselect) {
return toOperation(op, selects, from, where, group, having, order,
distinct, start, end, forUpdateClause, subselect, false);
}
/**
* Return the SQL for the given selecting operation.
*/
private SQLBuffer toOperation(String op, SQLBuffer selects, SQLBuffer from,
SQLBuffer where, SQLBuffer group, SQLBuffer having, SQLBuffer order,
boolean distinct, long start, long end, String forUpdateClause,
boolean subselect, boolean checkTableForUpdate) {
SQLBuffer buf = new SQLBuffer(this);
buf.append(op);
boolean range = start != 0 || end != Long.MAX_VALUE;
if (range && rangePosition == RANGE_PRE_DISTINCT)
appendSelectRange(buf, start, end, subselect);
if (distinct)
buf.append(" DISTINCT");
if (range && rangePosition == RANGE_POST_DISTINCT)
appendSelectRange(buf, start, end, subselect);
buf.append(" ").append(selects).append(" FROM ").append(from);
if (checkTableForUpdate
&& (StringUtil.isEmpty(forUpdateClause) && !StringUtil.isEmpty(tableForUpdateClause))) {
buf.append(" ").append(tableForUpdateClause);
}
if (where != null && !where.isEmpty())
buf.append(" WHERE ").append(where);
if (group != null && !group.isEmpty())
buf.append(" GROUP BY ").append(group);
if (having != null && !having.isEmpty()) {
assertSupport(supportsHaving, "SupportsHaving");
buf.append(" HAVING ").append(having);
}
if (order != null && !order.isEmpty())
buf.append(" ORDER BY ").append(order);
if (range && rangePosition == RANGE_POST_SELECT)
appendSelectRange(buf, start, end, subselect);
if (forUpdateClause != null)
buf.append(" ").append(forUpdateClause);
if (range && rangePosition == RANGE_POST_LOCK)
appendSelectRange(buf, start, end, subselect);
return buf;
}
/**
* If this dictionary can select ranges,
* use this method to append the range SQL.
*/
protected void appendSelectRange(SQLBuffer buf, long start, long end,
boolean subselect) {
}
/**
* Return the portion of the select statement between the SELECT keyword
* and the FROM keyword.
*/
protected SQLBuffer getSelects(Select sel, boolean distinctIdentifiers,
boolean forUpdate) {
// append the aliases for all the columns
SQLBuffer selectSQL = new SQLBuffer(this);
List aliases;
if (distinctIdentifiers)
aliases = sel.getIdentifierAliases();
else
aliases = sel.getSelectAliases();
Object alias;
for (int i = 0; i < aliases.size(); i++) {
alias = aliases.get(i);
if (alias instanceof String) {
alias = getNamingUtil().convertAlias((String)alias);
}
appendSelect(selectSQL, alias, sel, i);
if (i < aliases.size() - 1)
selectSQL.append(", ");
}
return selectSQL;
}
/**
* Append <code>elem</code> to <code>selectSQL</code>.
* @param selectSQL The SQLBuffer to append to.
* @param elem A {@link SQLBuffer} or a {@link String} to append.
*
* @since 1.1.0
*/
protected void appendSelect(SQLBuffer selectSQL, Object elem, Select sel, int idx) {
if (elem instanceof SQLBuffer)
selectSQL.append((SQLBuffer) elem);
else
selectSQL.append(elem.toString());
}
/**
* Returns true if a "FOR UPDATE" clause can be used for the specified
* Select object.
*/
public boolean supportsLocking(Select sel) {
if (sel.isAggregate())
return false;
if (!supportsSelectForUpdate)
return false;
if (!supportsLockingWithSelectRange && (sel.getStartIndex() != 0
|| sel.getEndIndex() != Long.MAX_VALUE))
return false;
// only inner select is locked
if (sel.getFromSelect() != null)
sel = sel.getFromSelect();
if (!supportsLockingWithDistinctClause && sel.isDistinct())
return false;
if (!supportsLockingWithMultipleTables
&& sel.getTableAliases().size() > 1)
return false;
if (!supportsLockingWithOrderClause && sel.getOrdering() != null)
return false;
if (!supportsLockingWithOuterJoin || !supportsLockingWithInnerJoin) {
for (Iterator itr = sel.getJoinIterator(); itr.hasNext();) {
Join join = (Join) itr.next();
if (!supportsLockingWithOuterJoin
&& join.getType() == Join.TYPE_OUTER)
return false;
if (!supportsLockingWithInnerJoin
&& join.getType() == Join.TYPE_INNER)
return false;
}
}
return true;
}
/**
* Return false if the given select requires a forward-only result set.
*/
public boolean supportsRandomAccessResultSet(Select sel,
boolean forUpdate) {
return !sel.isAggregate();
}
/**
* Assert that the given dictionary flag is true. If it is not true,
* throw an error saying that the given setting needs to return true for
* the current operation to work.
*/
public void assertSupport(boolean feature, String property) {
if (!feature)
throw new UnsupportedException(_loc.get("feature-not-supported",
getClass(), property));
}
////////////////////
// Query functions
////////////////////
/**
* Invoke this database's substring function.
* Numeric parameters are inlined if possible. This is to handle grouping by SUBSTRING -
* most databases do not allow parameter binding in this case.
*
* @param buf the SQL buffer to write the substring invocation to
* @param str a query value representing the target string
* @param start a query value representing the start index
* @param length a query value representing the length of substring, or null for none
*/
public void substring(SQLBuffer buf, FilterValue str, FilterValue start,
FilterValue length) {
buf.append(substringFunctionName).append("(");
str.appendTo(buf);
buf.append(", ");
if (start.getValue() instanceof Number) {
buf.append(Long.toString(toLong(start)));
} else {
start.appendTo(buf);
}
if (length != null) {
buf.append(", ");
if (length.getValue() instanceof Number) {
buf.append(Long.toString(toLong(length)));
} else {
length.appendTo(buf);
}
}
buf.append(")");
}
long toLong(FilterValue litValue) {
return ((Number) litValue.getValue()).longValue();
}
/**
* Invoke this database's indexOf function.
*
* @param buf the SQL buffer to write the indexOf invocation to
* @param str a query value representing the target string
* @param find a query value representing the search string
* @param start a query value representing the start index, or null
* to start at the beginning
*/
public void indexOf(SQLBuffer buf, FilterValue str, FilterValue find,
FilterValue start) {
buf.append("(INSTR((");
if (start != null)
substring(buf, str, start, null);
else
str.appendTo(buf);
buf.append("), (");
find.appendTo(buf);
buf.append("))");
if (start != null) {
buf.append(" - 1 + ");
start.appendTo(buf);
}
buf.append(")");
}
/**
* Append the numeric parts of a mathematical function.
*
* @param buf the SQL buffer to write the math function
* @param op the mathematical operation to perform
* @param lhs the left hand side of the math function
* @param rhs the right hand side of the math function
*/
public void mathFunction(SQLBuffer buf, String op, FilterValue lhs,
FilterValue rhs) {
boolean castlhs = false;
boolean castrhs = false;
Class lc = Filters.wrap(lhs.getType());
Class rc = Filters.wrap(rhs.getType());
int type = 0;
if (requiresCastForMathFunctions && (lc != rc
|| (lhs.isConstant() || rhs.isConstant()))) {
Class c = Filters.promote(lc, rc);
type = getJDBCType(JavaTypes.getTypeCode(c), false);
if (type != Types.VARBINARY && type != Types.BLOB) {
castlhs = (lhs.isConstant() && rhs.isConstant()) || lc != c;
castrhs = (lhs.isConstant() && rhs.isConstant()) || rc != c;
}
}
boolean mod = "MOD".equals(op);
if (mod) {
if (supportsModOperator)
op = "%";
else
buf.append(op);
}
buf.append("(");
if (castlhs)
appendCast(buf, lhs, type);
else
lhs.appendTo(buf);
if (mod && !supportsModOperator)
buf.append(", ");
else
buf.append(" ").append(op).append(" ");
if (castrhs)
appendCast(buf, rhs, type);
else
rhs.appendTo(buf);
buf.append(")");
}
/**
* Append a comparison.
*
* @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 comparison(SQLBuffer buf, String op, FilterValue lhs,
FilterValue rhs) {
boolean lhsxml = lhs.getXPath() != null;
boolean rhsxml = rhs.getXPath() != null;
if (lhsxml || rhsxml) {
appendXmlComparison(buf, op, lhs, rhs, lhsxml, rhsxml);
return;
}
boolean castlhs = false;
boolean castrhs = false;
Class lc = Filters.wrap(lhs.getType());
Class rc = Filters.wrap(rhs.getType());
// special case of comparison of two boolean constants
// because some databases do not like false = false or false = true
// but all databases understand 1 = 0 or 0 <> 1 etc.
if (lc == rc && lc == Boolean.class && lhs.isConstant() && rhs.isConstant()) {
String lvalue = Boolean.TRUE.equals(lhs.getValue()) ? "1" : "0";
String rvalue = Boolean.TRUE.equals(rhs.getValue()) ? "1" : "0";
buf.append(lvalue).append(op).append(rvalue);
return;
}
int type = 0;
if (requiresCastForComparisons && (lc != rc
|| (lhs.isConstant() && rhs.isConstant()))) {
Class c = Filters.promote(lc, rc);
type = getJDBCType(JavaTypes.getTypeCode(c), false);
if (type != Types.VARBINARY && type != Types.BLOB) {
castlhs = (lhs.isConstant() && rhs.isConstant()) || lc != c;
castrhs = (lhs.isConstant() && rhs.isConstant()) || rc != c;
castlhs = castlhs && lhs.requiresCast();
castrhs = castrhs && rhs.requiresCast();
}
}
if (castlhs)
appendCast(buf, lhs, type);
else
lhs.appendTo(buf);
buf.append(" ").append(op).append(" ");
if (castrhs)
appendCast(buf, rhs, type);
else
rhs.appendTo(buf);
}
/**
* If this dictionary supports XML type,
* use this method to append xml predicate.
*/
public void appendXmlComparison(SQLBuffer buf, String op, FilterValue lhs,
FilterValue rhs, boolean lhsxml, boolean rhsxml) {
assertSupport(supportsXMLColumn, "SupportsXMLColumn");
}
/**
* Append SQL for the given numeric value to the buffer, casting as needed.
*/
protected void appendNumericCast(SQLBuffer buf, FilterValue val) {
if (val.isConstant())
appendCast(buf, val, Types.NUMERIC);
else
val.appendTo(buf);
}
/**
* 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, Object 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 = "";
buf.append(pre);
if (val instanceof FilterValue)
((FilterValue) val).appendTo(buf);
else if (val instanceof SQLBuffer)
buf.append(((SQLBuffer) val));
else
buf.append(val.toString());
buf.append(mid);
buf.append(getTypeName(type));
appendLength(buf, type);
buf.append(post);
}
protected void appendLength(SQLBuffer buf, int type) {
}
/**
* add CAST for a function operator where operand is a param
* @param func function name
* @param val
* @return updated func
*/
public String addCastAsType(String func, Val val) {
return null;
}
///////////
// DDL SQL
///////////
/**
* Increment the reference count of any table components that this
* dictionary adds that are not used by mappings. Does nothing by default.
*/
public void refSchemaComponents(Table table) {
}
/**
* Returns the name of the column using database specific delimiters.
*/
public DBIdentifier getColumnIdentifier(Column column) {
if (column == null) {
return DBIdentifier.NULL;
}
return column.getIdentifier();
}
public String getColumnDBName(Column column) {
return toDBName(getColumnIdentifier(column));
}
/**
* Returns the full name of the table, including the schema (delimited
* by {@link #catalogSeparator}).
*/
public DBIdentifier getFullIdentifier(Table table, boolean logical) {
if (!useSchemaName || DBIdentifier.isNull(table.getSchemaIdentifier()))
return table.getIdentifier();
return table.getFullIdentifier();
}
public String getFullName(Table table, boolean logical) {
if (!useSchemaName || DBIdentifier.isNull(table.getSchemaIdentifier()))
return toDBName(table.getIdentifier());
return toDBName(table.getFullIdentifier());
}
/**
* Returns the full name of the index, including the schema (delimited
* by the result of {@link #catalogSeparator}).
*/
public String getFullName(Index index) {
if (!useSchemaName || DBIdentifier.isNull(index.getSchemaIdentifier()))
return toDBName(index.getIdentifier());
return toDBName(index.getFullIdentifier());
}
/**
* Returns the full name of the sequence, including the schema (delimited
* by the result of {@link #catalogSeparator}).
*/
public String getFullName(Sequence seq) {
if (!useSchemaName || DBIdentifier.isNull(seq.getSchemaIdentifier()))
return toDBName(seq.getIdentifier());
return toDBName(seq.getFullIdentifier());
}
/**
* Return the subset of the words in reservedWordSet that cannot be used as
* valid column names for the current DB. If the column name is invalid the
* getValidColumnName method of the DB dictionary should be invoked to make
* it valid.
*
* @see #getValidColumnName(org.apache.openjpa.jdbc.identifier.DBIdentifier, org.apache.openjpa.jdbc.schema.Table)
*/
public final Set<String> getInvalidColumnWordSet() {
return invalidColumnWordSet;
}
/**
* Make any necessary changes to the given table name to make it valid for
* the current DB.
* @deprecated
*/
@Deprecated
public String getValidTableName(String name, Schema schema) {
return getValidTableName(DBIdentifier.newTable(name), schema).getName();
}
/**
* Make any necessary changes to the given table name to make it valid for
* the current DB.
*/
public DBIdentifier getValidTableName(DBIdentifier name, Schema schema) {
return namingUtil.getValidTableIdentifier(name, schema, maxTableNameLength);
}
/**
* Make any necessary changes to the given sequence name to make it valid
* for the current DB.
* @deprecated
*/
@Deprecated
public String getValidSequenceName(String name, Schema schema) {
return getValidSequenceName(DBIdentifier.newSequence(name), schema).getName();
}
/**
* Make any necessary changes to the given sequence name to make it valid
* for the current DB.
*/
public DBIdentifier getValidSequenceName(DBIdentifier name, Schema schema) {
return namingUtil.getValidSequenceIdentifier(name, schema, maxTableNameLength);
}
/**
* Make any necessary changes to the given column name to make it valid
* for the current DB. The column name will be made unique for the
* specified table.
* @deprecated
*/
@Deprecated
public String getValidColumnName(String name, Table table) {
return getValidColumnName(DBIdentifier.newColumn(name), table, true).getName();
}
/**
* Make any necessary changes to the given column name to make it valid
* for the current DB. The column name will be made unique for the
* specified table.
*/
public DBIdentifier getValidColumnName(DBIdentifier name, Table table) {
return getValidColumnName(name, table, true);
}
/**
* Make any necessary changes to the given column name to make it valid
* for the current DB. If checkForUniqueness is true, the column name will
* be made unique for the specified table.
* @deprecated
*/
@Deprecated
public String getValidColumnName(String name, Table table,
boolean checkForUniqueness) {
return getValidColumnName(DBIdentifier.newColumn(name), table, checkForUniqueness).toString();
}
/**
* Make any necessary changes to the given column name to make it valid
* for the current DB. If checkForUniqueness is true, the column name will
* be made unique for the specified table.
*/
public DBIdentifier getValidColumnName(DBIdentifier name, Table table,
boolean checkForUniqueness) {
return getNamingUtil().getValidColumnIdentifier(name, table, maxColumnNameLength,
checkForUniqueness);
}
/**
* Make any necessary changes to the given primary key name to make it
* valid for the current DB.
*/
public String getValidPrimaryKeyName(String name, Table table) {
while (name.startsWith("_"))
name = name.substring(1);
return makeNameValid("P_" + name, table.getSchema().getSchemaGroup(),
maxConstraintNameLength, NAME_ANY);
}
/**
* Make any necessary changes to the given foreign key name to make it
* valid for the current DB.
* @deprecated
*/
@Deprecated
public String getValidForeignKeyName(String name, Table table,
Table toTable) {
return getValidForeignKeyName(DBIdentifier.newForeignKey(name), table,
toTable).getName();
}
/**
* Make any necessary changes to the given foreign key name to make it
* valid for the current DB.
*/
public DBIdentifier getValidForeignKeyName(DBIdentifier name, Table table,
Table toTable) {
return namingUtil.getValidForeignKeyIdentifier(name, table, toTable, maxConstraintNameLength);
}
/**
* Make any necessary changes to the given index name to make it valid
* for the current DB.
* @deprecated
*/
@Deprecated
public String getValidIndexName(String name, Table table) {
return getValidIndexName(DBIdentifier.newIndex(name), table).getName();
}
/**
* Make any necessary changes to the given index name to make it valid
* for the current DB.
*/
public DBIdentifier getValidIndexName(DBIdentifier name, Table table) {
return getNamingUtil().getValidIndexIdentifier(name, table, maxIndexNameLength);
}
/**
* Make any necessary changes to the given unique constraint name to make
* it valid for the current DB.
* @deprecated
*/
@Deprecated
public String getValidUniqueName(String name, Table table) {
return getValidUniqueName(DBIdentifier.newConstraint(name), table).getName();
}
/**
* Make any necessary changes to the given unique constraint name to make
* it valid for the current DB.
*/
public DBIdentifier getValidUniqueName(DBIdentifier name, Table table) {
return namingUtil.getValidUniqueIdentifier(name, table, maxConstraintNameLength);
}
/**
* Shorten the specified name to the specified target name. This will
* be done by first stripping out the vowels, and then removing
* characters from the middle of the word until it reaches the target
* length.
*/
public static String shorten(String name, int targetLength) {
if (name == null || name.length() <= targetLength)
return name;
StringBuilder nm = new StringBuilder(name);
while (nm.length() > targetLength) {
if (!stripVowel(nm)) {
// cut out the middle char
nm.replace(nm.length() / 2, (nm.length() / 2) + 1, "");
}
}
return nm.toString();
}
/**
* Remove vowels from the specified StringBuilder.
*
* @return true if any vowels have been removed
*/
private static boolean stripVowel(StringBuilder name) {
if (name == null || name.length() == 0)
return false;
char[] vowels = { 'A', 'E', 'I', 'O', 'U', };
for (int i = 0; i < vowels.length; i++) {
int index = name.toString().toUpperCase(Locale.ENGLISH).indexOf(vowels[i]);
if (index != -1) {
name.replace(index, index + 1, "");
return true;
}
}
return false;
}
/**
* Shortens the given name to the given maximum length, then checks that
* it is not a reserved word. If it is reserved, appends a "0". If
* the name conflicts with an existing schema component, the last
* character is replace with '0', then '1', etc.
* Note that the given max len may be 0 if the database metadata is
* incomplete.
* @deprecated
*/
@Deprecated
protected String makeNameValid(String name, NameSet set, int maxLen,
int nameType) {
return makeNameValid(name, set, maxLen, nameType, true);
}
/**
* Shortens the given name to the given maximum length, then checks that
* it is not a reserved word. If it is reserved, appends a "0". If
* the name conflicts with an existing schema component, the last
* character is replace with '0', then '1', etc.
* Note that the given max len may be 0 if the database metadata is
* incomplete.
*/
protected DBIdentifier makeNameValid(DBIdentifier name, NameSet set, int maxLen,
int nameType) {
return makeNameValid(name, set, maxLen, nameType, true);
}
/**
* Shortens the given name to the given maximum length, then checks that
* it is not a reserved word. If it is reserved, appends a "0". If
* the name conflicts with an existing schema component and uniqueness
* checking is enabled, the last character is replace with '0', then
* '1', etc.
* Note that the given max len may be 0 if the database metadata is
* incomplete.
*
* Note: If the name is delimited, make sure the ending delimiter is
* not stripped off.
*/
protected String makeNameValid(String name, NameSet set, int maxLen,
int nameType, boolean checkForUniqueness) {
return namingUtil.makeNameValid(name, set,
maxLen, nameType, checkForUniqueness).toString();
}
/**
* Shortens the given name to the given maximum length, then checks that
* it is not a reserved word. If it is reserved, appends a "0". If
* the name conflicts with an existing schema component and uniqueness
* checking is enabled, the last character is replace with '0', then
* '1', etc.
* Note that the given max len may be 0 if the database metadata is
* incomplete.
*
* Note: If the name is delimited, make sure the ending delimiter is
* not stripped off.
*/
protected DBIdentifier makeNameValid(DBIdentifier name, NameSet set, int maxLen,
int nameType, boolean checkForUniqueness) {
return namingUtil.makeIdentifierValid(name, set,
maxLen, checkForUniqueness);
}
/**
* Return a series of SQL statements to create the given table, complete
* with columns. Indexes and constraints will be created separately.
*/
public String[] getCreateTableSQL(Table table, SchemaGroup group) {
return getCreateTableSQL(table);
}
/**
* Return a series of SQL statements to create the given table, complete
* with columns. Indexes and constraints will be created separately.
*/
public String[] getCreateTableSQL(Table table) {
StringBuilder buf = new StringBuilder();
String tableName =
checkNameLength(getFullIdentifier(table, false), maxTableNameLength, "long-table-name",
tableLengthIncludesSchema);
buf.append("CREATE TABLE ").append(tableName);
if (supportsComments && table.hasComment()) {
buf.append(" ");
comment(buf, table.getComment());
buf.append("\n (");
} else {
buf.append(" (");
}
// do this before getting the columns so we know how to handle
// the last comma
StringBuilder endBuf = new StringBuilder();
PrimaryKey pk = table.getPrimaryKey();
String pkStr;
if (pk != null) {
pkStr = getPrimaryKeyConstraintSQL(pk);
if (pkStr != null)
endBuf.append(pkStr);
}
Unique[] unqs = table.getUniques();
String unqStr;
for (int i = 0; i < unqs.length; i++) {
unqStr = getUniqueConstraintSQL(unqs[i]);
if (unqStr != null) {
if (endBuf.length() > 0)
endBuf.append(", ");
endBuf.append(unqStr);
}
}
Column[] cols = table.getColumns();
for (int i = 0; i < cols.length; i++) {
buf.append(getDeclareColumnSQL(cols[i], false));
if (i < cols.length - 1 || endBuf.length() > 0)
buf.append(", ");
if (supportsComments && cols[i].hasComment()) {
comment(buf, cols[i].getComment());
buf.append("\n ");
}
}
buf.append(endBuf.toString());
buf.append(")");
return new String[]{ buf.toString() };
}
public int getBatchFetchSize(int batchFetchSize) {
return batchFetchSize;
}
protected StringBuilder comment(StringBuilder buf, String comment) {
return buf.append("-- ").append(comment);
}
/**
* Return a series of SQL statements to drop the given table. Indexes
* will be dropped separately. Returns
* <code>DROP TABLE &lt;table name&gt;</code> by default.
*/
public String[] getDropTableSQL(Table table) {
String drop = MessageFormat.format(dropTableSQL, new Object[]{
getFullName(table, false) });
return new String[]{ drop };
}
/**
* Return a series of SQL statements to create the given sequence. Returns
* <code>CREATE SEQUENCE &lt;sequence name&gt;[ START WITH &lt;start&gt;]
* [ INCREMENT BY &lt;increment&gt;]</code> by default.
*/
public String[] getCreateSequenceSQL(Sequence seq) {
return commonCreateAlterSequenceSQL(seq, true);
}
public String getAlterSequenceSQL(Sequence seq) {
return commonCreateAlterSequenceSQL(seq, false)[0];
}
private String[] commonCreateAlterSequenceSQL(Sequence seq, boolean create) {
if (nextSequenceQuery == null)
return null;
//We need a place to detect if the user is setting the 'useNativeSequenceCache' property.
//While in previous releases this property had meaning, it is no longer useful
//given the code added via OPENJPA-1327. 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 useNativeSequenceCache 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 useNativeSequenceCache.
if (!useNativeSequenceCache && logNativeSequenceCacheWarning){
log.warn(_loc.get("sequence-cache-warning"));
logNativeSequenceCacheWarning=false;
}
StringBuilder buf = new StringBuilder();
buf.append(create ? "CREATE" : "ALTER").append(" SEQUENCE ");
//Strip off the schema and verify the sequence name is within the legal length, NOT
//the schema name + sequence name.
checkNameLength(toDBName(seq.getFullIdentifier().getUnqualifiedName()),
maxTableNameLength, "long-seq-name");
//Now use the full sequence name (schema + sequence name).
String seqName = getFullName(seq);
buf.append(seqName);
if (create && seq.getInitialValue() != 0)
buf.append(" START WITH ").append(seq.getInitialValue());
if ((seq.getIncrement() >= 1) || (seq.getAllocate() >= 1))
buf.append(" INCREMENT BY ").append(seq.getIncrement() * seq.getAllocate());
return new String[]{ buf.toString() };
}
/**
* Return a series of SQL statements to drop the given sequence. Returns
* <code>DROP SEQUENCE &lt;sequence name&gt;</code> by default.
*/
public String[] getDropSequenceSQL(Sequence seq) {
return new String[]{ "DROP SEQUENCE " + getFullName(seq) };
}
/**
* Return a series of SQL statements to create the given index. Returns
* <code>CREATE [UNIQUE] INDEX &lt;index name&gt; ON &lt;table name&gt;
* (&lt;col list&gt;)</code> by default.
*/
public String[] getCreateIndexSQL(Index index) {
StringBuilder buf = new StringBuilder();
buf.append("CREATE ");
if (index.isUnique())
buf.append("UNIQUE ");
DBIdentifier fullIdxName = index.getIdentifier();
DBIdentifier unQualifiedName = fullIdxName.getUnqualifiedName();
checkNameLength(toDBName(unQualifiedName), maxIndexNameLength,
"long-index-name");
String indexName = toDBName(fullIdxName);
buf.append("INDEX ").append(indexName);
buf.append(" ON ").append(getFullName(index.getTable(), false));
buf.append(" (").append(namingUtil.appendColumns(index.getColumns())).
append(")");
return new String[]{ buf.toString() };
}
/**
* Return a series of SQL statements to drop the given index. Returns
* <code>DROP INDEX &lt;index name&gt;</code> by default.
*/
public String[] getDropIndexSQL(Index index) {
return new String[]{ "DROP INDEX " + getFullName(index) };
}
/**
* Return a series of SQL statements to add the given column to
* its table. Return an empty array if operation not supported. Returns
* <code>ALTER TABLE &lt;table name&gt; ADD (&lt;col dec&gt;)</code>
* by default.
*/
public String[] getAddColumnSQL(Column column) {
if (!supportsAlterTableWithAddColumn)
return new String[0];
String dec = getDeclareColumnSQL(column, true);
if (dec == null)
return new String[0];
return new String[]{ "ALTER TABLE "
+ getFullName(column.getTable(), false) + " ADD " + dec };
}
/**
* Return a series of SQL statements to drop the given column from
* its table. Return an empty array if operation not supported. Returns
* <code>ALTER TABLE &lt;table name&gt; DROP COLUMN &lt;col name&gt;</code>
* by default.
*/
public String[] getDropColumnSQL(Column column) {
if (!supportsAlterTableWithDropColumn)
return new String[0];
return new String[]{ "ALTER TABLE "
+ getFullName(column.getTable(), false)
+ " DROP COLUMN " + column };
}
/**
* Return a series of SQL statements to add the given primary key to
* its table. Return an empty array if operation not supported.
* Returns <code>ALTER TABLE &lt;table name&gt; ADD
* &lt;pk cons sql &gt;</code> by default.
*/
public String[] getAddPrimaryKeySQL(PrimaryKey pk) {
String pksql = getPrimaryKeyConstraintSQL(pk);
if (pksql == null)
return new String[0];
return new String[]{ "ALTER TABLE "
+ getFullName(pk.getTable(), false) + " ADD " + pksql };
}
/**
* Return a series of SQL statements to drop the given primary key from
* its table. Return an empty array if operation not supported.
* Returns <code>ALTER TABLE &lt;table name&gt; DROP CONSTRAINT
* &lt;pk name&gt;</code> by default.
*/
public String[] getDropPrimaryKeySQL(PrimaryKey pk) {
if (DBIdentifier.isNull(pk.getIdentifier()))
return new String[0];
return new String[]{ "ALTER TABLE "
+ getFullName(pk.getTable(), false)
+ " DROP CONSTRAINT " + toDBName(pk.getIdentifier()) };
}
/**
* Return a series of SQL statements to add the given foreign key to
* its table. Return an empty array if operation not supported.
* Returns <code>ALTER TABLE &lt;table name&gt; ADD
* &lt;fk cons sql &gt;</code> by default.
*/
public String[] getAddForeignKeySQL(ForeignKey fk) {
String fkSQL = getForeignKeyConstraintSQL(fk);
if (fkSQL == null)
return new String[0];
return new String[]{ "ALTER TABLE "
+ getFullName(fk.getTable(), false) + " ADD " + fkSQL };
}
/**
* Return a series of SQL statements to drop the given foreign key from
* its table. Return an empty array if operation not supported.
* Returns <code>ALTER TABLE &lt;table name&gt; DROP CONSTRAINT
* &lt;fk name&gt;</code> by default.
*/
public String[] getDropForeignKeySQL(ForeignKey fk, Connection conn) {
if (DBIdentifier.isNull(fk.getIdentifier())) {
String[] retVal;
DBIdentifier fkName = fk.loadIdentifierFromDB(this,conn);
retVal = (fkName == null || fkName.getName() == null) ? new String[0] :
new String[]{ "ALTER TABLE "
+ getFullName(fk.getTable(), false)
+ " DROP CONSTRAINT " + toDBName(fkName) };
return retVal;
}
return new String[]{ "ALTER TABLE "
+ getFullName(fk.getTable(), false)
+ " DROP CONSTRAINT " + toDBName(fk.getIdentifier()) };
}
/**
* Return the declaration SQL for the given column. This method is used
* for each column from within {@link #getCreateTableSQL} and
* {@link #getAddColumnSQL}.
*/
protected String getDeclareColumnSQL(Column col, boolean alter) {
StringBuilder buf = new StringBuilder();
String columnName = checkNameLength(toDBName(col.getIdentifier()), maxColumnNameLength,
"long-column-name");
buf.append(columnName).append(" ");
buf.append(getTypeName(col));
// can't add constraints to a column we're adding after table
// creation, cause some data might already be inserted
if (!alter) {
if (col.getDefaultString() != null && !col.isAutoAssigned())
buf.append(" DEFAULT ").append(col.getDefaultString());
if (col.isNotNull() || (!supportsNullUniqueColumn && col.hasConstraint(Unique.class)))
buf.append(" NOT NULL");
}
if (col.isAutoAssigned()) {
if (!supportsAutoAssign)
log.warn(_loc.get("invalid-autoassign", platform, col));
else if (autoAssignClause != null)
buf.append(" ").append(autoAssignClause);
}
return buf.toString();
}
/**
* Return the declaration SQL for the given primary key. This method is
* used from within {@link #getCreateTableSQL} and
* {@link #getAddPrimaryKeySQL}. Returns
* <code>CONSTRAINT &lt;pk name&gt; PRIMARY KEY (&lt;col list&gt;)</code>
* by default.
*/
protected String getPrimaryKeyConstraintSQL(PrimaryKey pk) {
// if we have disabled the creation of primary keys, abort here
if (!createPrimaryKeys)
return null;
String name = toDBName(pk.getIdentifier());
if (name != null && reservedWordSet.contains(name.toUpperCase(Locale.ENGLISH)))
name = null;
StringBuilder buf = new StringBuilder();
if (name != null && CONS_NAME_BEFORE.equals(constraintNameMode))
buf.append("CONSTRAINT ").append(name).append(" ");
buf.append("PRIMARY KEY ");
if (name != null && CONS_NAME_MID.equals(constraintNameMode))
buf.append(name).append(" ");
buf.append("(").append(namingUtil.appendColumns(pk.getColumns())).
append(")");
if (name != null && CONS_NAME_AFTER.equals(constraintNameMode))
buf.append(" CONSTRAINT ").append(name);
return buf.toString();
}
/**
* Return the declaration SQL for the given foreign key, or null if it is
* not supported. This method is used from within
* {@link #getCreateTableSQL} and {@link #getAddForeignKeySQL}. Returns
* <code>CONSTRAINT &lt;cons name&gt; FOREIGN KEY (&lt;col list&gt;)
* REFERENCES &lt;foreign table&gt; (&lt;col list&gt;)
* [ON DELETE &lt;action&gt;] [ON UPDATE &lt;action&gt;]</code> by default.
*/
protected String getForeignKeyConstraintSQL(ForeignKey fk) {
if (!supportsForeignKeys)
return null;
if (fk.getColumns().length > 0 && !supportsForeignKeysComposite)
return null;
if (fk.getDeleteAction() == ForeignKey.ACTION_NONE)
return null;
if (fk.isDeferred() && !supportsDeferredForeignKeyConstraints())
return null;
if (!supportsDeleteAction(fk.getDeleteAction())
|| !supportsUpdateAction(fk.getUpdateAction()))
return null;
Column[] locals = fk.getColumns();
Column[] foreigns = fk.getPrimaryKeyColumns();
int delActionId = fk.getDeleteAction();
if (delActionId == ForeignKey.ACTION_NULL) {
for (int i = 0; i < locals.length; i++) {
if (locals[i].isNotNull())
delActionId = ForeignKey.ACTION_NONE;
}
}
String delAction = getActionName(delActionId);
String upAction = getActionName(fk.getUpdateAction());
StringBuilder buf = new StringBuilder();
if (!DBIdentifier.isNull(fk.getIdentifier())
&& CONS_NAME_BEFORE.equals(constraintNameMode))
buf.append("CONSTRAINT ").append(toDBName(fk.getIdentifier())).append(" ");
buf.append("FOREIGN KEY ");
if (!DBIdentifier.isNull(fk.getIdentifier()) && CONS_NAME_MID.equals(constraintNameMode))
buf.append(toDBName(fk.getIdentifier())).append(" ");
buf.append("(").append(namingUtil.appendColumns(locals)).append(")");
buf.append(" REFERENCES ");
buf.append(getFullName(foreigns[0].getTable(), false));
buf.append(" (").append(namingUtil.appendColumns(foreigns)).append(")");
if (delAction != null)
buf.append(" ON DELETE ").append(delAction);
if (upAction != null)
buf.append(" ON UPDATE ").append(upAction);
if (fk.isDeferred())
buf.append(" INITIALLY DEFERRED");
if (supportsDeferredForeignKeyConstraints())
buf.append(" DEFERRABLE");
if (!DBIdentifier.isNull(fk.getIdentifier())
&& CONS_NAME_AFTER.equals(constraintNameMode))
buf.append(" CONSTRAINT ").append(toDBName(fk.getIdentifier()));
return buf.toString();
}
/**
* Whether or not this dictionary supports deferred foreign key constraints.
* This implementation returns {@link #supportsUniqueConstraints}.
*
* @since 1.1.0
*/
protected boolean supportsDeferredForeignKeyConstraints() {
return supportsDeferredConstraints;
}
/**
* Return the name of the given foreign key action.
*/
private String getActionName(int action) {
switch (action) {
case ForeignKey.ACTION_CASCADE:
return "CASCADE";
case ForeignKey.ACTION_NULL:
return "SET NULL";
case ForeignKey.ACTION_DEFAULT:
return "SET DEFAULT";
default:
return null;
}
}
/**
* Whether this database supports the given foreign key delete action.
*/
public boolean supportsDeleteAction(int action) {
if (action == ForeignKey.ACTION_NONE)
return true;
if (!supportsForeignKeys)
return false;
switch (action) {
case ForeignKey.ACTION_RESTRICT:
return supportsRestrictDeleteAction;
case ForeignKey.ACTION_CASCADE:
return supportsCascadeDeleteAction;
case ForeignKey.ACTION_NULL:
return supportsNullDeleteAction;
case ForeignKey.ACTION_DEFAULT:
return supportsDefaultDeleteAction;
default:
return false;
}
}
/**
* Whether this database supports the given foreign key update action.
*/
public boolean supportsUpdateAction(int action) {
if (action == ForeignKey.ACTION_NONE)
return true;
if (!supportsForeignKeys)
return false;
switch (action) {
case ForeignKey.ACTION_RESTRICT:
return supportsRestrictUpdateAction;
case ForeignKey.ACTION_CASCADE:
return supportsCascadeUpdateAction;
case ForeignKey.ACTION_NULL:
return supportsNullUpdateAction;
case ForeignKey.ACTION_DEFAULT:
return supportsDefaultUpdateAction;
default:
return false;
}
}
/**
* Return the declaration SQL for the given unique constraint. This
* method is used from within {@link #getCreateTableSQL}.
* Returns <code>CONSTRAINT &lt;name&gt; UNIQUE (&lt;col list&gt;)</code>
* by default.
*/
protected String getUniqueConstraintSQL(Unique unq) {
if (!supportsUniqueConstraints
|| (unq.isDeferred() && !supportsDeferredUniqueConstraints()))
return null;
StringBuilder buf = new StringBuilder();
if (!DBIdentifier.isNull(unq.getIdentifier())
&& CONS_NAME_BEFORE.equals(constraintNameMode))
buf.append("CONSTRAINT ").append(checkNameLength(toDBName(unq.getIdentifier()),
maxConstraintNameLength, "long-constraint-name")).append(" ");
buf.append("UNIQUE ");
if (!DBIdentifier.isNull(unq.getIdentifier()) && CONS_NAME_MID.equals(constraintNameMode))
buf.append(toDBName(unq.getIdentifier())).append(" ");
buf.append("(").append(namingUtil.appendColumns(unq.getColumns())).
append(")");
if (unq.isDeferred())
buf.append(" INITIALLY DEFERRED");
if (supportsDeferredUniqueConstraints())
buf.append(" DEFERRABLE");
if (!DBIdentifier.isNull(unq.getIdentifier())
&& CONS_NAME_AFTER.equals(constraintNameMode))
buf.append(" CONSTRAINT ").append(toDBName(unq.getIdentifier()));
return buf.toString();
}
/**
* Whether or not this dictionary supports deferred unique constraints.
* This implementation returns {@link #supportsUniqueConstraints}.
*
* @since 1.1.0
*/
protected boolean supportsDeferredUniqueConstraints() {
return supportsDeferredConstraints;
}
/////////////////////
// Database metadata
/////////////////////
/**
* This method is used to filter system tables from database metadata.
* Return true if the given table name represents a system table that
* should not appear in the schema definition. By default, returns
* true only if the given table is in the internal list of system tables,
* or if the given schema is in the list of system schemas and is not
* the target schema.
*
* @param name the table name
* @param schema the table schema; may be null
* @param targetSchema if true, then the given schema was listed by
* the user as one of his schemas
* @deprecated
*/
@Deprecated
public boolean isSystemTable(String name, String schema,
boolean targetSchema) {
return isSystemTable(DBIdentifier.newTable(name),
DBIdentifier.newSchema(schema), targetSchema);
}
/**
* This method is used to filter system tables from database metadata.
* Return true if the given table name represents a system table that
* should not appear in the schema definition. By default, returns
* true only if the given table is in the internal list of system tables,
* or if the given schema is in the list of system schemas and is not
* the target schema.
*
* @param name the table name
* @param schema the table schema; may be null
* @param targetSchema if true, then the given schema was listed by
* the user as one of his schemas
*/
public boolean isSystemTable(DBIdentifier name, DBIdentifier schema,
boolean targetSchema) {
DBIdentifier sName = DBIdentifier.toUpper(name);
if (systemTableSet.contains(sName.getName()))
return true;
DBIdentifier schName = DBIdentifier.toUpper(schema);
return !targetSchema && schema != null
&& systemSchemaSet.contains(schName.getName());
}
/**
* This method is used to filter system indexes from database metadata.
* Return true if the given index name represents a system index that
* should not appear in the schema definition. Returns false by default.
*
* @param name the index name
* @param table the index table
* @deprecated
*/
@Deprecated
public boolean isSystemIndex(String name, Table table) {
return false;
}
/**
* This method is used to filter system indexes from database metadata.
* Return true if the given index name represents a system index that
* should not appear in the schema definition. Returns false by default.
*
* @param name the index name
* @param table the index table
*/
public boolean isSystemIndex(DBIdentifier name, Table table) {
return false;
}
/**
* This method is used to filter system sequences from database metadata.
* Return true if the given sequence represents a system sequence that
* should not appear in the schema definition. Returns true if system
* schema by default.
*
* @param name the table name
* @param schema the table schema; may be null
* @param targetSchema if true, then the given schema was listed by
* the user as one of his schemas
* @deprecated
*/
@Deprecated
public boolean isSystemSequence(String name, String schema,
boolean targetSchema) {
return isSystemSequence(DBIdentifier.newSequence(name),
DBIdentifier.newSchema(schema), targetSchema);
}
/**
* This method is used to filter system sequences from database metadata.
* Return true if the given sequence represents a system sequence that
* should not appear in the schema definition. Returns true if system
* schema by default.
*
* @param name the table name
* @param schema the table schema; may be null
* @param targetSchema if true, then the given schema was listed by
* the user as one of his schemas
*/
public boolean isSystemSequence(DBIdentifier name, DBIdentifier schema,
boolean targetSchema) {
return !targetSchema && !DBIdentifier.isNull(schema)
&& systemSchemaSet.contains(DBIdentifier.toUpper(schema).getName());
}
/**
* This method is used to filter system sequences from database metadata.
* Return true if the given sequence represents a system sequence that
* should not appear in the schema definition. Returns true if system
* schema by default.
*
* @param name the table name
* @param schema the table schema; may be null
* @param targetSchema if true, then the given schema was listed by
* the user as one of his schemas
* @param conn connection to the database
*/
public boolean isSystemSequence(DBIdentifier name, DBIdentifier schema,
boolean targetSchema, Connection conn) {
return isSystemSequence(name, schema, targetSchema);
}
/**
* Reflect on the schema to find tables matching the given name pattern.
* @deprecated not used by openjpa internally anymore
* @see #getTables(DatabaseMetaData, DBIdentifier, DBIdentifier, DBIdentifier, Connection)
*/
@Deprecated
public Table[] getTables(DatabaseMetaData meta, String catalog,
String schemaName, String tableName, Connection conn)
throws SQLException {
return getTables(meta, DBIdentifier.newCatalog(catalog), DBIdentifier.newSchema(schemaName),
DBIdentifier.newTable(tableName), conn);
}
/**
* Reflect on the schema to find tables matching the given name pattern.
*/
public Table[] getTables(DatabaseMetaData meta, DBIdentifier sqlCatalog,
DBIdentifier sqlSchemaName, DBIdentifier sqlTableName, Connection conn)
throws SQLException {
String schemaName = DBIdentifier.isNull(sqlSchemaName) ? null : sqlSchemaName.getName();
if (!supportsSchemaForGetTables)
schemaName = null;
else {
schemaName = getSchemaNameForMetadata(sqlSchemaName);
}
String[] types = StringUtil.split(tableTypes, ",", 0);
for (int i = 0; i < types.length; i++)
types[i] = types[i].trim();
beforeMetadataOperation(conn);
ResultSet tables = null;
try {
tables = meta.getTables(getCatalogNameForMetadata(sqlCatalog),
schemaName, getTableNameForMetadata(sqlTableName), types);
List<Table> tableList = new ArrayList<>();
while (tables != null && tables.next()) {
tableList.add(newTable(tables));
}
return tableList.toArray(new Table[tableList.size()]);
} finally {
if (tables != null) {
try {
tables.close();
}
catch (Exception e) {
}
}
}
}
/**
* Create a new table from the information in the schema metadata.
*/
protected Table newTable(ResultSet tableMeta)
throws SQLException {
Table t = new Table();
t.setIdentifier(fromDBName(tableMeta.getString("TABLE_NAME"), DBIdentifierType.TABLE));
return t;
}
/**
* Reflect on the schema to find sequences matching the given name pattern.
* Returns an empty array by default, as there is no standard way to
* retrieve a list of sequences.
* @deprecated
*/
@Deprecated
public Sequence[] getSequences(DatabaseMetaData meta, String catalog,
String schemaName, String sequenceName, Connection conn)
throws SQLException {
return getSequences(meta, DBIdentifier.newCatalog(catalog), DBIdentifier.newSchema(schemaName),
DBIdentifier.newSequence(sequenceName), conn);
}
public Sequence[] getSequences(DatabaseMetaData meta, DBIdentifier catalog,
DBIdentifier schemaName, DBIdentifier sequenceName, Connection conn)
throws SQLException {
String str = getSequencesSQL(schemaName, sequenceName);
if (str == null)
return new Sequence[0];
PreparedStatement stmnt = prepareStatement(conn, str);
ResultSet rs = null;
try {
int idx = 1;
if (!DBIdentifier.isNull(schemaName))
stmnt.setString(idx++, DBIdentifier.toUpper(schemaName).getName());
if (!DBIdentifier.isNull(sequenceName))
stmnt.setString(idx++, sequenceName.getName());
setQueryTimeout(stmnt, conf.getQueryTimeout());
rs = executeQuery(conn, stmnt, str);
return getSequence(rs);
} finally {
if (rs != null)
try {
rs.close();
} catch (SQLException se) {
}
if (stmnt != null)
try {
stmnt.close();
} catch (SQLException se) {
}
}
}
/**
* Create a new sequence from the information in the schema metadata.
*/
protected Sequence newSequence(ResultSet sequenceMeta)
throws SQLException {
Sequence seq = new Sequence();
seq.setSchemaIdentifier(fromDBName(StringUtil.stripEnd(sequenceMeta.getString("SEQUENCE_SCHEMA"), null),
DBIdentifierType.SCHEMA));
seq.setIdentifier(fromDBName(StringUtil.stripEnd(sequenceMeta.getString("SEQUENCE_NAME"), null),
DBIdentifierType.SEQUENCE));
return seq;
}
/**
* Return the SQL needed to select the list of sequences.
* @deprecated
*/
@Deprecated
protected String getSequencesSQL(String schemaName, String sequenceName) {
return null;
}
protected String getSequencesSQL(DBIdentifier schemaName, DBIdentifier sequenceName) {
return null;
}
/**
* Reflect on the schema to find columns matching the given table and
* column patterns.
* @deprecated
*/
@Deprecated
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);
}
/**
* Reflect on the schema to find columns matching the given table and
* column patterns.
*/
public Column[] getColumns(DatabaseMetaData meta, DBIdentifier catalog,
DBIdentifier schemaName, DBIdentifier tableName, DBIdentifier columnName, Connection conn)
throws SQLException {
if (DBIdentifier.isNull(tableName) && !supportsNullTableForGetColumns)
return null;
String sqlSchemaName = null;
if (!DBIdentifier.isNull(schemaName)) {
sqlSchemaName = schemaName.getName();
}
if (!supportsSchemaForGetColumns)
sqlSchemaName = null;
else
sqlSchemaName = getSchemaNameForMetadata(schemaName);
beforeMetadataOperation(conn);
ResultSet cols = null;
try {
cols = meta.getColumns(getCatalogNameForMetadata(catalog),
sqlSchemaName, getTableNameForMetadata(tableName),
getColumnNameForMetadata(columnName));
List columnList = new ArrayList();
while (cols != null && cols.next())
columnList.add(newColumn(cols));
return (Column[]) columnList.toArray
(new Column[columnList.size()]);
} finally {
if (cols != null)
try {
cols.close();
} catch (Exception e) {
}
}
}
/**
* Create a new column from the information in the schema metadata.
*/
protected Column newColumn(ResultSet colMeta)
throws SQLException {
Column c = new Column();
c.setSchemaIdentifier(fromDBName(colMeta.getString("TABLE_SCHEM"), DBIdentifierType.SCHEMA));
c.setTableIdentifier(fromDBName(colMeta.getString("TABLE_NAME"), DBIdentifierType.TABLE));
c.setIdentifier(fromDBName(colMeta.getString("COLUMN_NAME"), DBIdentifierType.COLUMN));
c.setType(colMeta.getInt("DATA_TYPE"));
c.setTypeIdentifier(fromDBName(colMeta.getString("TYPE_NAME"), DBIdentifierType.COLUMN_DEFINITION));
c.setSize(colMeta.getInt("COLUMN_SIZE"));
c.setDecimalDigits(colMeta.getInt("DECIMAL_DIGITS"));
c.setNotNull(colMeta.getInt("NULLABLE")
== DatabaseMetaData.columnNoNulls);
String def = colMeta.getString("COLUMN_DEF");
if (!StringUtil.isEmpty(def) && !"null".equalsIgnoreCase(def))
c.setDefaultString(def);
return c;
}
/**
* Reflect on the schema to find primary keys for the given table pattern.
* @deprecated
*/
@Deprecated
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);
}
/**
* Reflect on the schema to find primary keys for the given table pattern.
*/
public PrimaryKey[] getPrimaryKeys(DatabaseMetaData meta,
DBIdentifier catalog, DBIdentifier schemaName, DBIdentifier tableName, Connection conn)
throws SQLException {
if (useGetBestRowIdentifierForPrimaryKeys)
return getPrimaryKeysFromBestRowIdentifier(meta, catalog,
schemaName, tableName, conn);
return getPrimaryKeysFromGetPrimaryKeys(meta, catalog,
schemaName, tableName, conn);
}
/**
* Reflect on the schema to find primary keys for the given table pattern.
* @deprecated
*/
@Deprecated
protected PrimaryKey[] getPrimaryKeysFromGetPrimaryKeys
(DatabaseMetaData meta, String catalog, String schemaName,
String tableName, Connection conn)
throws SQLException {
return getPrimaryKeysFromGetPrimaryKeys(meta, DBIdentifier.newCatalog(catalog),
DBIdentifier.newSchema(schemaName), DBIdentifier.newTable(tableName), conn);
}
/**
* Reflect on the schema to find primary keys for the given table pattern.
*/
protected PrimaryKey[] getPrimaryKeysFromGetPrimaryKeys
(DatabaseMetaData meta, DBIdentifier catalog, DBIdentifier schemaName,
DBIdentifier tableName, Connection conn)
throws SQLException {
if (tableName == null && !supportsNullTableForGetPrimaryKeys)
return null;
beforeMetadataOperation(conn);
ResultSet pks = null;
try {
pks = meta.getPrimaryKeys(getCatalogNameForMetadata(catalog),
getSchemaNameForMetadata(schemaName),
getTableNameForMetadata(tableName));
List pkList = new ArrayList();
while (pks != null && pks.next())
pkList.add(newPrimaryKey(pks));
return (PrimaryKey[]) pkList.toArray
(new PrimaryKey[pkList.size()]);
} finally {
if (pks != null)
try {
pks.close();
} catch (Exception e) {
}
}
}
/**
* Create a new primary key from the information in the schema metadata.
*/
protected PrimaryKey newPrimaryKey(ResultSet pkMeta)
throws SQLException {
PrimaryKey pk = new PrimaryKey();
pk.setSchemaIdentifier(fromDBName(pkMeta.getString("TABLE_SCHEM"), DBIdentifierType.SCHEMA));
pk.setTableIdentifier(fromDBName(pkMeta.getString("TABLE_NAME"), DBIdentifierType.TABLE));
pk.setColumnIdentifier(fromDBName(pkMeta.getString("COLUMN_NAME"), DBIdentifierType.COLUMN));
pk.setIdentifier(fromDBName(pkMeta.getString("PK_NAME"), DBIdentifierType.CONSTRAINT));
return pk;
}
/**
* Reflect on the schema to find primary keys for the given table pattern.
* @deprecated
*/
@Deprecated
protected PrimaryKey[] getPrimaryKeysFromBestRowIdentifier
(DatabaseMetaData meta, String catalog, String schemaName,
String tableName, Connection conn) throws SQLException {
return getPrimaryKeysFromBestRowIdentifier(meta, DBIdentifier.newCatalog(catalog),
DBIdentifier.newSchema(schemaName), DBIdentifier.newTable(tableName), conn);
}
/**
* Reflect on the schema to find primary keys for the given table pattern.
*/
protected PrimaryKey[] getPrimaryKeysFromBestRowIdentifier
(DatabaseMetaData meta, DBIdentifier catalog, DBIdentifier schemaName,
DBIdentifier tableName, Connection conn)
throws SQLException {
if (tableName == null)
return null;
beforeMetadataOperation(conn);
ResultSet pks = null;
try {
pks = meta.getBestRowIdentifier(toDBName(catalog), toDBName(schemaName),
toDBName(tableName), 0, false);
List pkList = new ArrayList();
while (pks != null && pks.next()) {
PrimaryKey pk = new PrimaryKey();
pk.setSchemaIdentifier(schemaName);
pk.setTableIdentifier(tableName);
pk.setColumnIdentifier(fromDBName(pks.getString("COLUMN_NAME"), DBIdentifierType.COLUMN));
pkList.add(pk);
}
return (PrimaryKey[]) pkList.toArray
(new PrimaryKey[pkList.size()]);
} finally {
if (pks != null)
try {
pks.close();
} catch (Exception e) {
}
}
}
/**
* Reflect on the schema to find indexes matching the given table pattern.
* @deprecated
*/
@Deprecated
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);
}
/**
* Reflect on the schema to find indexes matching the given table pattern.
*/
public Index[] getIndexInfo(DatabaseMetaData meta, DBIdentifier catalog,
DBIdentifier schemaName, DBIdentifier tableName, boolean unique,
boolean approx, Connection conn)
throws SQLException {
if (tableName == null && !supportsNullTableForGetIndexInfo)
return null;
beforeMetadataOperation(conn);
ResultSet indexes = null;
try {
indexes = meta.getIndexInfo(getCatalogNameForMetadata(catalog),
getSchemaNameForMetadata(schemaName),
getTableNameForMetadata(tableName), unique, approx);
List indexList = new ArrayList();
while (indexes != null && indexes.next())
indexList.add(newIndex(indexes));
return (Index[]) indexList.toArray(new Index[indexList.size()]);
} finally {
if (indexes != null)
try {
indexes.close();
} catch (Exception e) {
}
}
}
/**
* Create a new index from the information in the schema metadata.
*/
protected Index newIndex(ResultSet idxMeta)
throws SQLException {
Index idx = new Index();
idx.setSchemaIdentifier(fromDBName(idxMeta.getString("TABLE_SCHEM"), DBIdentifierType.SCHEMA));
idx.setTableIdentifier(fromDBName(idxMeta.getString("TABLE_NAME"), DBIdentifierType.TABLE));
idx.setColumnIdentifier(fromDBName(idxMeta.getString("COLUMN_NAME"), DBIdentifierType.COLUMN));
idx.setIdentifier(fromDBName(idxMeta.getString("INDEX_NAME"), DBIdentifierType.INDEX));
idx.setUnique(!idxMeta.getBoolean("NON_UNIQUE"));
return idx;
}
/**
* Reflect on the schema to return foreign keys imported by the given
* table pattern.
* @deprecated
*/
@Deprecated
public ForeignKey[] getImportedKeys(DatabaseMetaData meta, String catalog,
String schemaName, String tableName, Connection conn)
throws SQLException {
return getImportedKeys(meta, catalog, schemaName, tableName, conn, true);
}
/**
* Reflect on the schema to return foreign keys imported by the given
* table pattern.
*/
public ForeignKey[] getImportedKeys(DatabaseMetaData meta, DBIdentifier catalog,
DBIdentifier schemaName, DBIdentifier tableName, Connection conn)
throws SQLException {
return getImportedKeys(meta, catalog, schemaName, tableName, conn, true);
}
/**
* Reflect on the schema to return full foreign keys imported by the given
* table pattern.
* @deprecated
*/
@Deprecated
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);
}
/**
* Reflect on the schema to return full foreign keys imported by the given
* table pattern.
*/
public ForeignKey[] getImportedKeys(DatabaseMetaData meta, DBIdentifier catalog,
DBIdentifier schemaName, DBIdentifier tableName, Connection conn, boolean partialKeys)
throws SQLException {
if (!supportsForeignKeys)
return null;
if (tableName == null && !supportsNullTableForGetImportedKeys)
return null;
beforeMetadataOperation(conn);
ResultSet keys = null;
try {
keys = meta.getImportedKeys(getCatalogNameForMetadata(catalog),
getSchemaNameForMetadata(schemaName),
getTableNameForMetadata(tableName));
List<ForeignKey> importedKeyList = new ArrayList<>();
Map<FKMapKey, ForeignKey> fkMap = new HashMap<>();
while (keys != null && keys.next()) {
ForeignKey nfk = newForeignKey(keys);
if (!partialKeys) {
ForeignKey fk = combineForeignKey(fkMap, nfk);
// If the key returned != new key, fk col was combined
// with existing fk.
if (fk != nfk) {
continue;
}
}
importedKeyList.add(nfk);
}
return (ForeignKey[]) importedKeyList.toArray
(new ForeignKey[importedKeyList.size()]);
} finally {
if (keys != null) {
try {
keys.close();
} catch (Exception e) {
}
}
}
}
/**
* Combines partial foreign keys into singular key
*/
protected ForeignKey combineForeignKey(Map<FKMapKey, ForeignKey> fkMap,
ForeignKey fk) {
FKMapKey fkmk = new FKMapKey(fk);
ForeignKey baseKey = fkMap.get(fkmk);
// Found the FK, add the additional column
if (baseKey != null) {
baseKey.addColumn(fk);
return baseKey;
}
// fkey is new
fkMap.put(fkmk, fk);
return fk;
}
/**
* Create a new foreign key from the information in the schema metadata.
*/
protected ForeignKey newForeignKey(ResultSet fkMeta)
throws SQLException {
ForeignKey fk = new ForeignKey();
fk.setSchemaIdentifier(fromDBName(fkMeta.getString("FKTABLE_SCHEM"), DBIdentifierType.SCHEMA));
fk.setTableIdentifier(fromDBName(fkMeta.getString("FKTABLE_NAME"), DBIdentifierType.TABLE));
fk.setColumnIdentifier(fromDBName(fkMeta.getString("FKCOLUMN_NAME"), DBIdentifierType.COLUMN));
fk.setIdentifier(fromDBName(fkMeta.getString("FK_NAME"), DBIdentifierType.FOREIGN_KEY));
fk.setPrimaryKeySchemaIdentifier(fromDBName(fkMeta.getString("PKTABLE_SCHEM"), DBIdentifierType.SCHEMA));
fk.setPrimaryKeyTableIdentifier(fromDBName(fkMeta.getString("PKTABLE_NAME"), DBIdentifierType.TABLE));
fk.setPrimaryKeyColumnIdentifier(fromDBName(fkMeta.getString("PKCOLUMN_NAME"), DBIdentifierType.COLUMN));
fk.setKeySequence(fkMeta.getShort("KEY_SEQ"));
fk.setDeferred(fkMeta.getShort("DEFERRABILITY")
== DatabaseMetaData.importedKeyInitiallyDeferred);
int del = fkMeta.getShort("DELETE_RULE");
switch (del) {
case DatabaseMetaData.importedKeySetNull:
fk.setDeleteAction(ForeignKey.ACTION_NULL);
break;
case DatabaseMetaData.importedKeySetDefault:
fk.setDeleteAction(ForeignKey.ACTION_DEFAULT);
break;
case DatabaseMetaData.importedKeyCascade:
fk.setDeleteAction(ForeignKey.ACTION_CASCADE);
break;
default:
fk.setDeleteAction(ForeignKey.ACTION_RESTRICT);
break;
}
return fk;
}
/**
* Returns the table name that will be used for obtaining information
* from {@link DatabaseMetaData}.
*/
protected String getTableNameForMetadata(String tableName) {
return convertSchemaCase(DBIdentifier.newTable(tableName));
}
/**
* Returns the table name that will be used for obtaining information
* from {@link DatabaseMetaData}.
*/
protected String getTableNameForMetadata(DBIdentifier tableName) {
if (tableName == null) {
return null;
}
return convertSchemaCase(tableName.getUnqualifiedName());
}
/**
* Returns the schema name that will be used for obtaining information
* from {@link DatabaseMetaData}.
*/
protected String getSchemaNameForMetadata(String schemaName) {
if (schemaName == null)
schemaName = conf.getSchema();
return convertSchemaCase(DBIdentifier.newSchema(schemaName));
}
/**
* Returns the schema name that will be used for obtaining information
* from {@link DatabaseMetaData}.
*/
protected String getSchemaNameForMetadata(DBIdentifier schemaName) {
if (DBIdentifier.isNull(schemaName))
schemaName = DBIdentifier.newSchema(conf.getSchema());
return convertSchemaCase(schemaName);
}
/**
* Returns the catalog name that will be used for obtaining information
* from {@link DatabaseMetaData}.
*/
protected String getCatalogNameForMetadata(String catalogName) {
return convertSchemaCase(DBIdentifier.newCatalog(catalogName));
}
/**
* Returns the catalog name that will be used for obtaining information
* from {@link DatabaseMetaData}.
*/
protected String getCatalogNameForMetadata(DBIdentifier catalogName) {
return convertSchemaCase(catalogName);
}
/**
* Returns the column name that will be used for obtaining information
* from {@link DatabaseMetaData}.
*/
protected String getColumnNameForMetadata(String columnName) {
return convertSchemaCase(DBIdentifier.newColumn(columnName));
}
/**
* Returns the column name that will be used for obtaining information
* from {@link DatabaseMetaData}.
*/
protected String getColumnNameForMetadata(DBIdentifier columnName) {
return convertSchemaCase(columnName);
}
/**
* Convert the specified schema name to a name that the database will
* be able to understand.
*/
public String convertSchemaCase(String objectName) {
return convertSchemaCase(DBIdentifier.newIdentifier(objectName, DBIdentifierType.DEFAULT, false));
}
/**
* Convert the specified schema name to a name that the database will
* be able to understand.
*/
public String convertSchemaCase(DBIdentifier objectName) {
return toDBName(namingUtil.convertSchemaCase(objectName), false);
}
/**
* Return DB specific schemaCase
*/
@Override
public String getSchemaCase(){
return schemaCase;
}
/**
* Prepared the connection for metadata operations.
*/
private void beforeMetadataOperation(Connection c) {
if (requiresAutoCommitForMetaData) {
try {
c.rollback();
} catch (SQLException sqle) {
}
try {
if (!c.getAutoCommit())
c.setAutoCommit(true);
} catch (SQLException sqle) {
}
}
}
/////////////////////////////
// Sequences and Auto-Assign
/////////////////////////////
/**
* Return the last generated value for the given column.
* Throws an exception by default if {@link #lastGeneratedKeyQuery} is null.
*/
public Object getGeneratedKey(Column col, Connection conn)
throws SQLException {
if (lastGeneratedKeyQuery == null)
throw new StoreException(_loc.get("no-auto-assign"));
// replace things like "SELECT MAX({0}) FROM {1}"
String query = lastGeneratedKeyQuery;
if (query.indexOf('{') != -1) // only if the token is in the string
{
query = getGenKeySeqName(query, col);
}
PreparedStatement stmnt = prepareStatement(conn, query);
ResultSet rs = null;
try {
setQueryTimeout(stmnt, conf.getQueryTimeout());
rs = executeQuery(conn, stmnt, query);
return getKey(rs, col);
} finally {
if (rs != null)
try { rs.close(); } catch (SQLException se) {}
if (stmnt != null)
try { stmnt.close(); } catch (SQLException se) {}
}
}
protected String getGenKeySeqName(String query, Column col) {
return MessageFormat.format(query, new Object[]{
toDBName(col.getIdentifier()), getFullName(col.getTable(), false),
getGeneratedKeySequenceName(col),
});
}
/**
* Return the sequence name used by databases for the given autoassigned
* column. This is only used by databases that require an explicit name
* to be used for auto-assign support.
*/
protected String getGeneratedKeySequenceName(Column col) {
return toDBName(namingUtil.getGeneratedKeySequenceName(col, maxAutoAssignNameLength));
}
protected Map<String, StoredProcedure> _procs = new TreeMap<>();
/**
* Gets the metadata of the stored procedure by the given name either from the cached version or
* by enquiring the database.
* @param meta the database meta data
* @param catalog the catalog name or null
* @param schema the schema name or null
* @param procedure the procedure name
* @return metadata about the named procedure or null
* @throws SQLException when metadata query goes wrong
*/
public StoredProcedure getStoredProcedure(DatabaseMetaData meta, DBIdentifier catalog, DBIdentifier schema,
String procedure) throws SQLException {
if (_procs.containsKey(procedure)) {
return _procs.get(procedure);
}
ResultSet rs = meta.getProcedureColumns(
getCatalogNameForMetadata(catalog),
getSchemaNameForMetadata(schema),
procedure,
null);
StoredProcedure sp = null;
if (rs.next()) {
sp = new StoredProcedure(rs);
} else {
ResultSet rsExist = meta.getProcedures(
getCatalogNameForMetadata(catalog),
getSchemaNameForMetadata(schema),
procedure);
if (rsExist.next()) {
sp = new StoredProcedure((String) null);
sp.setCatalog(catalog);
sp.setSchema(schema);
sp.setName(procedure);
}
}
_procs.put(procedure, sp);
return sp;
}
///////////////////////////////
// Configurable implementation
///////////////////////////////
@Override
public void setConfiguration(Configuration conf) {
this.conf = (JDBCConfiguration) conf;
this.log = this.conf.getLog(JDBCConfiguration.LOG_JDBC);
// Create the naming utility
namingUtil = this.conf.getIdentifierUtilInstance();
namingUtil.setIdentifierConfiguration(this);
configureNamingRules();
// warn about unsupported dicts
if (log.isWarnEnabled() && !isSupported())
log.warn(_loc.get("dict-not-supported", getClass()));
}
private boolean isSupported() {
// if this is a custom dict, traverse to whatever openjpa dict it
// extends
Class c = getClass();
while (!c.getName().startsWith("org.apache.openjpa."))
c = c.getSuperclass();
// the generic dbdictionary is not considered a supported dict; all
// other concrete dictionaries are
if (c == DBDictionary.class)
return false;
return true;
}
@Override
public void startConfiguration() {
}
@Override
public void endConfiguration() {
// initialize the set of reserved SQL92 words from resource
InputStream in = DBDictionary.class.getResourceAsStream
("sql-keywords.rsrc");
try {
String keywords = new BufferedReader(new InputStreamReader(in)).
readLine();
reservedWordSet.addAll(Arrays.asList(StringUtil.split
(keywords, ",", 0)));
} catch (IOException ioe) {
throw new GeneralException(ioe);
} finally {
try { in.close(); } catch (IOException e) {}
}
// add additional reserved words set by user
if (reservedWords != null)
reservedWordSet.addAll(Arrays.asList(StringUtil.split(reservedWords.toUpperCase(Locale.ENGLISH), ",", 0)));
// add system schemas set by user
if (systemSchemas != null)
systemSchemaSet.addAll(Arrays.asList(StringUtil.split(systemSchemas.toUpperCase(Locale.ENGLISH), ",", 0)));
// add system tables set by user
if (systemTables != null)
systemTableSet.addAll(Arrays.asList(StringUtil.split(systemTables.toUpperCase(Locale.ENGLISH), ",", 0)));
// add fixed size type names set by the user
if (fixedSizeTypeNames != null)
fixedSizeTypeNameSet.addAll(Arrays.asList(StringUtil.split(fixedSizeTypeNames.toUpperCase(Locale.ENGLISH), ",", 0)));
// if user has unset sequence sql, null it out so we know sequences
// aren't supported
nextSequenceQuery = StringUtil.trimToNull(nextSequenceQuery);
if (selectWords != null)
selectWordSet.addAll(Arrays.asList(StringUtil.split(selectWords.toUpperCase(Locale.ENGLISH), ",", 0)));
// initialize the error codes
SQLErrorCodeReader codeReader = new SQLErrorCodeReader();
String rsrc = "sql-error-state-codes.xml";
// We'll allow sub-classes to override the stream for custom err codes
// @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UI_INHERITANCE_UNSAFE_GETRESOURCE")
InputStream stream = getClass().getResourceAsStream(rsrc);
String dictionaryClassName = getClass().getName();
if (stream == null) { // User supplied dictionary but no error codes xml
// use default
stream = DBDictionary.class.getResourceAsStream(rsrc);
dictionaryClassName = getClass().getSuperclass().getName();
}
codeReader.parse(stream, dictionaryClassName, this);
}
public void addErrorCode(int errorType, String errorCode) {
if (errorCode == null || errorCode.trim().length() == 0)
return;
Set<String> codes = sqlStateCodes.get(errorType);
if (codes == null) {
codes = new HashSet<>();
codes.add(errorCode.trim());
sqlStateCodes.put(errorType, codes);
} else {
codes.add(errorCode.trim());
}
}
/**
* FIXME - OPENJPA-957 - lockTimeout is a server-side function and
* shouldn't be using client-side setQueryTimeout for lock timeouts.
*
* This method is to provide override for non-JDBC or JDBC-like
* implementation of setting query and lock timeouts.
*
* @param stmnt
* @param fetch - optional lock and query timeouts in milliseconds
* @param forUpdate - true if we should also try setting a lock timeout
* @throws SQLException
*/
public void setTimeouts(PreparedStatement stmnt,
JDBCFetchConfiguration fetch, boolean forUpdate) throws SQLException {
if (supportsQueryTimeout) {
int timeout = fetch.getQueryTimeout();
if (forUpdate) {
// if this is a locking select and the lock timeout is greater
// than the configured query timeout, use the lock timeout
timeout = Math.max(fetch.getQueryTimeout(),
fetch.getLockTimeout());
}
setQueryTimeout(stmnt, timeout);
}
}
/**
* FIXME - OPENJPA-957 - lockTimeout is a server-side function and
* shouldn't be using client-side setQueryTimeout for lock timeouts.
*
* This method is to provide override for non-JDBC or JDBC-like
* implementation of setting query and lock timeouts.
*
* @param stmnt
* @param conf - optional lock and query timeouts in milliseconds
* @param forUpdate - true if we should also try setting a lock timeout
* @throws SQLException
*/
public void setTimeouts(PreparedStatement stmnt, JDBCConfiguration conf,
boolean forUpdate) throws SQLException {
if (supportsQueryTimeout) {
int timeout = conf.getQueryTimeout();
if (forUpdate) {
// if this is a locking select and the lock timeout is greater
// than the configured query timeout, use the lock timeout
timeout = Math.max(conf.getQueryTimeout(),
conf.getLockTimeout());
}
setQueryTimeout(stmnt, timeout);
}
}
/**
* Provides the default validation handling of setting a query timeout.
* @param stmnt
* @param timeout in milliseconds
* @throws SQLException
*/
public void setQueryTimeout(PreparedStatement stmnt, int timeout)
throws SQLException {
if (supportsQueryTimeout) {
if (timeout == -1) {
// special OpenJPA allowed case denoting no timeout
timeout = 0;
} else if (timeout < 0) {
if (log.isWarnEnabled())
log.warn(_loc.get("invalid-timeout", Integer.valueOf(timeout)));
return;
} else if (timeout > 0 && timeout < 1000) {
// round up to 1 sec
timeout = 1000;
if (log.isWarnEnabled())
log.warn(_loc.get("millis-query-timeout"));
}
setStatementQueryTimeout(stmnt, timeout);
}
}
/**
* Allow subclasses to provide DB unique override implementations of
* setting query timeouts, while preserving the default timeout logic
* in the public setQueryTimeout method.
* @param stmnt
* @param timeout in milliseconds
* @throws SQLException
*/
protected void setStatementQueryTimeout(PreparedStatement stmnt,
int timeout) throws SQLException {
// JDBC uses seconds, so we'll do a simple round-down conversion here
stmnt.setQueryTimeout(timeout / 1000);
}
//////////////////////////////////////
// ConnectionDecorator implementation
//////////////////////////////////////
/**
* Decorate the given connection if needed. Some databases require special
* handling for JDBC bugs. This implementation issues any
* {@link #initializationSQL} that has been set for the dictionary but
* does not decorate the connection.
*/
@Override
public Connection decorate(Connection conn)
throws SQLException {
if (!connected)
connectedConfiguration(conn);
if (!StringUtil.isEmpty(initializationSQL)) {
PreparedStatement stmnt = null;
try {
stmnt = conn.prepareStatement(initializationSQL);
stmnt.execute();
} catch (Exception e) {
if (log.isTraceEnabled())
log.trace(e.toString(), e);
} finally {
if (stmnt != null)
try {
stmnt.close();
} catch (SQLException se) {
}
}
}
return conn;
}
/**
* Implementation of the
* {@link LoggingConnectionDecorator.SQLWarningHandler} interface
* that allows customization of the actions to perform when a
* {@link SQLWarning} occurs at any point on a {@link Connection},
* {@link Statement}, or {@link ResultSet}. This method may
* be used determine those warnings the application wants to
* consider critical failures, and throw the warning in those
* cases. By default, this method does nothing.
*
* @see LoggingConnectionDecorator#setWarningAction
* @see LoggingConnectionDecorator#setWarningHandler
*/
@Override
public void handleWarning(SQLWarning warning)
throws SQLException {
}
/**
* Return a new exception that wraps <code>causes</code>.
* However, the details of exactly what type of exception is returned can
* be determined by the implementation. This may take into account
* DB-specific exception information in <code>causes</code>.
*/
public OpenJPAException newStoreException(String msg, SQLException[] causes, Object failed) {
if (causes != null && causes.length > 0) {
OpenJPAException ret = narrow(msg, causes[0], failed);
ret.setFailedObject(failed).setNestedThrowables(causes);
return ret;
}
return new StoreException(msg).setFailedObject(failed).
setNestedThrowables(causes);
}
/**
* Gets the category of StoreException by matching the given SQLException's
* error state code to the list of error codes supplied by the dictionary.
*
* @return a StoreException of {@link ExceptionInfo#GENERAL general} category
* if the given SQL Exception can not be further categorized.
*
* @see #matchErrorState(Map, SQLException)
*/
OpenJPAException narrow(String msg, SQLException ex, Object failed) {
int errorType = matchErrorState(sqlStateCodes, ex);
StoreException storeEx;
switch (errorType) {
case StoreException.LOCK:
storeEx = new LockException(failed);
break;
case StoreException.OBJECT_EXISTS:
storeEx = new ObjectExistsException(msg);
break;
case StoreException.OBJECT_NOT_FOUND:
storeEx = new ObjectNotFoundException(failed);
break;
case StoreException.OPTIMISTIC:
storeEx = new OptimisticException(failed);
break;
case StoreException.REFERENTIAL_INTEGRITY:
storeEx = new ReferentialIntegrityException(msg);
break;
case StoreException.QUERY:
storeEx = new QueryException(msg);
break;
default:
storeEx = new StoreException(msg);
}
storeEx.setFatal(isFatalException(errorType, ex));
return storeEx;
}
/**
* Determine the more appropriate type of store exception by matching the SQL Error State of the
* the given SQLException to the given Error States categorized by error types.
* Dictionary subclass can override this method and extract
* SQLException data to figure out if the exception is recoverable.
*
* @param errorStates classification of SQL error states by their specific nature. The keys of the
* map represent one of the constants defined in {@link StoreException}. The value corresponding to
* a key represent the set of SQL Error States representing specific category of database error.
* This supplied map is sourced from <code>sql-error-state-codes.xml</xml> and filtered the
* error states for the current database.
*
* @param ex original SQL Exception as raised by the database driver.
*
* @return A constant indicating the category of error as defined in {@link StoreException}.
*/
protected int matchErrorState(Map<Integer,Set<String>> errorStates, SQLException ex) {
String errorState = ex.getSQLState();
for (Map.Entry<Integer,Set<String>> states : errorStates.entrySet()) {
if (states.getValue().contains(errorState))
return states.getKey();
}
return ExceptionInfo.GENERAL;
}
/**
* Determine if the given SQL Exception is fatal or recoverable (such as a timeout).
* This implementation always returns true (i.e. all exceptions are fatal).
* The current dictionary implementation can overwrite this method to mark certain
* exception conditions as recoverable error.
* @param subtype A constant indicating the category of error as defined in {@link StoreException}.
* @param ex original SQL Exception as raised by the database driver.
*
* @return false if the error is fatal.
*/
public boolean isFatalException(int subtype, SQLException ex) {
return true;
}
/**
* Closes the specified {@link DataSource} and releases any
* resources associated with it.
*
* @param dataSource the DataSource to close
*/
public void closeDataSource(DataSource dataSource) {
DataSourceFactory.closeDataSource(dataSource);
}
/**
* Used by some mappings to represent data that has already been
* serialized so that we don't have to serialize multiple times.
*/
public static class SerializedData {
public final byte[] bytes;
public SerializedData(byte[] bytes) {
this.bytes = bytes;
}
}
/**
* Return version column name
* @param column
* @param tableAlias : this is needed for platform specific version column
*/
public String getVersionColumn(Column column, String tableAlias) {
return getVersionColumn(column, DBIdentifier.newTable(tableAlias)).toString();
}
public DBIdentifier getVersionColumn(Column column, DBIdentifier tableAlias) {
return column.getIdentifier();
}
public void insertBlobForStreamingLoad(Row row, Column col,
JDBCStore store, Object ob, Select sel) throws SQLException {
if (ob != null) {
row.setBinaryStream(col,
new ByteArrayInputStream(new byte[0]), 0);
} else {
row.setNull(col);
}
}
public void insertClobForStreamingLoad(Row row, Column col, Object ob)
throws SQLException {
if (ob != null) {
row.setCharacterStream(col,
new CharArrayReader(new char[0]), 0);
} else {
row.setNull(col);
}
}
public void updateBlob(Select sel, JDBCStore store, InputStream is)
throws SQLException {
SQLBuffer sql = sel.toSelect(true, store.getFetchConfiguration());
ResultSet res = null;
Connection conn = store.getConnection();
PreparedStatement stmnt = null;
try {
stmnt = sql.prepareStatement(conn, store.getFetchConfiguration(),
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
setTimeouts(stmnt, store.getFetchConfiguration(), true);
res = stmnt.executeQuery();
if (!res.next()) {
throw new InternalException(_loc.get("stream-exception"));
}
Blob blob = res.getBlob(1);
OutputStream os = blob.setBinaryStream(1);
copy(is, os);
os.close();
res.updateBlob(1, blob);
res.updateRow();
} catch (IOException ioe) {
throw new StoreException(ioe);
} finally {
if (res != null)
try { res.close (); } catch (SQLException e) {}
if (stmnt != null)
try { stmnt.close (); } catch (SQLException e) {}
if (conn != null)
try { conn.close (); } catch (SQLException e) {}
}
}
public void updateClob(Select sel, JDBCStore store, Reader reader)
throws SQLException {
SQLBuffer sql = sel.toSelect(true, store.getFetchConfiguration());
ResultSet res = null;
Connection conn = store.getConnection();
PreparedStatement stmnt = null;
try {
stmnt = sql.prepareStatement(conn, store.getFetchConfiguration(),
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
setTimeouts(stmnt, store.getFetchConfiguration(), true);
res = stmnt.executeQuery();
if (!res.next()) {
throw new InternalException(_loc.get("stream-exception"));
}
Clob clob = res.getClob(1);
if (clob != null) {
Writer writer = clob.setCharacterStream(1);
copy(reader, writer);
writer.close();
res.updateClob(1, clob);
res.updateRow();
}
} catch (IOException ioe) {
throw new StoreException(ioe);
} finally {
if (res != null)
try { res.close (); } catch (SQLException e) {}
if (stmnt != null)
try { stmnt.close (); } catch (SQLException e) {}
if (conn != null)
try { conn.close (); } catch (SQLException e) {}
}
}
protected long copy(InputStream in, OutputStream out) throws IOException {
byte[] copyBuffer = new byte[blobBufferSize];
long bytesCopied = 0;
int read = -1;
while ((read = in.read(copyBuffer, 0, copyBuffer.length)) != -1) {
out.write(copyBuffer, 0, read);
bytesCopied += read;
}
return bytesCopied;
}
protected long copy(Reader reader, Writer writer) throws IOException {
char[] copyBuffer = new char[clobBufferSize];
long bytesCopied = 0;
int read = -1;
while ((read = reader.read(copyBuffer, 0, copyBuffer.length)) != -1) {
writer.write(copyBuffer, 0, read);
bytesCopied += read;
}
return bytesCopied;
}
/**
* Attach CAST to the current function if necessary
*
* @param val operand value
* @parma func the sql function statement
* @return a String with the correct CAST function syntax
*/
public String getCastFunction(Val val, String func) {
return func;
}
/**
* Return the correct CAST function syntax. This should be overriden by subclasses
* that need access to the Column information.
*
* @param val operand of cast
* @param func original string
* @param col database column
* @return a String with the correct CAST function syntax
*/
public String getCastFunction(Val val, String func, Column col) {
return getCastFunction (val, func);
}
/**
* Create an index if necessary for some database tables
* @deprecated
*/
@Deprecated
public void createIndexIfNecessary(Schema schema, String table,
Column pkColumn) {
}
public void createIndexIfNecessary(Schema schema, DBIdentifier table,
Column pkColumn) {
}
/**
* Return the batchLimit
*/
public int getBatchLimit(){
return batchLimit;
}
/**
* Set the batchLimit value
*/
public void setBatchLimit(int limit){
batchLimit = limit;
}
/**
* Validate the batch process. In some cases, we can't batch the statements
* due to some restrictions. For example, if the GeneratedType=IDENTITY,
* we have to disable the batch process because we need to get the ID value
* right away for the in-memory entity to use.
*/
public boolean validateBatchProcess(RowImpl row, Column[] autoAssign,
OpenJPAStateManager sm, ClassMapping cmd ) {
boolean disableBatch = false;
if (getBatchLimit()== 0) return false;
if (autoAssign != null && sm != null) {
FieldMetaData[] fmd = cmd.getPrimaryKeyFields();
int i = 0;
while (!disableBatch && i < fmd.length) {
if (fmd[i].getValueStrategy() == ValueStrategies.AUTOASSIGN)
disableBatch = true;
i++;
}
}
// go to each Dictionary to validate the batch capability
if (!disableBatch)
disableBatch = validateDBSpecificBatchProcess(disableBatch, row,
autoAssign, sm, cmd);
return disableBatch;
}
/**
* Allow each Dictionary to validate its own batch process.
*/
public boolean validateDBSpecificBatchProcess (boolean disableBatch,
RowImpl row, Column[] autoAssign,
OpenJPAStateManager sm, ClassMapping cmd ) {
return disableBatch;
}
/**
* This method is to provide override for non-JDBC or JDBC-like
* implementation of executing query.
*/
protected ResultSet executeQuery(Connection conn, PreparedStatement stmnt,
String sql
) throws SQLException {
return stmnt.executeQuery();
}
/**
* This method is to provide override for non-JDBC or JDBC-like
* implementation of preparing statement.
*/
protected PreparedStatement prepareStatement(Connection conn, String sql)
throws SQLException {
return conn.prepareStatement(sql);
}
/**
* This method is to provide override for non-JDBC or JDBC-like
* implementation of getting sequence from the result set.
*/
protected Sequence[] getSequence(ResultSet rs) throws SQLException {
List seqList = new ArrayList();
while (rs != null && rs.next())
seqList.add(newSequence(rs));
return (Sequence[]) seqList.toArray(new Sequence[seqList.size()]);
}
/**
* This method is to provide override for non-JDBC or JDBC-like
* implementation of getting key from the result set.
*/
protected Object getKey (ResultSet rs, Column col) throws SQLException {
if (!rs.next())
throw new StoreException(_loc.get("no-genkey"));
Object key = rs.getObject(1);
if (key == null)
log.warn(_loc.get("invalid-genkey", col));
return key;
}
/**
* This method is to provide override for non-JDBC or JDBC-like
* implementation of calculating value.
*/
protected void calculateValue(Val val, Select sel, ExpContext ctx,
ExpState state, Path path, ExpState pathState) {
val.calculateValue(sel, ctx, state, (Val) path, pathState);
}
/**
* Determine whether the provided <code>sql</code> may be treated as a
* select statement on this database.
*
* @param sql A sql statement.
*
* @return true if <code>sql</code> represents a select statement.
*/
public boolean isSelect(String sql) {
Iterator i = selectWordSet.iterator();
String cur;
while (i.hasNext()) {
cur = (String) i.next();
if (sql.length() >= cur.length()
&& sql.substring(0, cur.length()).equalsIgnoreCase(cur)) {
return true;
}
}
return false;
}
public boolean needsToCreateIndex(Index idx, Table table, Unique[] uniques) {
return needsToCreateIndex(idx, table);
}
public boolean needsToCreateIndex(Index idx, Table table) {
return true;
}
/**
* Return batched statements update success count
* @param ps A PreparedStatement
* @return return update count
*/
public int getBatchUpdateCount(PreparedStatement ps) throws SQLException {
return 0;
}
public boolean getTrimSchemaName() {
return trimSchemaName;
}
public void setTrimSchemaName(boolean trimSchemaName) {
this.trimSchemaName = trimSchemaName;
}
public void deleteStream(JDBCStore store, Select sel) throws SQLException {
// Do nothing
}
/**
* Validate that the given name is not longer than given maximum length.
* If the given name is indeed longer then raises a UserException with the
* given message key otherwise returns the same name.
*/
final String checkNameLength(String name, int length, String msgKey) {
if (name.length() > length) {
throw new UserException(_loc.get(msgKey, name, name.length(), length));
}
return name;
}
/**
* Validate that the given name is not longer than given maximum length. Uses the unqualified name
* from the supplied {@link DBIdentifier} by default..
*
* @param identifier The database identifier to check.
* @param length Max length for this type of identifier
* @param msgKey message identifier for the exception.
*
* @throws {@link UserException} with the given message key if the given name is indeed longer.
* @return the same name.
*/
final String checkNameLength(DBIdentifier identifier, int length, String msgKey) {
return checkNameLength(identifier, length, msgKey, false);
}
/**
* Validate that the given name is not longer than given maximum length. Conditionally uses the unqualified name
* from the supplied {@link DBIdentifier}.
*
* @param identifier The database identifier to check.
* @param length Max length for this type of identifier
* @param msgKey message identifier for the exception.
* @param qualified If true the qualified name of the DBIdentifier will be used.
*
* @throws {@link UserException} with the given message key if the given name is indeed longer.
* @return the same name.
*/
final String checkNameLength(DBIdentifier identifier, int length, String msgKey, boolean qualified) {
// always return the input name,
String name = toDBName(identifier);
String compareName = qualified ? name : toDBName(identifier.getUnqualifiedName());
if (compareName.length() > length) {
throw new UserException(_loc.get(msgKey, name, name.length(), length));
}
return name;
}
protected void setDelimitedCase(DatabaseMetaData metaData) {
try {
if (metaData.storesMixedCaseQuotedIdentifiers()) {
delimitedCase = SCHEMA_CASE_PRESERVE;
}
else if (metaData.storesUpperCaseQuotedIdentifiers()) {
delimitedCase = SCHEMA_CASE_UPPER;
}
else if (metaData.storesLowerCaseQuotedIdentifiers()) {
delimitedCase = SCHEMA_CASE_LOWER;
}
} catch (SQLException e) {
getLog().warn("cannot-determine-identifier-case");
if (getLog().isTraceEnabled()) {
getLog().trace(e.toString(), e);
}
}
}
/**
* @return the supportsDelimitedIds
*/
@Override
public boolean getSupportsDelimitedIdentifiers() {
return (supportsDelimitedIdentifiers == null ? false : supportsDelimitedIdentifiers);
}
/**
* @param supportsDelimitedIds the supportsDelimitedIds to set
*/
public void setSupportsDelimitedIdentifiers(boolean supportsDelimitedIds) {
supportsDelimitedIdentifiers = Boolean.valueOf(supportsDelimitedIds);
}
/**
* @param metaData the DatabaseMetaData to use to determine whether delimiters can be supported
*/
private void setSupportsDelimitedIdentifiers(DatabaseMetaData metaData) {
try {
supportsDelimitedIdentifiers = Boolean.valueOf(
metaData.supportsMixedCaseQuotedIdentifiers() ||
metaData.storesLowerCaseQuotedIdentifiers() ||
metaData.storesUpperCaseQuotedIdentifiers());
} catch (SQLException e) {
supportsDelimitedIdentifiers = Boolean.valueOf(false);
getLog().warn(_loc.get("unknown-delim-support", e));
}
}
/**
* @return the delimitIds
*/
public boolean getDelimitIdentifiers() {
return delimitIdentifiers;
}
/**
* @param delimitIds the delimitIds to set
*/
public void setDelimitIdentifiers(boolean delimitIds) {
delimitIdentifiers = delimitIds;
}
/**
* @return supportsXMLColumn
*/
public boolean getSupportsXMLColumn() {
return supportsXMLColumn;
}
/**
* @param b boolean representing if XML columns are supported
*/
public void setSupportsXMLColumn(boolean b) {
supportsXMLColumn = b;
}
/**
* @return xmlTypeEncoding
*/
public String getXMLTypeEncoding() {
return xmlTypeEncoding;
}
/**
* @param encoding database required JAXB encoding for the XML value
*/
public void setXMLTypeEncoding(String encoding) {
xmlTypeEncoding = encoding;
}
public Log getLog() {
return log;
}
@Override
public boolean delimitAll() {
return delimitIdentifiers;
}
@Override
public String getLeadingDelimiter() {
return leadingDelimiter;
}
public void setLeadingDelimiter(String delim) {
leadingDelimiter = delim;
}
@Override
public String getIdentifierDelimiter() {
return catalogSeparator;
}
@Override
public String getIdentifierConcatenator() {
return nameConcatenator;
}
@Override
public String getTrailingDelimiter() {
return trailingDelimiter;
}
public void setTrailingDelimiter(String delim) {
trailingDelimiter = delim;
}
@Override
public IdentifierRule getDefaultIdentifierRule() {
if (defaultNamingRule == null) {
defaultNamingRule = namingRules.get(DBIdentifierType.DEFAULT.name());
}
return defaultNamingRule;
}
@Override
public <T> IdentifierRule getIdentifierRule(T t) {
if (t.equals(DBIdentifierType.DEFAULT.name())) {
return getDefaultIdentifierRule();
}
IdentifierRule nrule = namingRules.get(t);
if (nrule == null) {
return getDefaultIdentifierRule();
}
return nrule;
}
@Override
@SuppressWarnings("unchecked")
public Map<String, IdentifierRule> getIdentifierRules() {
return namingRules;
}
/**
* Returns the naming utility used by this dictionary instance
*/
public DBIdentifierUtil getNamingUtil() {
return namingUtil;
}
@Override
public String getDelimitedCase() {
return delimitedCase;
}
public String toDBName(DBIdentifier name) {
if (!getSupportsDelimitedIdentifiers())
return name.getName();
else
return getNamingUtil().toDBName(name);
}
public String toDBName(DBIdentifier name, boolean delimit) {
if (!getSupportsDelimitedIdentifiers())
return name.getName();
else
return getNamingUtil().toDBName(name, delimit);
}
public DBIdentifier fromDBName(String name, DBIdentifierType id) {
return getNamingUtil().fromDBName(name, id);
}
public void setDefaultSchemaName(String defaultSchemaName) {
this.defaultSchemaName = defaultSchemaName;
}
public String getDefaultSchemaName() {
return defaultSchemaName;
}
@Override
public String getConversionKey() {
if (conversionKey == null) {
conversionKey = getLeadingDelimiter() + getIdentifierDelimiter() +
getTrailingDelimiter();
}
return conversionKey;
}
/**
* Return parameter marker for INSERT and UPDATE statements.
* Usually it is <code>?</code> but some database-specific types might require customization.
*
* @param col column definition
* @param val value to be inserted/updated
* @return parameter marker
*/
public String getMarkerForInsertUpdate(Column col, Object val) {
return "?";
}
public String getIsNullSQL(String colAlias, int colType) {
return String.format("%s IS NULL", colAlias);
}
public String getIsNotNullSQL(String colAlias, int colType) {
return String.format("%s IS NOT NULL", colAlias);
}
public String getIdentityColumnName() {
return null;
}
/**
* Default behavior is ROUND
*/
public DateMillisecondBehaviors getMillisecondBehavior() {
// If the user hasn't configured this property, fall back
// to looking at the old property in the mean time till
// we can get rid of it.
if (dateMillisecondBehavior == null) {
if (roundTimeToMillisec) {
dateMillisecondBehavior = DateMillisecondBehaviors.ROUND;
} else {
dateMillisecondBehavior = DateMillisecondBehaviors.DROP;
}
}
return dateMillisecondBehavior;
}
public void setDateMillisecondBehavior(String str) {
if (str != null) {
// Tolerate different case
str = str.toUpperCase(Locale.ENGLISH);
dateMillisecondBehavior = DateMillisecondBehaviors.valueOf(str);
} else {
dateMillisecondBehavior = null;
}
}
public BooleanRepresentation getBooleanRepresentation() {
return booleanRepresentation;
}
public void setBooleanRepresentation(String booleanRepresentationKey) {
BooleanRepresentation evaluatedBooleanRepresentation = null;
if (booleanRepresentationKey != null && booleanRepresentationKey.length() > 0) {
ClassLoader cl = conf.getUserClassLoader();
evaluatedBooleanRepresentation = BooleanRepresentationFactory.valueOf(booleanRepresentationKey, cl);
}
booleanRepresentation = evaluatedBooleanRepresentation != null
? evaluatedBooleanRepresentation
: BooleanRepresentationFactory.INT_10;
if (log.isInfoEnabled()) {
log.info(_loc.get("using-booleanRepresentation", booleanRepresentation));
}
}
protected boolean isUsingRange(long start, long end) {
return isUsingOffset(start) || isUsingLimit(end);
}
protected boolean isUsingOffset(long start) {
return start != 0;
}
protected boolean isUsingLimit(long end) {
return end != Long.MAX_VALUE;
}
protected boolean isUsingOrderBy(SQLBuffer sql) {
return sql != null && !sql.isEmpty();
}
protected boolean versionEqualOrLaterThan(int maj, int min) {
return (major > maj) || (major == maj && minor >= min);
}
protected boolean versionEqualOrEarlierThan(int maj, int min) {
return (major < maj) || (major == maj && minor <= min);
}
protected boolean versionLaterThan(int maj) {
return (major > maj);
}
/**
* Gets major version of the database server.
*/
public final int getMajorVersion() {
return major;
}
/**
* Sets major version of the database server.
*/
public void setMajorVersion(int maj) {
major = maj;
}
/**
* Gets minor version of the database server.
*/
public final int getMinorVersion() {
return major;
}
/**
* Sets minor version of the database server.
*/
public void setMinorVersion(int min) {
minor = min;
}
String nullSafe(String s) {
return s == null ? "" : s;
}
public int applyRange(Select select, int count) {
return count;
}
}