OPENJPA-983: FirebirdDictionary improvements

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@763992 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Milosz Tylenda 2009-04-10 17:57:57 +00:00
parent 20b3559bbe
commit 62aabafc53
4 changed files with 522 additions and 21 deletions

View File

@ -18,44 +18,460 @@
*/ */
package org.apache.openjpa.jdbc.sql; package org.apache.openjpa.jdbc.sql;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.openjpa.jdbc.kernel.exps.FilterValue; import org.apache.openjpa.jdbc.kernel.exps.FilterValue;
import org.apache.openjpa.jdbc.schema.Column; import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.ForeignKey;
import org.apache.openjpa.jdbc.schema.Index;
import org.apache.openjpa.jdbc.schema.Sequence;
import org.apache.openjpa.jdbc.schema.Unique;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.util.UnsupportedException;
/** /**
* Dictionary for Firebird. * Dictionary for Firebird. Supports Firebird versions 1.5, 2.0 and 2.1.
*/ */
public class FirebirdDictionary public class FirebirdDictionary
extends InterbaseDictionary { extends DBDictionary {
public int firebirdVersion = 0;
public int indexedVarcharMaxSizeFB15 = 252;
public String rangeSyntax = null;
protected long maxRowNumberInRange = 16000000000L;
protected String alterSequenceSQLFB15 = "SET GENERATOR {0} TO {1}";
protected String alterSequenceSQLFB20 =
"ALTER SEQUENCE {0} RESTART WITH {1}";
protected String createSequenceSQLFB15 = "CREATE GENERATOR {0}";
protected String createSequenceSQLFB20 = "CREATE SEQUENCE {0}";
protected String dropSequenceSQLFB15 = "DROP GENERATOR ";
protected String nextSequenceQueryFB15 =
"SELECT GEN_ID({0}, 1) FROM RDB$DATABASE";
protected String nextSequenceQueryFB20 =
"SELECT NEXT VALUE FOR {0} FROM RDB$DATABASE";
protected String alterSequenceSQL = alterSequenceSQLFB20;
protected String createSequenceSQL = createSequenceSQLFB20;
public static final int FB_VERSION_15 = 15;
public static final int FB_VERSION_20 = 20;
public static final int FB_VERSION_21 = 21;
public static final String RANGE_SYNTAX_FIRST_SKIP = "firstskip";
public static final String RANGE_SYNTAX_ROWS = "rows";
private static final Localizer _loc =
Localizer.forPackage(FirebirdDictionary.class);
@SuppressWarnings("unchecked")
public FirebirdDictionary() { public FirebirdDictionary() {
platform = "Firebird"; platform = "Firebird";
validationSQL = "SELECT 1 FROM RDB$DATABASE";
supportsDeferredConstraints = false;
// Firebird 1.5+ locking statement useGetStringForClobs = true;
useSetStringForClobs = true;
useGetBytesForBlobs = true;
useSetBytesForBlobs = true;
maxTableNameLength = 31;
maxColumnNameLength = 31;
maxConstraintNameLength = 31;
maxIndexNameLength = 31;
supportsSelectStartIndex = true;
supportsSelectEndIndex = true;
supportsMultipleNontransactionalResultSets = false;
nextSequenceQuery = nextSequenceQueryFB20;
sequenceSQL =
"SELECT NULL AS SEQUENCE_SCHEMA, RDB$GENERATOR_NAME "
+ "AS SEQUENCE_NAME FROM RDB$GENERATORS "
+ "WHERE (RDB$SYSTEM_FLAG IS NULL OR RDB$SYSTEM_FLAG = 0) ";
sequenceNameSQL = "AND RDB$GENERATOR_NAME = ?";
// A rough sum of reserved words in Firebird 1.0 - 2.1.
reservedWordSet.addAll(Arrays.asList(new String[] { "ACTIVE", "ADMIN",
"AFTER", "ASCENDING", "AUTO", "AUTODDL", "BASED", "BASENAME",
"BASE_NAME", "BEFORE", "BIGINT", "BLOB", "BLOBEDIT", "BUFFER",
"CACHE", "CHECK_POINT_LEN", "CHECK_POINT_LENGTH", "COMPILETIME",
"COMPUTED", "CLOSE", "CONDITIONAL", "CONTAINING", "CSTRING",
"CURRENT_CONNECTION", "CURRENT_ROLE", "CURRENT_TRANSACTION",
"DATABASE", "DB_KEY", "DEBUG", "DESCENDING", "DO", "ECHO", "EDIT",
"ENTRY_POINT", "EVENT", "EXIT", "EXTERN", "FILE", "FILTER",
"FREE_IT", "FUNCTION", "GDSCODE", "GENERATOR", "GEN_ID", "GLOBAL",
"GOTO", "GROUP_COMMIT_WAIT", "GROUP_COMMIT_WAIT_TIME", "HELP",
"IF", "INACTIVE", "INDEX", "INIT", "INPUT_TYPE", "ISQL",
"LC_MESSAGES", "LC_TYPE", "LEV", "LOGFILE", "LOG_BUFFER_SIZE",
"LOG_BUF_SIZE", "LONG", "MANUAL", "MAXIMUM", "MAXIMUM_SEGMENT",
"MAX_SEGMENT", "MERGE", "MESSAGE", "MINUTE", "MODULE_NAME",
"NOAUTO", "NUM_LOG_BUFS", "NUM_LOG_BUFFERS", "OUTPUT_TYPE",
"OVERFLOW", "PAGE", "PAGELENGTH", "PAGES", "PAGE_SIZE",
"PARAMETER", "PASSWORD", "PLAN", "POST_EVENT", "PROCEDURE",
"PROTECTED", "QUIT", "RAW_PARTITIONS", "RDB$DB_KEY",
"RECORD_VERSION", "RECREATE", "RELEASE", "RESERV", "RESERVING",
"RETAIN", "RETURN", "RETURNING_VALUES", "RETURNS", "ROLE",
"RUNTIME", "SAVEPOINT", "SEGMENT", "SHADOW", "SHARED", "SHELL",
"SHOW", "SINGULAR", "SNAPSHOT", "SORT", "SQLWARNING", "STABILITY",
"START", "STARTING", "STARTS", "STATEMENT", "STATIC", "STATISTICS",
"SUB_TYPE", "SUSPEND", "TERMINATOR", "TRIGGER", "VARIABLE",
"VERSION", "WAIT", "WEEKDAY", "WHILE" }));
binaryTypeName = "BLOB";
bitTypeName = "SMALLINT";
charTypeName = "CHAR(1)";
clobTypeName = "BLOB SUB_TYPE 1";
doubleTypeName = "DOUBLE PRECISION";
floatTypeName = "DOUBLE PRECISION";
longVarbinaryTypeName = "BLOB";
longVarcharTypeName = "BLOB SUB_TYPE 1";
realTypeName = "FLOAT";
smallintTypeName = "SMALLINT";
tinyintTypeName = "SMALLINT";
varbinaryTypeName = "BLOB";
supportsLockingWithDistinctClause = false;
supportsLockingWithMultipleTables = false; supportsLockingWithMultipleTables = false;
supportsLockingWithOuterJoin = false;
supportsLockingWithInnerJoin = false;
forUpdateClause = "FOR UPDATE WITH LOCK"; forUpdateClause = "FOR UPDATE WITH LOCK";
supportsQueryTimeout = false;
} }
/**
* Determine Firebird version and configure itself accordingly.
*/
@SuppressWarnings("unchecked")
@Override
public void connectedConfiguration(Connection conn) throws SQLException {
super.connectedConfiguration(conn);
firebirdVersion = determineFirebirdVersion(conn);
determineRangeSyntax();
if (firebirdVersion == FB_VERSION_21)
selectWordSet.add("WITH");
if (!(firebirdVersion == FB_VERSION_21)) {
crossJoinClause = "JOIN";
requiresConditionForCrossJoin = true;
}
if (firebirdVersion == FB_VERSION_15) {
stringLengthFunction = "STRLEN({0})";
trimLeadingFunction = "LTRIM({0})";
trimTrailingFunction = "RTRIM({0})";
trimBothFunction = "LTRIM(RTRIM({0}))";
alterSequenceSQL = alterSequenceSQLFB15;
createSequenceSQL = createSequenceSQLFB15;
nextSequenceQuery = nextSequenceQueryFB15;
}
}
/**
* Use either <code>FIRST &lt;p&gt; SKIP &lt;q&gt;</code> or
* <code>ROWS &lt;m&gt; TO &lt;n&gt;</code> syntax. If <code>ROWS</code>
* variant is used and <code>end</code> equals {@link Long#MAX_VALUE}, a
* constant is used as <code>&lt;n&gt;</code> value.
*/
@Override
protected void appendSelectRange(SQLBuffer buf, long start, long end,
boolean subselect) {
if (RANGE_SYNTAX_FIRST_SKIP.equals(rangeSyntax)) {
if (end != Long.MAX_VALUE)
buf.append(" FIRST ").appendValue(end - start);
if (start != 0)
buf.append(" SKIP ").appendValue(start);
return;
}
buf.append(" ROWS ");
if (start == 0) {
buf.appendValue(end);
return;
}
buf.appendValue(start + 1).append(" TO ");
if (end == Long.MAX_VALUE)
buf.appendValue(maxRowNumberInRange);
else
buf.appendValue(end);
}
/**
* Determine Firebird version either by using JDBC 3 methods or, if they
* are not available, by parsing the value returned by
* {@linkplain DatabaseMetaData#getDatabaseProductVersion()}. User can
* override Firebird version.
*/
protected int determineFirebirdVersion(Connection con)
throws SQLException {
// Let user override firebirdVersion.
if (firebirdVersion != 0)
return firebirdVersion;
DatabaseMetaData metaData = con.getMetaData();
int maj = 0;
int min = 0;
if (isJDBC3) {
maj = metaData.getDatabaseMajorVersion();
min = metaData.getDatabaseMinorVersion();
} else {
try {
// The product version looks like
// "LI-V2.1.1.17910 Firebird 2.1,LI-V2.1.1.17910 Firebird
// 2.1/tcp (hostname)/P10" or
// "WI-V1.5.5.4926 Firebird 1.52WI-V1.5.5.4926 Firebird 1.5/tcp
// (hostname)/P10"
String productVersion = metaData.getDatabaseProductVersion();
Pattern p = Pattern.compile(".*-V(\\d)\\.(\\d)\\..*",
Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(productVersion);
m.matches();
String majString = m.group(1);
String minString = m.group(2);
maj = Integer.parseInt(majString);
min = Integer.parseInt(minString);
} catch (Exception e) {
// We don't understand the version format.
if (log.isWarnEnabled())
log.warn(e.toString(), e);
}
}
if (maj < 2)
return FB_VERSION_15;
if (maj == 2 && min == 0)
return FB_VERSION_20;
return FB_VERSION_21;
}
/**
* Determine range syntax to be used depending on Firebird version.
* User can override range syntax.
*/
protected void determineRangeSyntax() {
// Let user override rangeSyntax.
if (rangeSyntax == null)
rangeSyntax =
(firebirdVersion == FB_VERSION_15) ? RANGE_SYNTAX_FIRST_SKIP
: RANGE_SYNTAX_ROWS;
if (RANGE_SYNTAX_FIRST_SKIP.equals(rangeSyntax))
rangePosition = RANGE_PRE_DISTINCT;
else
rangePosition = RANGE_POST_SELECT;
}
/**
* Return <code>&lt;value&gt; AS &lt;type&gt;</code>.
*/
@Override
public String getPlaceholderValueString(Column col) { public String getPlaceholderValueString(Column col) {
return super.getPlaceholderValueString(col) return super.getPlaceholderValueString(col) + " AS "
+ " AS " + getTypeName(col); + getTypeName(col);
} }
/**
* Return <code>%</code> if <code>tableName</code> is <code>null</code>,
* otherwise delegate to super implementation.
*/
@Override
protected String getTableNameForMetadata(String tableName) {
return (tableName == null) ? "%" : super
.getTableNameForMetadata(tableName);
}
/**
* Return <code>%</code> if <code>columnName</code> is <code>null</code>,
* otherwise delegate to super implementation.
*/
@Override
protected String getColumnNameForMetadata(String columnName) {
return (columnName == null) ? "%" : super
.getColumnNameForMetadata(columnName);
}
/**
* Return
* <code>ALTER TABLE &lt;table name&gt; DROP &lt;col name&gt;</code>.
*/
@Override
public String[] getDropColumnSQL(Column column) {
return new String[] { "ALTER TABLE "
+ getFullName(column.getTable(), false) + " DROP " + column };
}
/**
* Return either
* <code>CREATE SEQUENCE &lt;sequence name&gt;</code> or
* <code>CREATE GENERATOR &lt;sequence name&gt;</code>.
* If initial value of sequence is set, return also
* an appropriate <code>ALTER</code> statement.
*/
@Override
public String[] getCreateSequenceSQL(Sequence seq) {
String seqName =
checkNameLength(getFullName(seq), maxTableNameLength,
"long-seq-name");
String createSeq =
MessageFormat.format(createSequenceSQL, new Object[] { seqName });
if (seq.getInitialValue() == 0)
return new String[] { createSeq };
// Use String.valueOf to get rid of possible number formatting.
String alterSeq =
MessageFormat.format(alterSequenceSQL, new Object[] { seqName,
String.valueOf(seq.getInitialValue()) });
return new String[] { createSeq, alterSeq };
}
/**
* Return Firebird-specific statement to select the list of sequences.
*/
@Override
protected String getSequencesSQL(String schemaName, String sequenceName) {
StringBuilder buf = new StringBuilder(sequenceSQL);
if (sequenceName != null)
buf.append(sequenceNameSQL);
return buf.toString();
}
/**
* Call super implementation and trim sequence name. This is because of
* trailing spaces problem: <code>RDB$GENERATORS.RDB$GENERATOR_NAME</code>
* is <code>CHAR(31)</code> and using <code>RTRIM</code> UDF function on
* Firebird 1.5 surprisingly returns a string right-padded with spaces up
* to the length of 255.
*/
@Override
protected Sequence newSequence(ResultSet sequenceMeta) throws SQLException {
Sequence seq = super.newSequence(sequenceMeta);
seq.setName(seq.getName().trim());
return seq;
}
/**
* On Firebird 1.5 return
* <code>DROP GENERATOR &lt;sequence name&gt;</code>.
* On Firebird 2.0 and later delegate to the super implementation.
*/
@Override
public String[] getDropSequenceSQL(Sequence seq) {
if (firebirdVersion == FB_VERSION_15)
return new String[] { dropSequenceSQLFB15 + getFullName(seq) };
return super.getDropSequenceSQL(seq);
}
/**
* Throw {@link UnsupportedException}. Firebird in version earlier than 2.1
* has no suitable function. Firebird 2.1 has the <code>POSITION</code>
* function but using it here results in errors like "data type unknown" or
* "expression evaluation not supported".
*/
@Override
public void indexOf(SQLBuffer buf, FilterValue str, FilterValue find,
FilterValue start) {
throw new UnsupportedException(_loc.get("function-not-supported",
getClass(), "LOCATE"));
}
/**
* Use
* <code>SUBSTRING(&lt;col name&gt; FROM &lt;m&gt; FOR &lt;n&gt;)</code>.
* Parameters are inlined because neither parameter binding nor expressions
* are accepted by Firebird here. As a result, an
* {@link UnsupportedException} is thrown when something else than a
* constant is used in <code>start</code> or <code>end</code>.
*/
@Override
public void substring(SQLBuffer buf, FilterValue str, FilterValue start, public void substring(SQLBuffer buf, FilterValue str, FilterValue start,
FilterValue end) { FilterValue end) {
// SUBSTRING in Firebird is of the form: buf.append(substringFunctionName).append("(");
// SELECT SUBSTRING(SOME_COLUMN FROM 1 FOR 5)
buf.append("SUBSTRING(");
str.appendTo(buf); str.appendTo(buf);
buf.append(" FROM "); buf.append(" FROM ");
start.appendTo(buf); if (start.getValue() instanceof Number) {
buf.append(" + 1"); long startLong = toLong(start);
buf.append(Long.toString(startLong + 1));
} else {
throw new UnsupportedException(_loc.get("function-not-supported",
getClass(), substringFunctionName + " with non-constants"));
}
if (end != null) { if (end != null) {
buf.append(" FOR "); buf.append(" FOR ");
end.appendTo(buf); if (start.getValue() instanceof Number
buf.append(" - ("); && end.getValue() instanceof Number) {
start.appendTo(buf); long startLong = toLong(start);
buf.append(")"); long endLong = toLong(end);
buf.append(Long.toString(endLong - startLong));
} else {
throw new UnsupportedException(_loc.get(
"function-not-supported", getClass(), substringFunctionName
+ " with non-constants"));
}
} }
buf.append(")"); buf.append(")");
} }
/**
* On Firebird 1.5 reduce the size of indexed <code>VARCHAR</code> column
* to 252 or a value specified by user. 252 is the maximum Firebird 1.5 can
* handle for one-column indexes. On Firebird 2.0 and later delegate to the
* super implementation.
*/
@Override
protected String appendSize(Column col, String typeName) {
if (firebirdVersion != FB_VERSION_15)
return super.appendSize(col, typeName);
if (col.getType() == Types.VARCHAR
&& col.getSize() > indexedVarcharMaxSizeFB15
&& col.getTable() != null) {
if (col.isPrimaryKey()) {
col.setSize(indexedVarcharMaxSizeFB15);
return super.appendSize(col, typeName);
}
Index[] indexes = col.getTable().getIndexes();
for (Index index : indexes) {
if (index.containsColumn(col)) {
col.setSize(indexedVarcharMaxSizeFB15);
return super.appendSize(col, typeName);
}
}
Unique[] uniques = col.getTable().getUniques();
for (Unique unique : uniques) {
if (unique.containsColumn(col)) {
col.setSize(indexedVarcharMaxSizeFB15);
return super.appendSize(col, typeName);
}
}
ForeignKey[] foreignKeys = col.getTable().getForeignKeys();
for (ForeignKey foreignKey : foreignKeys) {
if (foreignKey.containsColumn(col)) {
col.setSize(indexedVarcharMaxSizeFB15);
return super.appendSize(col, typeName);
}
}
}
return super.appendSize(col, typeName);
}
/**
* Use error code as SQL state returned by Firebird is ambiguous.
*/
@Override
protected Boolean matchErrorState(int subtype, Set<String> errorStates,
SQLException ex) {
int errorCode = ex.getErrorCode();
return errorStates.contains(String.valueOf(errorCode)) ? Boolean.FALSE
: null;
}
} }

View File

@ -137,9 +137,9 @@
</dictionary> </dictionary>
<dictionary class="org.apache.openjpa.jdbc.sql.FirebirdDictionary"> <dictionary class="org.apache.openjpa.jdbc.sql.FirebirdDictionary">
<lock></lock> <lock>335544336</lock>
<referential-integrity></referential-integrity> <referential-integrity></referential-integrity>
<object-exists></object-exists> <object-exists>335544665</object-exists>
<object-not-found></object-not-found> <object-not-found></object-not-found>
<optimistic></optimistic> <optimistic></optimistic>
<query></query> <query></query>

View File

@ -3148,6 +3148,86 @@ overridden. It is used only when the schema is generated using the
in alphabetical order --> in alphabetical order -->
</itemizedlist> </itemizedlist>
</section> </section>
<section id="ref_guide_dbsetup_dbsupport_firebird">
<title>
FirebirdDictionary Properties
</title>
<indexterm zone="ref_guide_dbsetup_dbsupport_firebird">
<primary>
Firebird
</primary>
<seealso>
DBDictionary
</seealso>
</indexterm>
<para>
The <literal>firebird</literal> dictionary understands the following additional
properties:
</para>
<itemizedlist>
<listitem id="FirebirdDictionary.FirebirdVersion">
<para>
<indexterm>
<primary>
Firebird
</primary>
<secondary>
FirebirdVersion
</secondary>
</indexterm>
<literal>FirebirdVersion</literal>: The database version OpenJPA connects to.
This property affects the SQL statements executed by OpenJPA.
Available values are: 15, 20 and 21
- they indicate Firebird versions 1.5, 2.0 and 2.1 respectively.
If not set, the value will be auto-detected.
</para>
</listitem>
<listitem id="FirebirdDictionary.IndexedVarcharMaxSizeFB15">
<para>
<indexterm>
<primary>
Firebird
</primary>
<secondary>
IndexedVarcharMaxSizeFB15
</secondary>
</indexterm>
<literal>IndexedVarcharMaxSizeFB15</literal>: Firebird 1.5 imposes
tight limits on index size. In particular, an indexed
<literal>VARCHAR</literal> column size cannot exceed 252.
When <link linkend="ref_guide_mapping_mappingtool">schema is created</link>,
OpenJPA will use this property to reduce the size
of indexed <literal>VARCHAR</literal> columns.
Defaults to 252 but you might want to decrease this value if multi-column
indexes are used. If the Firebird version is 2.0 or later or
schema creation is not used, this property does not matter.
</para>
</listitem>
<listitem id="FirebirdDictionary.RangeSyntax">
<para>
<indexterm>
<primary>
Firebird
</primary>
<secondary>
RangeSyntax
</secondary>
</indexterm>
<literal>RangeSyntax</literal>: Firebird 2.0 and later support two
ways of handling queries that select a range of data:
<literal>"FIRST &lt;p&gt; SKIP &lt;q&gt;"</literal> and
<literal>"ROWS &lt;m&gt; TO &lt;n&gt;"</literal>. Earlier versions support only
<literal>"FIRST &lt;p&gt; SKIP &lt;q&gt;"</literal> syntax.
This property determines the syntax to be used.
Available values are:
<literal>"firstskip"</literal> and <literal>"rows"</literal>.
Defaults to using <literal>"ROWS &lt;m&gt; TO &lt;n&gt;"</literal> if the
Firebird version is 2.0 or later, and
<literal>"FIRST &lt;p&gt; SKIP &lt;q&gt;"</literal> otherwise.
</para>
</listitem>
</itemizedlist>
</section>
<section id="ref_guide_dbsetup_dbsupport_mysql"> <section id="ref_guide_dbsetup_dbsupport_mysql">
<title> <title>
MySQLDictionary Properties MySQLDictionary Properties

View File

@ -126,13 +126,13 @@ by OpenJPA.
Firebird Firebird
</entry> </entry>
<entry colname="dbversion"> <entry colname="dbversion">
1.5 1.5, 2.0, 2.1
</entry> </entry>
<entry colname="drivname"> <entry colname="drivname">
JayBird JCA/JDBC driver JayBird JCA/JDBC driver
</entry> </entry>
<entry colname="drivversion"> <entry colname="drivversion">
1.0.1 2.1.6
</entry> </entry>
</row> </row>
<row> <row>
@ -553,7 +553,7 @@ will fail unless the <literal>SimulateLocking</literal> property is set for the
</title> </title>
<programlisting> <programlisting>
openjpa.ConnectionDriverName: org.firebirdsql.jdbc.FBDriver openjpa.ConnectionDriverName: org.firebirdsql.jdbc.FBDriver
openjpa.ConnectionURL: jdbc:firebirdsql://SERVER_NAME:SERVER_PORT/DB_PATH openjpa.ConnectionURL: jdbc:firebirdsql:SERVER_NAME/3050:DB_PATH_OR_ALIAS
</programlisting> </programlisting>
</example> </example>
<section id="dbsupport_firebird_issues"> <section id="dbsupport_firebird_issues">
@ -568,8 +568,13 @@ Firebird does not support auto-increment columns.
</listitem> </listitem>
<listitem> <listitem>
<para> <para>
Firebird does not support the <literal>LOWER</literal>, <literal>SUBSTRING In order to use many of JPQL functions with Firebird 1.5, Interbase UDFs
</literal>, or <literal>INSTR</literal> SQL functions. have to be available in the database.
</para>
</listitem>
<listitem>
<para>
<literal>LOCATE</literal> JPQL function is not supported.
</para> </para>
</listitem> </listitem>
</itemizedlist> </itemizedlist>