blob: 61b119c5987bfd00c41eadd140034d47b16f5ba0 [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.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Arrays;
import javax.sql.DataSource;
import org.apache.openjpa.util.StoreException;
/**
* Dictionary for Apache Derby (formerly Cloudscape).
*/
public class DerbyDictionary
extends AbstractDB2Dictionary {
/**
* If true, we will shutdown the embedded database when closing
* the DataSource.
*/
public boolean shutdownOnClose = true;
public DerbyDictionary() {
platform = "Apache Derby";
validationSQL = "VALUES(1)";
stringLengthFunction = "LENGTH({0})";
substringFunctionName = "SUBSTR";
toUpperCaseFunction = "UPPER(CAST({0} AS VARCHAR(" + varcharCastLength + ")))";
toLowerCaseFunction = "LOWER(CAST({0} AS VARCHAR(" + varcharCastLength + ")))";
// Derby name length restriction has been relaxed
//http://www.archivum.info/derby-dev@db.apache.org/2004-12/msg00270.html
maxConstraintNameLength = 128;
maxIndexNameLength = 128;
maxColumnNameLength = 128;
maxTableNameLength = 128;
useGetBytesForBlobs = true;
useSetBytesForBlobs = true;
allowsAliasInBulkClause = false;
supportsDeferredConstraints = false;
supportsParameterInSelect = false;
supportsSelectForUpdate = true;
supportsDefaultDeleteAction = false;
requiresCastForMathFunctions = true;
requiresCastForComparisons = true;
supportsSimpleCaseExpression = false;
supportsNullUniqueColumn = false;
supportsComments = true;
// Derby does still not support 'WITH TIMEZONE' from the SQL92 standard
fixedSizeTypeNameSet.addAll(Arrays.asList(new String[]{
"BIGINT", "INTEGER", "TEXT"
}));
reservedWordSet.addAll(Arrays.asList(new String[]{
"BOOLEAN", "CALL", "ENDEXEC", "EXPLAIN", "FUNCTION",
"GET_CURRENT_CONNECTION", "INOUT", "LONGINT", "LTRIM", "NONE",
"NVARCHAR", "OFF", "OUT", "RTRIM", "SUBSTR", "XML", "XMLEXISTS",
"XMLPARSE", "XMLSERIALIZE",
}));
// reservedWordSet subset that CANNOT be used as valid column names
// (i.e., without surrounding them with double-quotes)
// generated at 2021-05-02T14:07:25.218 via org.apache.openjpa.reservedwords.ReservedWordsIT
invalidColumnWordSet.addAll(Arrays.asList(new String[] {
"ADD", "ALL", "ALLOCATE", "ALTER", "AND", "ANY", "ARE", "AS", "ASC", "ASSERTION", "AT", "AUTHORIZATION", "AVG",
"BEGIN", "BETWEEN", "BIGINT", "BIT", "BOOLEAN", "BOTH", "BY", "CALL", "CASCADE", "CASCADED", "CASE", "CAST", "CHAR",
"CHARACTER", "CHARACTER_LENGTH", "CHECK", "CLOSE", "COLLATE", "COLLATION", "COLUMN", "COMMIT", "CONNECT", "CONNECTION",
"CONSTRAINT", "CONSTRAINTS", "CONTINUE", "CONVERT", "CORRESPONDING", "CREATE", "CROSS", "CURRENT", "CURRENT_DATE",
"CURRENT_ROLE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", "CURSOR", "DEALLOCATE", "DEC", "DECIMAL",
"DECLARE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DEFINER", "DELETE", "DESC", "DESCRIBE", "DETERMINISTIC", "DIAGNOSTICS",
"DISCONNECT", "DISTINCT", "DOUBLE", "DROP", "ELSE", "END", "END-EXEC", "ESCAPE", "EXCEPT", "EXCEPTION", "EXEC",
"EXECUTE", "EXISTS", "EXPLAIN", "EXTERNAL", "FALSE", "FETCH", "FIRST", "FLOAT", "FOR", "FOREIGN", "FOUND", "FROM",
"FULL", "FUNCTION", "GET", "GETCURRENTCONNECTION", "GLOBAL", "GO", "GOTO", "GRANT", "GROUP", "HAVING", "HOUR",
"IDENTITY", "IMMEDIATE", "IN", "INDICATOR", "INITIALLY", "INNER", "INOUT", "INPUT", "INSENSITIVE", "INSERT", "INT",
"INTEGER", "INTERSECT", "INTO", "INVOKER", "IS", "ISOLATION", "JOIN", "KEY", "LAST", "LEADING", "LEFT", "LIKE",
"LOWER", "LTRIM", "MATCH", "MAX", "MIN", "MINUTE", "NATIONAL", "NATURAL", "NCHAR", "NEXT", "NO", "NONE", "NOT",
"NULL", "NULLIF", "NUMERIC", "NVARCHAR", "OF", "ON", "ONLY", "OPEN", "OPTION", "OR", "ORDER", "OUT", "OUTER", "OUTPUT",
"OVERLAPS", "PAD", "PARTIAL", "PREPARE", "PRESERVE", "PRIMARY", "PRIOR", "PRIVILEGES", "PROCEDURE", "PUBLIC", "READ",
"REAL", "REFERENCES", "RELATIVE", "RESTRICT", "REVOKE", "RIGHT", "ROLLBACK", "ROWS", "RTRIM", "SCHEMA", "SCROLL",
"SECOND", "SELECT", "SESSION_USER", "SET", "SMALLINT", "SOME", "SPACE", "SQL", "SQLCODE", "SQLERROR", "SQLSTATE",
"SUBSTR", "SUBSTRING", "SUM", "SYSTEM_USER", "TABLE", "TEMPORARY", "TIMEZONE_HOUR", "TIMEZONE_MINUTE", "TO", "TRAILING",
"TRANSACTION", "TRANSLATE", "TRANSLATION", "TRIM", "TRUE", "UNION", "UNIQUE", "UNKNOWN", "UPDATE", "UPPER", "USER",
"USING", "VALUES", "VARCHAR", "VARYING", "VIEW", "WHENEVER", "WHERE", "WINDOW", "WITH", "WORK", "WRITE", "XML",
"XMLEXISTS", "XMLPARSE", "XMLQUERY", "XMLSERIALIZE", "YEAR",
// end generated.
// the following keywords used to be defined as reserved words in the past, but now seem to work
// we still add them for compat reasons
"COALESCE", "OVER", "ROW_NUMBER",
}));
}
@Override
public void connectedConfiguration(Connection conn) throws SQLException {
super.connectedConfiguration(conn);
if (versionEqualOrLaterThan(10, 5)) {
supportsSelectStartIndex = true;
supportsSelectEndIndex = true;
}
}
/**
* Appends a range to the given buffer.
* <br>
* A range query is never appended to a subselct clause.
* <br>
* If this dictionary supports {@link DBDictionary#supportsSelectStartIndex offset}
* and {@link DBDictionary#supportsSelectEndIndex limit} on queries then the
* syntax is <pre>
* [ OFFSET {start} ROWS ]
* [ FETCH NEXT {end-start} ROWS ONLY ]
* </pre>
* Otherwise, the offset is not used and the syntax is <pre>
* [ FETCH FIRST {end} ROWS ONLY ]
* </pre>
* @param buf the SQL buffer to be appended
* @param start starting offset. {@code 0} means offset is not used.
* @param end number of rows to be fetched. {@code Long.MAX_VALUE} means no limit.
* @param subselect flags if the buffer represents a SQL Subquery clause
*/
@Override
protected void appendSelectRange(SQLBuffer buf, long start, long end, boolean subselect) {
// do not generate FETCH FIRST clause for subselect
if (subselect)
return;
if (supportsSelectStartIndex && supportsSelectEndIndex) {
if (isUsingOffset(start))
buf.append(" OFFSET ").append(Long.toString(start)).append(" ROWS ");
if (isUsingLimit(end)) {
long rowCount = end - start;
buf.append(" FETCH NEXT ").append(Long.toString(rowCount)).append(" ROWS ONLY");
}
} else if (isUsingLimit(end)) {
buf.append(" FETCH FIRST ").append(Long.toString(end)).append(" ROWS ONLY");
}
}
@Override
public void closeDataSource(DataSource dataSource) {
super.closeDataSource(dataSource);
if (!shutdownOnClose)
return;
// as well as closing the DataSource, we also need to
// shut down the instance if we are using an embedded database, which
// can only be done by connecting to the same URL with the
// ";shutdown=true" string appended to the end
// see: http://db.apache.org/derby/docs/dev/devguide/tdevdvlp40464.html
if (conf != null && conf.getConnectionDriverName() != null &&
conf.getConnectionDriverName().indexOf("EmbeddedDriver") != -1) {
try {
DriverManager.getConnection(conf.getConnectionURL()
+ ";shutdown=true");
} catch (SQLException e) {
// we actually expect a SQLException to be thrown here:
// Derby strangely uses that as a mechanism to report
// a successful shutdown
}
}
}
@Override
public boolean isFatalException(int subtype, SQLException ex) {
int errorCode = ex.getErrorCode();
if ((subtype == StoreException.LOCK ||
subtype == StoreException.QUERY) && errorCode <= 30000) {
return false;
}
return super.isFatalException(subtype, ex);
}
/**
* Applies range calculation on the actual number of rows selected by a
* {@code COUNT(*)} query. A range query may use either only the limit or
* both offset and limit based on database dictionary support and
* accordingly the number of rows in the result set needs to be modified.
*
* @param select
* @param count
* @return
*/
@Override
public int applyRange(Select select, int count) {
long start = select.getStartIndex();
long end = select.getEndIndex();
if (supportsSelectStartIndex) {
if (start > 0)
count -= start;
if (end != Long.MAX_VALUE) {
long size = end - start;
count = (int) Math.min(count, size);
}
}
return count;
}
/**
* Derby doesn't support SQL-2003 'WITH TIMEZONE' nor the respective JDBC types.
*/
@Override
public int getPreferredType(int type) {
switch (type) {
case Types.TIME_WITH_TIMEZONE:
return Types.TIME;
case Types.TIMESTAMP_WITH_TIMEZONE:
return Types.TIMESTAMP;
default:
return type;
}
}
}