org.firebirdsql.jdbc.FBDatabaseMetaData Maven / Gradle / Ivy
Show all versions of jaybird-jdk18 Show documentation
/*
* Firebird Open Source JavaEE Connector - JDBC Driver
*
* Distributable under LGPL license.
* You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* LGPL License for more details.
*
* This file was created by members of the firebird development team.
* All individual contributions remain the Copyright (C) of those
* individuals. Contributors to this file are either listed here or
* can be obtained from a source control history command.
*
* All rights reserved.
*/
package org.firebirdsql.jdbc;
import org.firebirdsql.encodings.EncodingFactory;
import org.firebirdsql.gds.impl.GDSFactory;
import org.firebirdsql.gds.impl.GDSHelper;
import org.firebirdsql.gds.impl.GDSType;
import org.firebirdsql.gds.ng.DatatypeCoder;
import org.firebirdsql.gds.ng.DefaultDatatypeCoder;
import org.firebirdsql.gds.ng.fields.RowDescriptor;
import org.firebirdsql.gds.ng.fields.RowDescriptorBuilder;
import org.firebirdsql.gds.ng.fields.RowValue;
import org.firebirdsql.gds.ng.fields.RowValueBuilder;
import org.firebirdsql.jca.FBManagedConnectionFactory;
import org.firebirdsql.jdbc.escape.FBEscapedFunctionHelper;
import org.firebirdsql.jdbc.field.JdbcTypeConverter;
import org.firebirdsql.logging.Logger;
import org.firebirdsql.logging.LoggerFactory;
import org.firebirdsql.util.FirebirdSupportInfo;
import java.nio.charset.StandardCharsets;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.sql.*;
import java.util.*;
import static org.firebirdsql.gds.ISCConstants.*;
import static org.firebirdsql.util.FirebirdSupportInfo.supportInfoFor;
/**
* Comprehensive information about the database as a whole.
*
* Many of the methods here return lists of information in
* the form of ResultSet
objects.
* You can use the normal ResultSet
methods such as getString and getInt
* to retrieve the data from these ResultSet
. If a given form of
* metadata is not available, these methods should throw an SQLException.
*
*
Some of these methods take arguments that are String patterns. These
* arguments all have names such as fooPattern. Within a pattern String, "%"
* means match any substring of 0 or more characters, and "_" means match
* any one character. Only metadata entries matching the search pattern
* are returned. If a search pattern argument is set to a null ref,
* that argument's criteria will be dropped from the search.
*
*
An SQLException
will be thrown if a driver does not support a meta
* data method. In the case of methods that return a ResultSet
,
* either a ResultSet
(which may be empty) is returned or a
* SQLException is thrown.
*
* @author David Jencks
* @author Mark Rotteveel
*/
public class FBDatabaseMetaData implements FirebirdDatabaseMetaData {
private final static Logger log = LoggerFactory.getLogger(FBDatabaseMetaData.class);
private static final String SPACES_31 = " "; // 31 spaces
private static final String SPACES_15 = " "; // 15 spaces
private static final int OBJECT_NAME_LENGTH_BEFORE_V4_0 = 31;
private static final int OBJECT_NAME_LENGTH_V4_0 = 63;
private static final int OBJECT_NAME_LENGTH = OBJECT_NAME_LENGTH_V4_0;
private static final int DRIVER_MAJOR_VERSION = 3;
private static final int DRIVER_MINOR_VERSION = 0;
private static final String DRIVER_VERSION = DRIVER_MAJOR_VERSION + "." + DRIVER_MINOR_VERSION;
private static final int SUBTYPE_NUMERIC = 1;
private static final int SUBTYPE_DECIMAL = 2;
protected static final DatatypeCoder datatypeCoder =
new DefaultDatatypeCoder(EncodingFactory.createInstance(StandardCharsets.UTF_8));
private static final byte[] TRUE_BYTES = getBytes("T");
private static final byte[] FALSE_BYTES = getBytes("F");
private static final byte[] YES_BYTES = getBytes("YES");
private static final byte[] NO_BYTES = getBytes("NO");
private static final byte[] EMPTY_STRING_BYTES = getBytes("");
private static final byte[] CASESENSITIVE = TRUE_BYTES;
private static final byte[] CASEINSENSITIVE = FALSE_BYTES;
private static final byte[] UNSIGNED = TRUE_BYTES;
private static final byte[] SIGNED = FALSE_BYTES;
private static final byte[] FIXEDSCALE = TRUE_BYTES;
private static final byte[] VARIABLESCALE = FALSE_BYTES;
private static final byte[] NOTAUTOINC = FALSE_BYTES;
private static final byte[] INT_ZERO = createInt(0);
private static final byte[] SHORT_ZERO = createShort(0);
private static final byte[] SHORT_ONE = createShort(1);
private static final byte[] RADIX_BINARY = createInt(2);
private static final byte[] RADIX_TEN = createInt(10);
private static final byte[] RADIX_TEN_SHORT = createShort(10);
private static final byte[] RADIX_BINARY_SHORT = createShort(2);
private static final byte[] TYPE_PRED_NONE = createShort(DatabaseMetaData.typePredNone);
private static final byte[] TYPE_PRED_BASIC = createShort(DatabaseMetaData.typePredBasic);
private static final byte[] TYPE_SEARCHABLE = createShort(DatabaseMetaData.typeSearchable);
private static final byte[] TYPE_NULLABLE = createShort(DatabaseMetaData.typeNullable);
private static final byte[] PROCEDURE_NO_RESULT = createShort(DatabaseMetaData.procedureNoResult);
private static final byte[] PROCEDURE_RETURNS_RESULT = createShort(DatabaseMetaData.procedureReturnsResult);
private static final byte[] PROCEDURE_NO_NULLS = createShort(DatabaseMetaData.procedureNoNulls);
private static final byte[] PROCEDURE_NULLABLE = createShort(DatabaseMetaData.procedureNullable);
private static final byte[] PROCEDURE_COLUMN_IN = createShort(DatabaseMetaData.procedureColumnIn);
private static final byte[] PROCEDURE_COLUMN_OUT = createShort(DatabaseMetaData.procedureColumnOut);
private static final byte[] FLOAT_PRECISION = createInt(7);
private static final byte[] DOUBLE_PRECISION = createInt(15);
private static final byte[] BIGINT_PRECISION = createInt(19);
private static final byte[] INTEGER_PRECISION = createInt(10);
private static final byte[] SMALLINT_PRECISION = createInt(5);
private static final byte[] DATE_PRECISION = createInt(10);
private static final byte[] TIME_PRECISION = createInt(8);
private static final byte[] TIMESTAMP_PRECISION = createInt(19);
private static final byte[] NUMERIC_PRECISION = createInt(18);
private static final byte[] DECIMAL_PRECISION = createInt(18);
private static final byte[] BOOLEAN_PRECISION = createInt(1);
private static final byte[] COLUMN_NO_NULLS = createInt(DatabaseMetaData.columnNoNulls);
private static final byte[] COLUMN_NULLABLE = createInt(DatabaseMetaData.columnNullable);
private static final byte[] IMPORTED_KEY_NO_ACTION = createShort(DatabaseMetaData.importedKeyNoAction);
private static final byte[] IMPORTED_KEY_CASCADE = createShort(DatabaseMetaData.importedKeyCascade);
private static final byte[] IMPORTED_KEY_SET_NULL = createShort(DatabaseMetaData.importedKeySetNull);
private static final byte[] IMPORTED_KEY_SET_DEFAULT = createShort(DatabaseMetaData.importedKeySetDefault);
private static final byte[] IMPORTED_KEY_NOT_DEFERRABLE = createShort(DatabaseMetaData.importedKeyNotDeferrable);
private static final byte[] TABLE_INDEX_OTHER = createShort(DatabaseMetaData.tableIndexOther);
private static final byte[] ASC_BYTES = getBytes("A");
private static final byte[] DESC_BYTES = getBytes("D");
private GDSHelper gdsHelper;
private FBConnection connection;
private final FirebirdSupportInfo firebirdSupportInfo;
private static final int STATEMENT_CACHE_SIZE = 12;
private final Map statements = new LruPreparedStatementCache(STATEMENT_CACHE_SIZE);
private final FirebirdVersionMetaData versionMetaData;
protected FBDatabaseMetaData(FBConnection c) throws SQLException {
this.gdsHelper = c.getGDSHelper();
this.connection = c;
firebirdSupportInfo = supportInfoFor(c);
versionMetaData = FirebirdVersionMetaData.getVersionMetaDataFor(c);
}
@Override
public void close() {
synchronized (statements) {
if (statements.isEmpty()) {
return;
}
try {
for (FBStatement stmt : statements.values()) {
try {
stmt.close();
} catch (Exception e) {
log.warn("error closing cached statements in DatabaseMetaData.close; "
+ "see debug level for stacktrace");
log.debug("error closing cached statements in DatabaseMetaData.close", e);
}
}
} finally {
statements.clear();
}
}
}
//----------------------------------------------------------------------
// First, a variety of minor information about the target database.
/**
* Can all the procedures returned by getProcedures be called by the
* current user?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean allProceduresAreCallable() throws SQLException {
//returns all procedures whether or not you have execute permission
return false;
}
/**
* Can all the tables returned by getTable be SELECTed by the
* current user?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean allTablesAreSelectable() throws SQLException {
//returns all tables matching criteria independent of access permissions.
return false;
}
/**
* What's the url for this database?
*
* @return the url or null if it cannot be generated
* @exception SQLException if a database access error occurs
*/
public String getURL() throws SQLException {
// TODO Think of a less complex way to obtain the url
GDSType gdsType = ((FBManagedConnectionFactory) connection.mc.getManagedConnectionFactory()).getGDSType();
return GDSFactory.getJdbcUrl(gdsType, connection.mc.getDatabase());
}
/**
* What's our user name as known to the database?
*
* @return our database user name
* @exception SQLException
* if a database access error occurs
*/
public String getUserName() throws SQLException {
return gdsHelper.getUserName();
}
/**
* Is the database in read-only mode?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean isReadOnly() throws SQLException {
return false;//could be true, not yetimplemented
}
/**
* Are NULL values sorted high?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean nullsAreSortedHigh() throws SQLException {
// in Firebird 1.5.x NULLs are always sorted at the end
// in Firebird 2.0.x NULLs are sorted low
return false;
}
/**
* Are NULL values sorted low?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean nullsAreSortedLow() throws SQLException {
// in Firebird 1.5.x NULLs are always sorted at the end
// in Firebird 2.0.x NULLs are sorted low
return gdsHelper.compareToVersion(2, 0) >= 0;
}
/**
* Are NULL values sorted at the start regardless of sort order?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean nullsAreSortedAtStart() throws SQLException {
// in Firebird 1.5.x NULLs are always sorted at the end
// in Firebird 2.0.x NULLs are sorted low
return false;
}
/**
* Are NULL values sorted at the end regardless of sort order?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean nullsAreSortedAtEnd() throws SQLException {
// in Firebird 1.5.x NULLs are always sorted at the end
// in Firebird 2.0.x NULLs are sorted low
return gdsHelper.compareToVersion(2, 0) < 0;
}
/**
* What's the name of this database product?
*
* @return database product name
* @exception SQLException if a database access error occurs
*/
public String getDatabaseProductName() throws SQLException {
return gdsHelper.getDatabaseProductName();
}
/**
* What's the version of this database product?
*
* @return database version
* @exception SQLException if a database access error occurs
*/
public String getDatabaseProductVersion() throws SQLException {
return gdsHelper.getDatabaseProductVersion();
}
/**
* What's the name of this JDBC driver?
*
* @return JDBC driver name
* @exception SQLException if a database access error occurs
*/
public String getDriverName() throws SQLException {
return "Jaybird JCA/JDBC driver";
}
/**
* What's the version of this JDBC driver?
*
* @return JDBC driver version
* @exception SQLException if a database access error occurs
*/
public String getDriverVersion() throws SQLException {
return DRIVER_VERSION;
}
/**
* What's this JDBC driver's major version number?
*
* @return JDBC driver major version
*/
public int getDriverMajorVersion() {
return DRIVER_MAJOR_VERSION;
}
/**
* What's this JDBC driver's minor version number?
*
* @return JDBC driver minor version number
*/
public int getDriverMinorVersion() {
return DRIVER_MINOR_VERSION;
}
/**
* Does the database store tables in a local file?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean usesLocalFiles() throws SQLException {
return false;
}
/**
* Does the database use a file for each table?
*
* @return true if the database uses a local file for each table
* @exception SQLException if a database access error occurs
*/
public boolean usesLocalFilePerTable() throws SQLException {
return false;
}
/**
* Does the database treat mixed case unquoted SQL identifiers as
* case sensitive and as a result store them in mixed case?
*
* A JDBC CompliantTM driver will always return false.
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsMixedCaseIdentifiers() throws SQLException {
return false;
}
/**
*
* @return a boolean
value
* @exception SQLException if an error occurs
* TODO implement statement pooling on the server.. then in the driver
*/
public boolean supportsStatementPooling() throws SQLException {
return false;
}
public boolean locatorsUpdateCopy() throws SQLException {
// Firebird creates a new blob when making changes
return true;
}
/**
* Does the database treat mixed case unquoted SQL identifiers as
* case insensitive and store them in upper case?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean storesUpperCaseIdentifiers() throws SQLException {
return true;
}
/**
* Does the database treat mixed case unquoted SQL identifiers as
* case insensitive and store them in lower case?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean storesLowerCaseIdentifiers() throws SQLException {
return false;
}
/**
* Does the database treat mixed case unquoted SQL identifiers as
* case insensitive and store them in mixed case?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean storesMixedCaseIdentifiers() throws SQLException {
return false;
}
/**
* Does the database treat mixed case quoted SQL identifiers as
* case sensitive and as a result store them in mixed case?
*
* A JDBC CompliantTM driver will always return true.
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException {
return true;
}
/**
* Does the database treat mixed case quoted SQL identifiers as
* case insensitive and store them in upper case?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean storesUpperCaseQuotedIdentifiers() throws SQLException {
return false;
}
/**
* Does the database treat mixed case quoted SQL identifiers as
* case insensitive and store them in lower case?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean storesLowerCaseQuotedIdentifiers() throws SQLException {
return false;
}
/**
* Does the database treat mixed case quoted SQL identifiers as
* case insensitive and store them in mixed case?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean storesMixedCaseQuotedIdentifiers() throws SQLException {
return false;
}
/**
* What's the string used to quote SQL identifiers?
* This returns a space " " if identifier quoting isn't supported.
*
* A JDBC CompliantTM
* driver always uses a double quote character.
*
* @return the quoting string
* @exception SQLException if a database access error occurs
*/
public String getIdentifierQuoteString() throws SQLException {
return "\"";
}
@Override
public String getSQLKeywords() throws SQLException {
return versionMetaData.getSqlKeywords();
}
/**
* NOTE: Some of the functions listed may only work on Firebird 2.1 or higher, or when equivalent UDFs
* are installed.
*
* {@inheritDoc}
*
*/
public String getNumericFunctions() throws SQLException {
return collectionToCommaSeparatedList(FBEscapedFunctionHelper.getSupportedNumericFunctions());
}
private static String collectionToCommaSeparatedList(Collection collection) {
StringBuilder sb = new StringBuilder();
for (String item : collection) {
sb.append(item);
sb.append(',');
}
sb.setLength(sb.length() - 1);
return sb.toString();
}
/**
* NOTE: Some of the functions listed may only work on Firebird 2.1 or higher, or when equivalent UDFs
* are installed.
*
* {@inheritDoc}
*
*/
public String getStringFunctions() throws SQLException {
return collectionToCommaSeparatedList(FBEscapedFunctionHelper.getSupportedStringFunctions());
}
/**
* NOTE: Some of the functions listed may only work on Firebird 2.1 or higher, or when equivalent UDFs
* are installed.
*
* {@inheritDoc}
*
*/
public String getSystemFunctions() throws SQLException {
return collectionToCommaSeparatedList(FBEscapedFunctionHelper.getSupportedSystemFunctions());
}
/**
* NOTE: Some of the functions listed may only work on Firebird 2.1 or higher, or when equivalent UDFs
* are installed.
*
* {@inheritDoc}
*
*/
public String getTimeDateFunctions() throws SQLException {
return collectionToCommaSeparatedList(FBEscapedFunctionHelper.getSupportedTimeDateFunctions());
}
/**
* Gets the string that can be used to escape wildcard characters.
* This is the string that can be used to escape '_' or '%' in
* the string pattern style catalog search parameters.
*
* The '_' character represents any single character.
*
The '%' character represents any sequence of zero or
* more characters.
*
* @return the string used to escape wildcard characters
* @exception SQLException if a database access error occurs
*/
public String getSearchStringEscape() throws SQLException {
return "\\";
}
/**
* Gets all the "extra" characters that can be used in unquoted
* identifier names (those beyond a-z, A-Z, 0-9 and _).
*
* @return the string containing the extra characters
* @exception SQLException if a database access error occurs
*/
public String getExtraNameCharacters() throws SQLException {
return "$";
}
//--------------------------------------------------------------------
// Functions describing which features are supported.
/**
* Is "ALTER TABLE" with add column supported?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsAlterTableWithAddColumn() throws SQLException {
return true;
}
/**
* Is "ALTER TABLE" with drop column supported?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsAlterTableWithDropColumn() throws SQLException {
return true;
}
/**
* Is column aliasing supported?
*
*
If so, the SQL AS clause can be used to provide names for
* computed columns or to provide alias names for columns as
* required.
*
* A JDBC CompliantTM driver always returns true.
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsColumnAliasing() throws SQLException {
return true;
}
/**
* Retrieves whether concatenations between NULL and non-NULL values
* equal NULL. For SQL-92 compliance, a JDBC technology-enabled driver will
* return true
.
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean nullPlusNonNullIsNull() throws SQLException {
return true;
}
/**
* Is the CONVERT function between SQL types supported?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsConvert() throws SQLException {
// TODO: Set true after JDBC-294 has been done
return false; // Support is broken right now
}
/**
* Retrieves whether CONVERT between the given SQL types supported.
*
* @param fromType the type to convert from
* @param toType the type to convert to
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
* @see Types
*/
public boolean supportsConvert(int fromType, int toType) throws SQLException {
// TODO: implement actual mapping with JDBC-294
return false; // Support is broken right now
}
/**
* Are table correlation names supported?
*
* A JDBC CompliantTM driver always returns true.
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsTableCorrelationNames() throws SQLException {
return true;
}
/**
* If table correlation names are supported, are they restricted
* to be different from the names of the tables?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsDifferentTableCorrelationNames() throws SQLException {
return false; //I think
}
/**
* Are expressions in "ORDER BY" lists supported?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsExpressionsInOrderBy() throws SQLException {
return false; //coming soon
}
/**
* Can an "ORDER BY" clause use columns not in the SELECT statement?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsOrderByUnrelated() throws SQLException {
return true;
}
/**
* Is some form of "GROUP BY" clause supported?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsGroupBy() throws SQLException {
return true;
}
/**
* Can a "GROUP BY" clause use columns not in the SELECT?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsGroupByUnrelated() throws SQLException {
return false;
}
/**
* Can a "GROUP BY" clause add columns not in the SELECT
* provided it specifies all the columns in the SELECT?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsGroupByBeyondSelect() throws SQLException {
return false;
}
/**
* Is the escape character in "LIKE" clauses supported?
*
* A JDBC CompliantTM driver always returns true.
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsLikeEscapeClause() throws SQLException {
return true;
}
/**
* Are multiple ResultSet
from a single execute supported?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsMultipleResultSets() throws SQLException {
return false;
}
/**
* Can we have multiple transactions open at once (on different
* connections)?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsMultipleTransactions() throws SQLException {
return true;
}
/**
* Can columns be defined as non-nullable?
*
* A JDBC CompliantTM driver always returns true.
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsNonNullableColumns() throws SQLException {
return true;
}
/**
* Is the ODBC Minimum SQL grammar supported?
*
* All JDBC CompliantTM drivers must return true.
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsMinimumSQLGrammar() throws SQLException {
return true; //lets see what the tests say
}
/**
* Is the ODBC Core SQL grammar supported?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsCoreSQLGrammar() throws SQLException {
return true; //lets see what the tests say
}
/**
* Is the ODBC Extended SQL grammar supported?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsExtendedSQLGrammar() throws SQLException {
return true; //lets see what the tests say
}
/**
* Is the ANSI92 entry level SQL grammar supported?
*
* All JDBC CompliantTM drivers must return true.
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsANSI92EntryLevelSQL() throws SQLException {
return true; //lets see what the tests say
}
/**
* Is the ANSI92 intermediate SQL grammar supported?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsANSI92IntermediateSQL() throws SQLException {
return false; //lets see what the tests say
}
/**
* Is the ANSI92 full SQL grammar supported?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsANSI92FullSQL() throws SQLException {
return false; //Nah, but lets see what the tests say
}
/**
* Is the SQL Integrity Enhancement Facility supported?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsIntegrityEnhancementFacility() throws SQLException {
return true; // rrokytskyy: yep, they call so foreign keys + cascade deletes
}
public boolean supportsOuterJoins() throws SQLException {
return true;
}
public boolean supportsFullOuterJoins() throws SQLException {
return true;
}
public boolean supportsLimitedOuterJoins() throws SQLException {
return true;
}
/**
* What's the database vendor's preferred term for "schema"?
*
* @return the vendor term, always null
because
* schemas are not supported by database server (see JDBC CTS
* for details).
* @exception SQLException if a database access error occurs
*/
public String getSchemaTerm() throws SQLException {
return null;
}
/**
* What's the database vendor's preferred term for "procedure"?
*
* @return the vendor term
* @exception SQLException if a database access error occurs
*/
public String getProcedureTerm() throws SQLException {
return "PROCEDURE";
}
/**
* What's the database vendor's preferred term for "catalog"?
*
* @return the vendor term, always null
because
* catalogs are not supported by database server (see JDBC CTS
* for details).
*
* @exception SQLException if a database access error occurs
*/
public String getCatalogTerm() throws SQLException {
return null;
}
/**
* Does a catalog appear at the start of a qualified table name?
* (Otherwise it appears at the end)
*
* @return true if it appears at the start
* @exception SQLException if a database access error occurs
*/
public boolean isCatalogAtStart() throws SQLException {
return false;
}
/**
* What's the separator between catalog and table name?
*
* @return the separator string, always null
because
* catalogs are not supported by database server (see JDBC CTS
* for details).
* @exception SQLException if a database access error occurs
*/
public String getCatalogSeparator() throws SQLException {
return null;
}
/**
* Can a schema name be used in a data manipulation statement?
*
* @return true
if so; false
otherwise
*
* @exception SQLException if a database access error occurs
*/
public boolean supportsSchemasInDataManipulation() throws SQLException {
return false;
}
/**
* Can a schema name be used in a procedure call statement?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsSchemasInProcedureCalls() throws SQLException {
return false;
}
/**
* Can a schema name be used in a table definition statement?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsSchemasInTableDefinitions() throws SQLException {
return false;
}
/**
* Can a schema name be used in an index definition statement?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsSchemasInIndexDefinitions() throws SQLException {
return false;
}
/**
* Can a schema name be used in a privilege definition statement?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsSchemasInPrivilegeDefinitions() throws SQLException {
return false;
}
/**
* Can a catalog name be used in a data manipulation statement?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsCatalogsInDataManipulation() throws SQLException {
return false;
}
/**
* Can a catalog name be used in a procedure call statement?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsCatalogsInProcedureCalls() throws SQLException {
return false;
}
/**
* Can a catalog name be used in a table definition statement?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsCatalogsInTableDefinitions() throws SQLException {
return false;
}
/**
* Can a catalog name be used in an index definition statement?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsCatalogsInIndexDefinitions() throws SQLException {
return false;
}
/**
* Can a catalog name be used in a privilege definition statement?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException {
return false;
}
/**
* Is positioned DELETE supported?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsPositionedDelete() throws SQLException {
return true;
}
/**
* Is positioned UPDATE supported?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsPositionedUpdate() throws SQLException {
return true;
}
/**
* Is SELECT for UPDATE supported?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsSelectForUpdate() throws SQLException {
return true;
}
/**
* Are stored procedure calls using the stored procedure escape
* syntax supported?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsStoredProcedures() throws SQLException {
return true;
}
/**
* Are subqueries in comparison expressions supported?
*
* A JDBC CompliantTM driver always returns true.
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsSubqueriesInComparisons() throws SQLException {
return true;
}
/**
* Are subqueries in 'exists' expressions supported?
*
* A JDBC CompliantTM driver always returns true.
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsSubqueriesInExists() throws SQLException {
return true;
}
/**
* Are subqueries in 'in' statements supported?
*
* A JDBC CompliantTM driver always returns true.
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsSubqueriesInIns() throws SQLException {
return true;
}
/**
* Are subqueries in quantified expressions supported?
*
* A JDBC CompliantTM driver always returns true.
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsSubqueriesInQuantifieds() throws SQLException {
return true;
}
/**
* Are correlated subqueries supported?
*
* A JDBC CompliantTM driver always returns true.
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsCorrelatedSubqueries() throws SQLException {
return true;
}
/**
* Is SQL UNION supported?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsUnion() throws SQLException {
return true;
}
/**
* Is SQL UNION ALL supported?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsUnionAll() throws SQLException {
return true;
}
/**
* Can cursors remain open across commits?
*
* @return true
if cursors always remain open;
* false
if they might not remain open
* @exception SQLException if a database access error occurs
*/
public boolean supportsOpenCursorsAcrossCommit() throws SQLException {
return false;//only when commit retaining is executed I think
}
/**
* Can cursors remain open across rollbacks?
*
* @return true
if cursors always remain open;
* false
if they might not remain open
* @exception SQLException if a database access error occurs
*/
public boolean supportsOpenCursorsAcrossRollback() throws SQLException {
return false;//commit retaining only.
}
/**
* Can statements remain open across commits?
*
* @return true
if statements always remain open;
* false
if they might not remain open
* @exception SQLException if a database access error occurs
*/
public boolean supportsOpenStatementsAcrossCommit() throws SQLException {
return true;
}
/**
* Can statements remain open across rollbacks?
*
* @return true
if statements always remain open;
* false
if they might not remain open
* @exception SQLException if a database access error occurs
*/
public boolean supportsOpenStatementsAcrossRollback() throws SQLException {
return true;
}
//----------------------------------------------------------------------
// The following group of methods exposes various limitations
// based on the target database with the current driver.
// Unless otherwise specified, a result of zero means there is no
// limit, or the limit is not known.
/**
* How many hex characters can you have in an inline binary literal?
*
* @return max binary literal length in hex characters;
* a result of zero means that there is no limit or the limit is not known
* @exception SQLException if a database access error occurs
*/
public int getMaxBinaryLiteralLength() throws SQLException {
return 0; // TODO 32764 Test (assumed on length/2 and max string literal length)
}
/**
* What's the max length for a character literal?
*
* @return max literal length;
* a result of zero means that there is no limit or the limit is not known
* @exception SQLException if a database access error occurs
*/
public int getMaxCharLiteralLength() throws SQLException {
return 32765;
}
/**
* What's the limit on column name length?
*
* @return max column name length;
* a result of zero means that there is no limit or the limit is not known
* @exception SQLException if a database access error occurs
*/
public int getMaxColumnNameLength() throws SQLException {
if (gdsHelper.compareToVersion(4, 0) < 0) {
return OBJECT_NAME_LENGTH_BEFORE_V4_0;
} else {
return OBJECT_NAME_LENGTH_V4_0;
}
}
/**
* What's the maximum number of columns in a "GROUP BY" clause?
*
* @return max number of columns;
* a result of zero means that there is no limit or the limit is not known
* @exception SQLException if a database access error occurs
*/
public int getMaxColumnsInGroupBy() throws SQLException {
return 0; //I don't know
}
/**
* What's the maximum number of columns allowed in an index?
*
* @return max number of columns;
* a result of zero means that there is no limit or the limit is not known
* @exception SQLException if a database access error occurs
*/
public int getMaxColumnsInIndex() throws SQLException {
return 0; //I don't know
}
/**
* What's the maximum number of columns in an "ORDER BY" clause?
*
* @return max number of columns;
* a result of zero means that there is no limit or the limit is not known
* @exception SQLException if a database access error occurs
*/
public int getMaxColumnsInOrderBy() throws SQLException {
return 0; //I don't know
}
/**
* What's the maximum number of columns in a "SELECT" list?
*
* @return max number of columns;
* a result of zero means that there is no limit or the limit is not known
* @exception SQLException if a database access error occurs
*/
public int getMaxColumnsInSelect() throws SQLException {
return 0; //I don't know
}
/**
* What's the maximum number of columns in a table?
*
* @return max number of columns;
* a result of zero means that there is no limit or the limit is not known
* @exception SQLException if a database access error occurs
*/
public int getMaxColumnsInTable() throws SQLException {
return 32767; // Depends on datatypes and sizes, at most 64 kbyte excluding blobs (but including blob ids)
}
/**
* How many active connections can we have at a time to this database?
*
* @return max number of active connections;
* a result of zero means that there is no limit or the limit is not known
* @exception SQLException if a database access error occurs
*/
public int getMaxConnections() throws SQLException {
return 0; //I don't know
}
/**
* What's the maximum cursor name length?
*
* @return max cursor name length in bytes;
* a result of zero means that there is no limit or the limit is not known
* @exception SQLException if a database access error occurs
*/
public int getMaxCursorNameLength() throws SQLException {
return 31;
}
/**
* Retrieves the maximum number of bytes for an index, including all
* of the parts of the index.
*
* @return max index length in bytes, which includes the composite of all
* the constituent parts of the index;
* a result of zero means that there is no limit or the limit is not known
* @exception SQLException if a database access error occurs
*/
public int getMaxIndexLength() throws SQLException {
if (gdsHelper.compareToVersion(2, 0) < 0) {
return 252; // See http://www.firebirdsql.org/en/firebird-technical-specifications/
} else {
return 0; // 1/4 of page size, maybe retrieve page size and use that?
}
}
/**
* What's the maximum length allowed for a schema name?
*
* @return max name length in bytes;
* a result of zero means that there is no limit or the limit is not known
* @exception SQLException if a database access error occurs
*/
public int getMaxSchemaNameLength() throws SQLException {
return 0; //No schemas
}
/**
* What's the maximum length of a procedure name?
*
* @return max name length in bytes;
* a result of zero means that there is no limit or the limit is not known
* @exception SQLException if a database access error occurs
*/
public int getMaxProcedureNameLength() throws SQLException {
if (gdsHelper.compareToVersion(4, 0) < 0) {
return OBJECT_NAME_LENGTH_BEFORE_V4_0;
} else {
return OBJECT_NAME_LENGTH_V4_0;
}
}
/**
* What's the maximum length of a catalog name?
*
* @return max name length in bytes;
* a result of zero means that there is no limit or the limit is not known
* @exception SQLException if a database access error occurs
*/
public int getMaxCatalogNameLength() throws SQLException {
return 0; //No catalogs
}
/**
* What's the maximum length of a single row?
*
* @return max row size in bytes;
* a result of zero means that there is no limit or the limit is not known
* @exception SQLException if a database access error occurs
*/
public int getMaxRowSize() throws SQLException {
if (gdsHelper.compareToVersion(1, 5) >= 0)
return 65531;
else
return 0;
}
/**
* Did getMaxRowSize() include LONGVARCHAR and LONGVARBINARY
* blobs?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean doesMaxRowSizeIncludeBlobs() throws SQLException {
return false; // Blob sizes are not included in rowsize
}
/**
* What's the maximum length of an SQL statement?
*
* @return max length in bytes;
* a result of zero means that there is no limit or the limit is not known
* @exception SQLException if a database access error occurs
*/
public int getMaxStatementLength() throws SQLException {
// TODO 10MB (or 2GB?) for Firebird 3 (test if we don't need to change anything else to support this)
return 65536;
}
/**
* How many active statements can we have open at one time to this
* database?
*
* @return the maximum number of statements that can be open at one time;
* a result of zero means that there is no limit or the limit is not known
* @exception SQLException if a database access error occurs
*/
public int getMaxStatements() throws SQLException {
return 0;
}
/**
* What's the maximum length of a table name?
*
* @return max name length in bytes;
* a result of zero means that there is no limit or the limit is not known
* @exception SQLException if a database access error occurs
*/
public int getMaxTableNameLength() throws SQLException {
if (gdsHelper.compareToVersion(4, 0) < 0) {
return OBJECT_NAME_LENGTH_BEFORE_V4_0;
} else {
return OBJECT_NAME_LENGTH_V4_0;
}
}
/**
* What's the maximum number of tables in a SELECT statement?
*
* @return the maximum number of tables allowed in a SELECT statement;
* a result of zero means that there is no limit or the limit is not known
* @exception SQLException if a database access error occurs
*/
public int getMaxTablesInSelect() throws SQLException {
return 0;
}
/**
* What's the maximum length of a user name?
*
* @return max user name length in bytes;
* a result of zero means that there is no limit or the limit is not known
* @exception SQLException if a database access error occurs
*/
public int getMaxUserNameLength() throws SQLException {
return 31;//I don't know??
}
//----------------------------------------------------------------------
/**
* What's the database's default transaction isolation level? The
* values are defined in java.sql.Connection
.
*
* @return the default isolation level
* @exception SQLException if a database access error occurs
* @see Connection
*/
public int getDefaultTransactionIsolation() throws SQLException {
return Connection.TRANSACTION_READ_COMMITTED;//close enough to snapshot.
}
/**
* Are transactions supported? If not, invoking the method
* commit
is a noop and the
* isolation level is TRANSACTION_NONE.
*
* @return true
if transactions are supported; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsTransactions() throws SQLException {
return true;
}
/**
* Does this database support the given transaction isolation level?
*
* @param level the values are defined in java.sql.Connection
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
* @see Connection
*/
public boolean supportsTransactionIsolationLevel(int level) throws SQLException {
switch (level) {
case Connection.TRANSACTION_NONE: return false;
case Connection.TRANSACTION_READ_COMMITTED: return true;
case Connection.TRANSACTION_READ_UNCOMMITTED: return false;
case Connection.TRANSACTION_REPEATABLE_READ: return true;
case Connection.TRANSACTION_SERIALIZABLE: return true;
default: return false;
}
}
/**
* Are both data definition and data manipulation statements
* within a transaction supported?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException {
return true;//but not on the tables you defined in the transaction!
}
/**
* Are only data manipulation statements within a transaction
* supported?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsDataManipulationTransactionsOnly() throws SQLException {
return false;
}
/**
* Does a data definition statement within a transaction force the
* transaction to commit?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean dataDefinitionCausesTransactionCommit() throws SQLException {
return false;//but you can't use the table till the transaction is committed.
}
/**
* Is a data definition statement within a transaction ignored?
*
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean dataDefinitionIgnoredInTransactions() throws SQLException {
return false;
}
private static final String GET_PROCEDURES_START = "select "
+ "cast(RDB$PROCEDURE_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as PROCEDURE_NAME,"
+ "RDB$DESCRIPTION as REMARKS,"
+ "RDB$PROCEDURE_OUTPUTS as PROCEDURE_TYPE "
+ "from "
+ "RDB$PROCEDURES "
+ "where ";
private static final String GET_PROCEDURES_END = "1 = 1 order by 1";
/**
* Gets a description of the stored procedures available in a
* catalog.
*
*
Only procedure descriptions matching the schema and
* procedure name criteria are returned. They are ordered by
* PROCEDURE_SCHEM, and PROCEDURE_NAME.
*
*
Each procedure description has the the following columns:
*
* - PROCEDURE_CAT String => procedure catalog (may be null)
*
- PROCEDURE_SCHEM String => procedure schema (may be null)
*
- PROCEDURE_NAME String => procedure name
*
- reserved for future use
*
- reserved for future use
*
- reserved for future use
*
- REMARKS String => explanatory comment on the procedure
*
- PROCEDURE_TYPE short => kind of procedure:
*
* - procedureResultUnknown - May return a result
*
- procedureNoResult - Does not return a result
*
- procedureReturnsResult - Returns a result
*
* - SPECIFIC_NAME String => The name which uniquely identifies this procedure within its schema.
*
*
* @param catalog a catalog name; "" retrieves those without a
* catalog; null means drop catalog name from the selection criteria
* @param schemaPattern a schema name pattern; "" retrieves those
* without a schema
* @param procedureNamePattern a procedure name pattern
* @return ResultSet
- each row is a procedure description
* @exception SQLException if a database access error occurs
* @see #getSearchStringEscape
*/
public ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern)
throws SQLException {
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(9, datatypeCoder)
.at(0).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PROCEDURE_CAT", "PROCEDURES").addField()
.at(1).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PROCEDURE_SCHEM", "ROCEDURES").addField()
.at(2).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PROCEDURE_NAME", "PROCEDURES").addField()
.at(3).simple(SQL_VARYING, 31, "FUTURE1", "PROCEDURES").addField()
.at(4).simple(SQL_VARYING, 31, "FUTURE2", "PROCEDURES").addField()
.at(5).simple(SQL_VARYING, 31, "FUTURE3", "PROCEDURES").addField()
// Field in Firebird is actually a blob, using Integer.MAX_VALUE for length
.at(6).simple(SQL_VARYING, Integer.MAX_VALUE, "REMARKS", "PROCEDURES").addField()
.at(7).simple(SQL_SHORT, 0, "PROCEDURE_TYPE", "PROCEDURES").addField()
.at(8).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "SPECIFIC_NAME", "PROCEDURES").addField()
.toRowDescriptor();
Clause procedureClause = new Clause("RDB$PROCEDURE_NAME", procedureNamePattern);
String sql = GET_PROCEDURES_START;
sql += procedureClause.getCondition();
sql += GET_PROCEDURES_END;
List params = procedureClause.hasCondition()
? Collections.singletonList(procedureClause.getValue())
: Collections.emptyList();
try (ResultSet rs = doQuery(sql, params)) {
if (!rs.next()) {
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
final List rows = new ArrayList<>();
final RowValueBuilder valueBuilder = new RowValueBuilder(rowDescriptor);
do {
rows.add(valueBuilder
.at(2).set(getBytes(rs.getString("PROCEDURE_NAME")))
.at(6).set(getBytes(rs.getString("REMARKS")))
.at(7).set(rs.getShort("PROCEDURE_TYPE") == 0 ? PROCEDURE_NO_RESULT : PROCEDURE_RETURNS_RESULT)
.at(8).set(valueBuilder.get(2))
.toRowValue(true)
);
} while (rs.next());
return new FBResultSet(rowDescriptor, rows);
}
}
private static final String GET_PROCEDURE_COLUMNS_START = "select "
+ "cast(PP.RDB$PROCEDURE_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as PROCEDURE_NAME,"
+ "cast(PP.RDB$PARAMETER_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as COLUMN_NAME,"
+ "PP.RDB$PARAMETER_TYPE as COLUMN_TYPE,"
+ "F.RDB$FIELD_TYPE as FIELD_TYPE,"
+ "F.RDB$FIELD_SUB_TYPE as FIELD_SUB_TYPE,"
+ "F.RDB$FIELD_PRECISION as FIELD_PRECISION,"
+ "F.RDB$FIELD_SCALE as FIELD_SCALE,"
+ "F.RDB$FIELD_LENGTH as FIELD_LENGTH,"
+ "F.RDB$NULL_FLAG as NULL_FLAG,"
+ "PP.RDB$DESCRIPTION as REMARKS,"
+ "F.RDB$CHARACTER_LENGTH AS CHAR_LEN,"
+ "PP.RDB$PARAMETER_NUMBER + 1 AS PARAMETER_NUMBER,"
+ "F.RDB$CHARACTER_SET_ID "
+ "from "
+ "RDB$PROCEDURE_PARAMETERS PP,"
+ "RDB$FIELDS F "
+ "where ";
private static final String GET_PROCEDURE_COLUMNS_END = " PP.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME "
+ "order by "
+ "PP.RDB$PROCEDURE_NAME,"
+ "PP.RDB$PARAMETER_TYPE desc,"
+ "PP.RDB$PARAMETER_NUMBER ";
/**
* Retrieves a description of the given catalog's stored procedure parameter
* and result columns.
*
* Only descriptions matching the schema, procedure and
* parameter name criteria are returned. They are ordered by
* PROCEDURE_CAT, PROCEDURE_SCHEM, PROCEDURE_NAME and SPECIFIC_NAME. Within this, the return value,
* if any, is first. Next are the parameter descriptions in call
* order. The column descriptions follow in column number order.
*
*
Each row in the ResultSet
is a parameter description or
* column description with the following fields:
*
* - PROCEDURE_CAT String => procedure catalog (may be
null
)
* - PROCEDURE_SCHEM String => procedure schema (may be
null
)
* - PROCEDURE_NAME String => procedure name
*
- COLUMN_NAME String => column/parameter name
*
- COLUMN_TYPE Short => kind of column/parameter:
*
* - procedureColumnUnknown - nobody knows
*
- procedureColumnIn - IN parameter
*
- procedureColumnInOut - INOUT parameter
*
- procedureColumnOut - OUT parameter
*
- procedureColumnReturn - procedure return value
*
- procedureColumnResult - result column in
ResultSet
*
* - DATA_TYPE int => SQL type from java.sql.Types
*
- TYPE_NAME String => SQL type name, for a UDT type the
* type name is fully qualified
*
- PRECISION int => precision
*
- LENGTH int => length in bytes of data
*
- SCALE short => scale - null is returned for data types where
* SCALE is not applicable.
*
- RADIX short => radix
*
- NULLABLE short => can it contain NULL.
*
* - procedureNoNulls - does not allow NULL values
*
- procedureNullable - allows NULL values
*
- procedureNullableUnknown - nullability unknown
*
* - REMARKS String => comment describing parameter/column
*
- COLUMN_DEF String => default value for the column, which should be interpreted as a string when the value is enclosed in single quotes (may be
null
)
*
* - The string NULL (not enclosed in quotes) - if NULL was specified as the default value
*
- TRUNCATE (not enclosed in quotes) - if the specified default value cannot be represented without truncation
*
- NULL - if a default value was not specified
*
* - SQL_DATA_TYPE int => reserved for future use
*
- SQL_DATETIME_SUB int => reserved for future use
*
- CHAR_OCTET_LENGTH int => the maximum length of binary and character based columns. For any other datatype the returned value is a
* NULL
*
- ORDINAL_POSITION int => the ordinal position, starting from 1, for the input and output parameters for a procedure. A value of 0
*is returned if this row describes the procedure's return value. For result set columns, it is the
*ordinal position of the column in the result set starting from 1. If there are
*multiple result sets, the column ordinal positions are implementation
* defined.
*
- IS_NULLABLE String => ISO rules are used to determine the nullability for a column.
*
* - YES --- if the parameter can include NULLs
*
- NO --- if the parameter cannot include NULLs
*
- empty string --- if the nullability for the
* parameter is unknown
*
* - SPECIFIC_NAME String => the name which uniquely identifies this procedure within its schema.
*
*
* Note: Some databases may not return the column
* descriptions for a procedure.
*
*
The PRECISION column represents the specified column size for the given column.
* For numeric data, this is the maximum precision. For character data, this is the length in characters.
* For datetime datatypes, this is the length in characters of the String representation (assuming the
* maximum allowed precision of the fractional seconds component). For binary data, this is the length in bytes. For the ROWID datatype,
* this is the length in bytes. Null is returned for data types where the
* column size is not applicable.
* @param catalog a catalog name; must match the catalog name as it
* is stored in the database; "" retrieves those without a catalog;
* null
means that the catalog name should not be used to narrow
* the search
* @param schemaPattern a schema name pattern; must match the schema name
* as it is stored in the database; "" retrieves those without a schema;
* null
means that the schema name should not be used to narrow
* the search
* @param procedureNamePattern a procedure name pattern; must match the
* procedure name as it is stored in the database
* @param columnNamePattern a column name pattern; must match the column name
* as it is stored in the database
* @return ResultSet
- each row describes a stored procedure parameter or
* column
* @exception SQLException if a database access error occurs
* @see #getSearchStringEscape
*/
public ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern,
String columnNamePattern) throws SQLException {
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(20, datatypeCoder)
.at(0).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PROCEDURE_CAT", "COLUMNINFO").addField()
.at(1).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PROCEDURE_SCHEM", "COLUMNINFO").addField()
.at(2).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PROCEDURE_NAME", "COLUMNINFO").addField()
.at(3).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "COLUMN_NAME", "COLUMNINFO").addField()
.at(4).simple(SQL_SHORT, 0, "COLUMN_TYPE", "COLUMNINFO").addField()
.at(5).simple(SQL_LONG, 0, "DATA_TYPE", "COLUMNINFO").addField()
.at(6).simple(SQL_VARYING, 31, "TYPE_NAME", "COLUMNINFO").addField()
.at(7).simple(SQL_LONG, 0, "PRECISION", "COLUMNINFO").addField()
.at(8).simple(SQL_LONG, 0, "LENGTH", "COLUMNINFO").addField()
.at(9).simple(SQL_SHORT, 0, "SCALE", "COLUMNINFO").addField()
.at(10).simple(SQL_SHORT, 0, "RADIX", "COLUMNINFO").addField()
.at(11).simple(SQL_SHORT, 0, "NULLABLE", "COLUMNINFO").addField()
// Field in Firebird is actually a blob, using Integer.MAX_VALUE for length
.at(12).simple(SQL_VARYING, Integer.MAX_VALUE, "REMARKS", "COLUMNINFO").addField()
.at(13).simple(SQL_VARYING, 31, "COLUMN_DEF", "COLUMNINFO").addField()
.at(14).simple(SQL_LONG, 0, "SQL_DATA_TYPE", "COLUMNINFO").addField()
.at(15).simple(SQL_LONG, 0, "SQL_DATETIME_SUB", "COLUMNINFO").addField()
.at(16).simple(SQL_LONG, 0, "CHAR_OCTET_LENGTH", "COLUMNINFO").addField()
.at(17).simple(SQL_LONG, 0, "ORDINAL_POSITION", "COLUMNINFO").addField()
.at(18).simple(SQL_VARYING, 3, "IS_NULLABLE", "COLUMNINFO").addField()
.at(19).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "SPECIFIC_NAME", "COLUMNINFO").addField()
.toRowDescriptor();
Clause procedureClause = new Clause("PP.RDB$PROCEDURE_NAME", procedureNamePattern);
Clause columnClause = new Clause("PP.RDB$PARAMETER_NAME", columnNamePattern);
String sql = GET_PROCEDURE_COLUMNS_START;
sql += procedureClause.getCondition();
sql += columnClause.getCondition();
sql += GET_PROCEDURE_COLUMNS_END;
List params = new ArrayList<>(2);
if (procedureClause.hasCondition()) {
params.add(procedureClause.getValue());
}
if (columnClause.hasCondition()) {
params.add(columnClause.getValue());
}
try (ResultSet rs = doQuery(sql, params)) {
// if nothing found, return an empty result set
if (!rs.next()) {
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
final List rows = new ArrayList<>();
final RowValueBuilder valueBuilder = new RowValueBuilder(rowDescriptor);
do {
final short columnType = rs.getShort("COLUMN_TYPE");
final short fieldType = rs.getShort("FIELD_TYPE");
final short fieldSubType = rs.getShort("FIELD_SUB_TYPE");
final short fieldScale = rs.getShort("FIELD_SCALE");
final int characterSetId = rs.getInt("RDB$CHARACTER_SET_ID");
// TODO: Find out what the difference is with NULL_FLAG in RDB$PROCEDURE_PARAMETERS (might be ODS dependent)
final short nullFlag = rs.getShort("NULL_FLAG");
final int dataType = getDataType(fieldType, fieldSubType, fieldScale, characterSetId);
valueBuilder
.at(2).set(getBytes(rs.getString("PROCEDURE_NAME")))
.at(3).set(getBytes(rs.getString("COLUMN_NAME")))
// TODO: Unsure if procedureColumnOut is correct, maybe procedureColumnResult, or need ODS dependent use of RDB$PROCEDURE_TYPE to decide on selectable or executable?
// TODO: ResultSet columns should not be first according to JDBC 4.1 description
.at(4).set(columnType == 0 ? PROCEDURE_COLUMN_IN : PROCEDURE_COLUMN_OUT)
.at(5).set(createInt(dataType))
.at(6).set(getBytes(getDataTypeName(fieldType, fieldSubType, fieldScale)))
.at(8).set(createInt(rs.getShort("FIELD_LENGTH")))
.at(10).set(RADIX_TEN_SHORT)
.at(11).set(nullFlag == 1 ? PROCEDURE_NO_NULLS : PROCEDURE_NULLABLE)
.at(12).set(getBytes(rs.getString("REMARKS")))
// TODO: Need to write ODS version dependent method to retrieve some of the info for indexes 13 (From 2.0 defaults for procedure parameters), 14 and 15
// TODO: Find correct value for ORDINAL_POSITION (+ order of columns and intent, see JDBC-229)
.at(17).set(createInt(rs.getInt("PARAMETER_NUMBER")))
// TODO: Find out if there is a conceptual difference with NULLABLE (idx 11)
.at(18).set(nullFlag == 1 ? NO_BYTES : YES_BYTES)
.at(19).set(valueBuilder.get(2));
switch (dataType) {
case Types.DECIMAL:
case Types.NUMERIC:
valueBuilder
.at(7).set(createInt(rs.getShort("FIELD_PRECISION")))
.at(9).set(createShort(-1 * fieldScale));
break;
case Types.CHAR:
case Types.VARCHAR:
case Types.BINARY:
case Types.VARBINARY:
short charLen = rs.getShort("CHAR_LEN");
if (!rs.wasNull()) {
valueBuilder.at(7).set(createInt(charLen));
} else {
valueBuilder.at(8).set(valueBuilder.get(8));
}
valueBuilder.at(16).set(valueBuilder.get(8));
break;
case Types.FLOAT:
valueBuilder.at(7).set(FLOAT_PRECISION);
break;
case Types.DOUBLE:
valueBuilder.at(7).set(DOUBLE_PRECISION);
break;
case Types.BIGINT:
valueBuilder
.at(7).set(BIGINT_PRECISION)
.at(9).set(SHORT_ZERO);
break;
case Types.INTEGER:
valueBuilder
.at(7).set(INTEGER_PRECISION)
.at(9).set(SHORT_ZERO);
break;
case Types.SMALLINT:
valueBuilder
.at(7).set(SMALLINT_PRECISION)
.at(9).set(SHORT_ZERO);
break;
case Types.DATE:
valueBuilder.at(7).set(DATE_PRECISION);
break;
case Types.TIME:
valueBuilder.at(7).set(TIME_PRECISION);
break;
case Types.TIMESTAMP:
valueBuilder.at(7).set(TIMESTAMP_PRECISION);
break;
case Types.BOOLEAN:
valueBuilder
.at(7).set(BOOLEAN_PRECISION)
.at(10).set(RADIX_BINARY_SHORT);
}
rows.add(valueBuilder.toRowValue(true));
} while (rs.next());
return new FBResultSet(rowDescriptor, rows);
}
}
public static final String TABLE = "TABLE";
public static final String SYSTEM_TABLE = "SYSTEM TABLE";
public static final String VIEW = "VIEW";
public static final String GLOBAL_TEMPORARY = "GLOBAL TEMPORARY";
/**
* Table types supported for Firebird 2.5 and up (will also work with 2.1 and earlier though)
*/
public static final String[] ALL_TYPES_2_5 = {TABLE, SYSTEM_TABLE, VIEW, GLOBAL_TEMPORARY};
/**
* Table types supported for Firebird 2.1 and lower
*/
public static final String[] ALL_TYPES_2_1 = {TABLE, SYSTEM_TABLE, VIEW};
@SuppressWarnings("unused")
public static final String[] ALL_TYPES = ALL_TYPES_2_5;
/**
* Retrieves a description of the tables available in the given catalog.
* Only table descriptions matching the catalog, schema, table
* name and type criteria are returned. They are ordered by
* TABLE_TYPE
, TABLE_CAT
,
* TABLE_SCHEM
and TABLE_NAME
.
*
* Each table description has the following columns:
*
* - TABLE_CAT String {@code =>} table catalog (may be
null
)
* - TABLE_SCHEM String {@code =>} table schema (may be
null
)
* - TABLE_NAME String {@code =>} table name
*
- TABLE_TYPE String {@code =>} table type. Typical types are "TABLE",
* "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY",
* "LOCAL TEMPORARY", "ALIAS", "SYNONYM".
*
- REMARKS String {@code =>} explanatory comment on the table
*
- TYPE_CAT String {@code =>} the types catalog (may be
null
)
* - TYPE_SCHEM String {@code =>} the types schema (may be
null
)
* - TYPE_NAME String {@code =>} type name (may be
null
)
* - SELF_REFERENCING_COL_NAME String {@code =>} name of the designated
* "identifier" column of a typed table (may be
null
)
* - REF_GENERATION String {@code =>} specifies how values in
* SELF_REFERENCING_COL_NAME are created. Values are
* "SYSTEM", "USER", "DERIVED". (may be
null
)
*
*
* Note: Some databases may not return information for
* all tables.
*
* @param catalog a catalog name; must match the catalog name as it
* is stored in the database; "" retrieves those without a catalog;
* null
means that the catalog name should not be used to narrow
* the search
* @param schemaPattern a schema name pattern; must match the schema name
* as it is stored in the database; "" retrieves those without a schema;
* null
means that the schema name should not be used to narrow
* the search
* @param tableNamePattern a table name pattern; must match the
* table name as it is stored in the database
* @param types a list of table types, which must be from the list of table types
* returned from {@link #getTableTypes},to include; null
returns
* all types
* @return ResultSet
- each row is a table description
* @exception SQLException if a database access error occurs
* @see #getSearchStringEscape
*/
public ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String types[])
throws SQLException {
if (hasGlobalTemporaryTables()) {
return getTables_2_5(tableNamePattern, types);
} else {
return getTables_2_1(tableNamePattern, types);
}
}
private static final String GET_TABLE_ORDER_BY = " order by 4, 3";
//@formatter:off
private static final String LEGACY_IS_TABLE = " rdb$relation_type is null and rdb$view_blr is null ";
private static final String LEGACY_IS_VIEW = " rdb$relation_type is null and rdb$view_blr is not null ";
private static final String TABLE_COLUMNS_2_5 =
" select cast(null as varchar(" + OBJECT_NAME_LENGTH + ")) as TABLE_CAT,"
+ "cast(null as varchar(" + OBJECT_NAME_LENGTH + ")) as TABLE_SCHEM,"
+ "cast(RDB$RELATION_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as TABLE_NAME,"
+ "cast(case"
+ " when rdb$relation_type = 0 or " + LEGACY_IS_TABLE + " then case when RDB$SYSTEM_FLAG = 1 then '" + SYSTEM_TABLE + "' else '" + TABLE + "' end"
+ " when rdb$relation_type = 1 or " + LEGACY_IS_VIEW + " then '" + VIEW + "'"
+ " when rdb$relation_type = 2 then '" + TABLE + "'" // external table; assume as normal table
+ " when rdb$relation_type = 3 then '" + SYSTEM_TABLE + "'" // virtual (monitoring) table: assume system
+ " when rdb$relation_type in (4, 5) then '" + GLOBAL_TEMPORARY + "'"
+ "end as varchar(31)) as TABLE_TYPE,"
+ "RDB$DESCRIPTION as REMARKS,"
+ "cast(null as varchar(" + OBJECT_NAME_LENGTH + ")) as TYPE_CAT,"
+ "cast(null as varchar(" + OBJECT_NAME_LENGTH + ")) as TYPE_SCHEM,"
+ "cast(null as varchar(31)) as TYPE_NAME,"
+ "cast(null as varchar(" + OBJECT_NAME_LENGTH + ")) as SELF_REFERENCING_COL_NAME,"
+ "cast(null as varchar(31)) as REF_GENERATION,"
+ "cast(RDB$OWNER_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as OWNER_NAME "
+ "from RDB$RELATIONS ";
//@formatter:on
/**
* Implementation of {@link #getTables(String, String, String, String[])} for Firebird 2.5 and up.
*/
private ResultSet getTables_2_5(String tableNamePattern, String[] types) throws SQLException {
if (types == null) {
types = ALL_TYPES_2_5;
}
Clause nameClause = new Clause("RDB$RELATION_NAME", tableNamePattern);
String sql = TABLE_COLUMNS_2_5;
List params;
if (nameClause.hasCondition()) {
sql = sql + " where " + nameClause.getCondition();
params = Collections.singletonList(nameClause.getValue());
} else {
params = Collections.emptyList();
}
Set typeSet = new HashSet<>(Arrays.asList(types));
if (!typeSet.containsAll(Arrays.asList(ALL_TYPES_2_5))) {
// Only construct conditions when we don't query for all
StringBuilder typeCondition = new StringBuilder(112);
if (typeSet.contains(SYSTEM_TABLE) && typeSet.contains(TABLE)) {
typeCondition.append(" (rdb$relation_type in (0, 2, 3) or " + LEGACY_IS_TABLE + ") ");
} else if (typeSet.contains(SYSTEM_TABLE)) {
typeCondition.append(" (rdb$relation_type in (0, 3) or " + LEGACY_IS_TABLE + ") and rdb$system_flag = 1 "); // We assume that external tables are never system and that virtual tables are always system
} else if (typeSet.contains(TABLE)) {
typeCondition.append(" (rdb$relation_type in (0, 2) or " + LEGACY_IS_TABLE + ") and rdb$system_flag = 0 "); // We assume that external tables are never system and that virtual tables are always system
}
if (typeSet.contains(VIEW)) {
if (typeCondition.length() > 0) {
typeCondition.append(" or ");
}
typeCondition.append(" (rdb$relation_type = 1 or " + LEGACY_IS_VIEW + ") "); // We assume (but don't check) that views are never system
}
if (typeSet.contains(GLOBAL_TEMPORARY)) {
if (typeCondition.length() > 0) {
typeCondition.append(" or ");
}
typeCondition.append(" rdb$relation_type in (4, 5) ");
}
if (typeCondition.length() == 0) {
// Requested types are unknown, query nothing
typeCondition.append(" 1 = 0 ");
}
sql = sql + (nameClause.hasCondition() ? " (" + typeCondition + ") " : " where " + typeCondition + " ");
} else if (nameClause.hasCondition()) {
// Clause condition always ends in "and"
sql += " 1=1 ";
}
sql = sql + GET_TABLE_ORDER_BY;
return doQuery(sql, params);
}
//@formatter:off
private static final String TABLE_COLUMNS_FORMAT_2_1 =
" select cast(null as varchar(" + OBJECT_NAME_LENGTH + ")) as TABLE_CAT,"
+ "cast(null as varchar(" + OBJECT_NAME_LENGTH + ")) as TABLE_SCHEM,"
+ "cast(RDB$RELATION_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as TABLE_NAME,"
+ "cast('%s' as varchar(31)) as TABLE_TYPE,"
+ "RDB$DESCRIPTION as REMARKS,"
+ "cast(null as varchar(" + OBJECT_NAME_LENGTH + ")) as TYPE_CAT,"
+ "cast(null as varchar(" + OBJECT_NAME_LENGTH + ")) as TYPE_SCHEM,"
+ "cast(null as varchar(31)) as TYPE_NAME,"
+ "cast(null as varchar(" + OBJECT_NAME_LENGTH + ")) as SELF_REFERENCING_COL_NAME,"
+ "cast(null as varchar(31)) as REF_GENERATION,"
+ "cast(RDB$OWNER_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as OWNER_NAME "
+ "from RDB$RELATIONS ";
private static final String TABLE_COLUMNS_SYSTEM_2_1 =
String.format(TABLE_COLUMNS_FORMAT_2_1, SYSTEM_TABLE);
private static final String TABLE_COLUMNS_NORMAL =
String.format(TABLE_COLUMNS_FORMAT_2_1, TABLE);
private static final String TABLE_COLUMNS_VIEW =
String.format(TABLE_COLUMNS_FORMAT_2_1, VIEW);
private static final String GET_TABLES_ALL_2_1 =
TABLE_COLUMNS_SYSTEM_2_1
+ " where ? = 'T' and RDB$SYSTEM_FLAG = 1 and rdb$view_blr is null"
+ " union"
+ TABLE_COLUMNS_NORMAL
+ " where ? = 'T' and RDB$SYSTEM_FLAG = 0 and rdb$view_blr is null"
+ " union"
+ TABLE_COLUMNS_VIEW
+ " where ? = 'T' and rdb$view_blr is not null "
+ GET_TABLE_ORDER_BY;
private static final String GET_TABLES_EXACT_2_1 =
TABLE_COLUMNS_SYSTEM_2_1
+ " where ? = 'T' and RDB$SYSTEM_FLAG = 1 and rdb$view_blr is null"
+ " and cast(RDB$RELATION_NAME as varchar(" + (OBJECT_NAME_LENGTH + 10) + ")) = ? "
+ " union"
+ TABLE_COLUMNS_NORMAL
+ " where ? = 'T' and RDB$SYSTEM_FLAG = 0 and rdb$view_blr is null"
+ " and cast(RDB$RELATION_NAME as varchar(" + (OBJECT_NAME_LENGTH + 10) + ")) = ? "
+ " union"
+ TABLE_COLUMNS_VIEW
+ " where ? = 'T' and rdb$view_blr is not null"
+ " and cast(RDB$RELATION_NAME as varchar(" + (OBJECT_NAME_LENGTH + 10) + ")) = ? "
+ GET_TABLE_ORDER_BY;
private static final String GET_TABLES_LIKE_2_1 =
TABLE_COLUMNS_SYSTEM_2_1
+ " where ? = 'T' and RDB$SYSTEM_FLAG = 1 and rdb$view_blr is null"
+ " and RDB$RELATION_NAME || '" + SPACES_31 + "' like ? escape '\\'"
+ " union"
+ TABLE_COLUMNS_NORMAL
+ " where ? = 'T' and RDB$SYSTEM_FLAG = 0 and rdb$view_blr is null"
+ " and RDB$RELATION_NAME || '" + SPACES_31 + "' like ? escape '\\'"
+ " union"
+ TABLE_COLUMNS_VIEW
+ " where ? = 'T' and rdb$view_blr is not null"
+ " and RDB$RELATION_NAME || '" + SPACES_31 + "' like ? escape '\\' "
+ GET_TABLE_ORDER_BY;
//@formatter:on
/**
* Implementation of {@link #getTables(String, String, String, String[])} for Firebird 2.1 and lower.
*/
private ResultSet getTables_2_1(String tableNamePattern, String[] types) throws SQLException {
if (types == null) {
types = ALL_TYPES_2_1;
}
String sql;
List params;
if (isAllCondition(tableNamePattern)) {
sql = GET_TABLES_ALL_2_1;
params = new ArrayList<>(3);
params.add(getWantsSystemTables(types));
params.add(getWantsTables(types));
params.add(getWantsViews(types));
} else if (hasNoWildcards(tableNamePattern)) {
tableNamePattern = stripEscape(tableNamePattern);
sql = GET_TABLES_EXACT_2_1;
params = new ArrayList<>(6);
params.add(getWantsSystemTables(types));
params.add(tableNamePattern);
params.add(getWantsTables(types));
params.add(tableNamePattern);
params.add(getWantsViews(types));
params.add(tableNamePattern);
} else {
// See also comment in Clause for explanation
tableNamePattern = tableNamePattern + SPACES_15 + "%";
sql = GET_TABLES_LIKE_2_1;
params = new ArrayList<>(6);
params.add(getWantsSystemTables(types));
params.add(tableNamePattern);
params.add(getWantsTables(types));
params.add(tableNamePattern);
params.add(getWantsViews(types));
params.add(tableNamePattern);
}
return doQuery(sql, params);
}
/**
* Gets the schema names available in this database. The results
* are ordered by schema name.
*
* The schema columns are:
*
* - TABLE_SCHEM String => schema name
*
- TABLE_CATALOG String => catalog name (may be
null
)
*
*
* @return ResultSet
- each row has a single String column that is a
* schema name
* @exception SQLException if a database access error occurs
*/
public ResultSet getSchemas() throws SQLException {
return getSchemas(null, null);
}
/**
* Gets the catalog names available in this database. The results
* are ordered by catalog name.
*
* The catalog column is:
*
* - TABLE_CAT String => catalog name
*
*
* @return ResultSet
- each row has a single String column that is a
* catalog name
* @exception SQLException if a database access error occurs
*/
public ResultSet getCatalogs() throws SQLException {
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(1, datatypeCoder)
.at(0).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_CAT", "TABLECATALOGS").addField()
.toRowDescriptor();
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
/**
* Gets the table types available in this database. The results
* are ordered by table type.
*
* The table type is:
*
* - TABLE_TYPE String => table type. Typical types are "TABLE",
* "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY",
* "LOCAL TEMPORARY", "ALIAS", "SYNONYM".
*
*
* @return ResultSet
- each row has a single String column that is a
* table type
* @exception SQLException if a database access error occurs
*/
public ResultSet getTableTypes() throws SQLException {
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(1, datatypeCoder)
.at(0).simple(SQL_VARYING, 31, "TABLE_TYPE", "TABLETYPES").addField()
.toRowDescriptor();
final String[] types = hasGlobalTemporaryTables()
? ALL_TYPES_2_5
: ALL_TYPES_2_1;
final List rows = new ArrayList<>(types.length);
for (String type : types) {
rows.add(RowValue.of(rowDescriptor, getBytes(type)));
}
return new FBResultSet(rowDescriptor, rows);
}
private boolean hasGlobalTemporaryTables() throws SQLException {
return getOdsMajorVersion() == 11 && getOdsMinorVersion() >= 2 || getOdsMajorVersion() > 11;
}
//@formatter:off
private static final String GET_COLUMNS_COMMON =
"SELECT cast(RF.RDB$RELATION_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) AS RELATION_NAME," +
"cast(RF.RDB$FIELD_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) AS FIELD_NAME," +
"F.RDB$FIELD_TYPE AS FIELD_TYPE," +
"F.RDB$FIELD_SUB_TYPE AS FIELD_SUB_TYPE," +
"F.RDB$FIELD_PRECISION AS FIELD_PRECISION," +
"F.RDB$FIELD_SCALE AS FIELD_SCALE," +
"F.RDB$FIELD_LENGTH AS FIELD_LENGTH," +
"F.RDB$CHARACTER_LENGTH AS CHAR_LEN," +
"RF.RDB$DESCRIPTION AS REMARKS," +
"RF.RDB$DEFAULT_SOURCE AS DEFAULT_SOURCE," +
"F.RDB$DEFAULT_SOURCE AS DOMAIN_DEFAULT_SOURCE," +
"RF.RDB$FIELD_POSITION + 1 AS FIELD_POSITION," +
"RF.RDB$NULL_FLAG AS NULL_FLAG," +
"F.RDB$NULL_FLAG AS SOURCE_NULL_FLAG," +
"F.RDB$COMPUTED_BLR AS COMPUTED_BLR," +
"F.RDB$CHARACTER_SET_ID,";
private static final String GET_COLUMNS_3_0_START =
GET_COLUMNS_COMMON +
"CASE WHEN RF.RDB$IDENTITY_TYPE IS NULL THEN CAST('NO' AS VARCHAR(3)) ELSE CAST('YES' AS VARCHAR(3)) END AS IS_IDENTITY," +
"CASE RF.RDB$IDENTITY_TYPE WHEN 0 THEN CAST('ALWAYS' AS VARCHAR(10)) WHEN 1 THEN CAST('BY DEFAULT' AS VARCHAR(10)) ELSE NULL END AS JB_IDENTITY_TYPE " +
"FROM RDB$RELATION_FIELDS RF," +
"RDB$FIELDS F " +
"WHERE ";
private static final String GET_COLUMNS_START =
GET_COLUMNS_COMMON +
"'NO' AS IS_IDENTITY," +
"CAST(NULL AS VARCHAR(10)) AS JB_IDENTITY_TYPE " +
"FROM RDB$RELATION_FIELDS RF," +
"RDB$FIELDS F " +
"WHERE ";
public static final String GET_COLUMNS_END = " RF.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME " +
"order by RF.RDB$RELATION_NAME, RF.RDB$FIELD_POSITION";
//@formatter:on
/**
* Retrieves a description of table columns available in
* the specified catalog.
*
* Only column descriptions matching the catalog, schema, table
* and column name criteria are returned. They are ordered by
* TABLE_CAT
,TABLE_SCHEM
,
* TABLE_NAME
, and ORDINAL_POSITION
.
*
*
Each column description has the following columns:
*
* - TABLE_CAT String => table catalog (may be
null
)
* - TABLE_SCHEM String => table schema (may be
null
)
* - TABLE_NAME String => table name
*
- COLUMN_NAME String => column name
*
- DATA_TYPE int => SQL type from java.sql.Types
*
- TYPE_NAME String => Data source dependent type name,
* for a UDT the type name is fully qualified
*
- COLUMN_SIZE int => column size.
*
- BUFFER_LENGTH is not used.
*
- DECIMAL_DIGITS int => the number of fractional digits. Null is returned for data types where
* DECIMAL_DIGITS is not applicable.
*
- NUM_PREC_RADIX int => Radix (typically either 10 or 2)
*
- NULLABLE int => is NULL allowed.
*
* - columnNoNulls - might not allow
NULL
values
* - columnNullable - definitely allows
NULL
values
* - columnNullableUnknown - nullability unknown
*
* - REMARKS String => comment describing column (may be
null
)
* - COLUMN_DEF String => default value for the column, which should be interpreted as a string when the value is enclosed in single quotes (may be
null
)
* - SQL_DATA_TYPE int => unused
*
- SQL_DATETIME_SUB int => unused
*
- CHAR_OCTET_LENGTH int => for char types the
* maximum number of bytes in the column
*
- ORDINAL_POSITION int => index of column in table
* (starting at 1)
*
- IS_NULLABLE String => ISO rules are used to determine the nullability for a column.
*
* - YES --- if the column can include NULLs
*
- NO --- if the column cannot include NULLs
*
- empty string --- if the nullability for the
* column is unknown
*
* - SCOPE_CATALOG String => catalog of table that is the scope
* of a reference attribute (
null
if DATA_TYPE isn't REF)
* - SCOPE_SCHEMA String => schema of table that is the scope
* of a reference attribute (
null
if the DATA_TYPE isn't REF)
* - SCOPE_TABLE String => table name that this the scope
* of a reference attribute (
null
if the DATA_TYPE isn't REF)
* - SOURCE_DATA_TYPE short => source type of a distinct type or user-generated
* Ref type, SQL type from java.sql.Types (
null
if DATA_TYPE
* isn't DISTINCT or user-generated REF)
* - IS_AUTOINCREMENT String => Indicates whether this column is auto incremented
*
* - YES --- if the column is auto incremented
*
- NO --- if the column is not auto incremented
*
- empty string --- if it cannot be determined whether the column is auto incremented
*
* - IS_GENERATEDCOLUMN String => Indicates whether this is a generated column
*
* - YES --- if this a generated column
*
- NO --- if this not a generated column
*
- empty string --- if it cannot be determined whether this is a generated column
*
* - JB_IS_IDENTITY String => Indicates whether this column is an identity column (NOTE: Jaybird specific column; retrieve by name!).
* There is subtle difference with the meaning of {@code IS_AUTOINCREMENT}. This column indicates if the column
* is a true identity column.
*
* - YES --- if the column is an identity column
*
- NO --- if the column is not an identity column
*
* - JB_IDENTITY_TYPE String => Type of identity column (NOTE: Jaybird specific column; retrieve by name!)
*
* - ALWAYS --- for a GENERATED ALWAYS AS IDENTITY column (not yet supported in Firebird 3!)
*
- BY DEFAULT --- for a GENERATED BY DEFAULT AS IDENTITY column
*
- null --- if the column is not an identity type (or the identity type is unknown)
*
*
*
* The COLUMN_SIZE column specifies the column size for the given column.
* For numeric data, this is the maximum precision. For character data, this is the length in characters.
* For datetime datatypes, this is the length in characters of the String representation (assuming the
* maximum allowed precision of the fractional seconds component). For binary data, this is the length in bytes. For the ROWID datatype,
* this is the length in bytes. Null is returned for data types where the
* column size is not applicable.
*
* @param catalog a catalog name; must match the catalog name as it
* is stored in the database; "" retrieves those without a catalog;
* null
means that the catalog name should not be used to narrow
* the search
* @param schemaPattern a schema name pattern; must match the schema name
* as it is stored in the database; "" retrieves those without a schema;
* null
means that the schema name should not be used to narrow
* the search
* @param tableNamePattern a table name pattern; must match the
* table name as it is stored in the database
* @param columnNamePattern a column name pattern; must match the column
* name as it is stored in the database
* @return ResultSet
- each row is a column description
* @exception SQLException if a database access error occurs
* @see #getSearchStringEscape
*/
public ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern)
throws SQLException {
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(26, datatypeCoder)
.at(0).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_CAT", "COLUMNINFO").addField()
.at(1).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_SCHEM", "COLUMNINFO").addField()
.at(2).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_NAME", "COLUMNINFO").addField()
.at(3).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "COLUMN_NAME", "COLUMNINFO").addField()
.at(4).simple(SQL_LONG, 0, "DATA_TYPE", "COLUMNINFO").addField()
.at(5).simple(SQL_VARYING | 1, 31, "TYPE_NAME", "COLUMNINFO").addField()
.at(6).simple(SQL_LONG, 0, "COLUMN_SIZE", "COLUMNINFO").addField()
.at(7).simple(SQL_LONG, 0, "BUFFER_LENGTH", "COLUMNINFO").addField()
.at(8).simple(SQL_LONG, 0, "DECIMAL_DIGITS", "COLUMNINFO").addField()
.at(9).simple(SQL_LONG, 0, "NUM_PREC_RADIX", "COLUMNINFO").addField()
.at(10).simple(SQL_LONG, 0, "NULLABLE", "COLUMNINFO").addField()
// Field in Firebird is actually a blob, using Integer.MAX_VALUE for length
.at(11).simple(SQL_VARYING | 1, Integer.MAX_VALUE, "REMARKS", "COLUMNINFO").addField()
.at(12).simple(SQL_VARYING | 1, 31, "COLUMN_DEF", "COLUMNINFO").addField()
.at(13).simple(SQL_LONG, 0, "SQL_DATA_TYPE", "COLUMNINFO").addField()
.at(14).simple(SQL_LONG, 0, "SQL_DATETIME_SUB", "COLUMNINFO").addField()
.at(15).simple(SQL_LONG, 0, "CHAR_OCTET_LENGTH", "COLUMNINFO").addField()
.at(16).simple(SQL_LONG, 0, "ORDINAL_POSITION", "COLUMNINFO").addField()
.at(17).simple(SQL_VARYING, 3, "IS_NULLABLE", "COLUMNINFO").addField()
.at(18).simple(SQL_VARYING, OBJECT_NAME_LENGTH, getScopeCatalogColumnName(), "COLUMNINFO").addField()
.at(19).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "SCOPE_SCHEMA", "COLUMNINFO").addField()
.at(20).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "SCOPE_TABLE", "COLUMNINFO").addField()
.at(21).simple(SQL_SHORT, 0, "SOURCE_DATA_TYPE", "COLUMNINFO").addField()
.at(22).simple(SQL_VARYING, 3, "IS_AUTOINCREMENT", "COLUMNINFO").addField()
.at(23).simple(SQL_VARYING, 3, "IS_GENERATEDCOLUMN", "COLUMNINFO").addField()
.at(24).simple(SQL_VARYING, 3, "JB_IS_IDENTITY", "COLUMNINFO").addField()
.at(25).simple(SQL_VARYING, 10, "JB_IDENTITY_TYPE", "COLUMNINFO").addField()
.toRowDescriptor();
Clause tableClause = new Clause("RF.RDB$RELATION_NAME", tableNamePattern);
Clause columnClause = new Clause("RF.RDB$FIELD_NAME", columnNamePattern);
String sql = hasIdentityColumns() ? GET_COLUMNS_3_0_START : GET_COLUMNS_START;
sql += tableClause.getCondition();
sql += columnClause.getCondition();
sql += GET_COLUMNS_END;
List params = new ArrayList<>(2);
if (tableClause.hasCondition()) {
params.add(tableClause.getValue());
}
if (columnClause.hasCondition()) {
params.add(columnClause.getValue());
}
try (ResultSet rs = doQuery(sql, params)) {
if (!rs.next()) {
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
final List rows = new ArrayList<>();
final RowValueBuilder valueBuilder = new RowValueBuilder(rowDescriptor);
do {
final short fieldType = rs.getShort("FIELD_TYPE");
final short fieldSubType = rs.getShort("FIELD_SUB_TYPE");
final short fieldScale = rs.getShort("FIELD_SCALE");
final int characterSetId = rs.getInt("RDB$CHARACTER_SET_ID");
final int dataType = getDataType(fieldType, fieldSubType, fieldScale, characterSetId);
valueBuilder
.at(2).set(getBytes(rs.getString("RELATION_NAME")))
.at(3).set(getBytes(rs.getString("FIELD_NAME")))
.at(4).set(createInt(dataType))
.at(5).set(getBytes(getDataTypeName(fieldType, fieldSubType, fieldScale)))
.at(9).set(RADIX_TEN);
switch (dataType) {
case Types.DECIMAL:
case Types.NUMERIC:
valueBuilder
.at(6).set(createInt(rs.getShort("FIELD_PRECISION")))
.at(8).set(createInt(fieldScale * (-1)));
break;
case Types.CHAR:
case Types.VARCHAR:
case Types.BINARY:
case Types.VARBINARY:
valueBuilder.at(15).set(createInt(rs.getShort("FIELD_LENGTH")));
short charLen = rs.getShort("CHAR_LEN");
if (!rs.wasNull()) {
valueBuilder.at(6).set(createInt(charLen));
} else {
valueBuilder.at(6).set(valueBuilder.get(15));
}
break;
case Types.FLOAT:
valueBuilder.at(6).set(FLOAT_PRECISION);
break;
case Types.DOUBLE:
valueBuilder.at(6).set(DOUBLE_PRECISION);
break;
case Types.BIGINT:
valueBuilder
.at(6).set(BIGINT_PRECISION)
.at(8).set(INT_ZERO);
break;
case Types.INTEGER:
valueBuilder
.at(6).set(INTEGER_PRECISION)
.at(8).set(INT_ZERO);
break;
case Types.SMALLINT:
valueBuilder
.at(6).set(SMALLINT_PRECISION)
.at(8).set(INT_ZERO);
break;
case Types.DATE:
valueBuilder.at(6).set(DATE_PRECISION);
break;
case Types.TIME:
valueBuilder.at(6).set(TIME_PRECISION);
break;
case Types.TIMESTAMP:
valueBuilder.at(6).set(TIMESTAMP_PRECISION);
break;
case Types.BOOLEAN:
valueBuilder
.at(6).set(BOOLEAN_PRECISION)
.at(9).set(RADIX_BINARY);
break;
}
final short nullFlag = rs.getShort("NULL_FLAG");
final short sourceNullFlag = rs.getShort("SOURCE_NULL_FLAG");
valueBuilder.at(10).set(nullFlag == 1 || sourceNullFlag == 1
? COLUMN_NO_NULLS
: COLUMN_NULLABLE)
.at(11).set(getBytes(rs.getString("REMARKS")));
String column_def = rs.getString("DEFAULT_SOURCE");
if (column_def == null) {
column_def = rs.getString("DOMAIN_DEFAULT_SOURCE");
}
if (column_def != null) {
// TODO This looks suspicious (what if it contains default)
int defaultPos = column_def.toUpperCase().indexOf("DEFAULT");
if (defaultPos >= 0)
column_def = column_def.substring(7).trim();
valueBuilder.at(12).set(getBytes(column_def));
}
valueBuilder
.at(16).set(createInt(rs.getInt("FIELD_POSITION")))
.at(17).set(nullFlag == 1 || sourceNullFlag == 1 ? NO_BYTES : YES_BYTES);
final boolean isIdentity = Objects.equals("YES", rs.getString("IS_IDENTITY"));
if (isIdentity) {
// identity column is an autoincrement for sure
valueBuilder.at(22).set(YES_BYTES);
} else {
switch (dataType) {
case Types.INTEGER:
case Types.TINYINT:
case Types.BIGINT:
case Types.SMALLINT:
// Could be autoincrement by trigger, but we simply don't know
valueBuilder.at(22).set(EMPTY_STRING_BYTES);
break;
case Types.NUMERIC:
case Types.DECIMAL:
if (fieldScale == 0) {
// Could be autoincrement by trigger, but we simply don't know
valueBuilder.at(22).set(EMPTY_STRING_BYTES);
} else {
// Scaled NUMERIC/DECIMAL: definitely not autoincrement
valueBuilder.at(22).set(NO_BYTES);
}
break;
default:
// All other types are never autoincrement
valueBuilder.at(22).set(NO_BYTES);
}
}
// Retrieving COMPUTED_BLR to check if it was NULL or not
rs.getString("COMPUTED_BLR");
// consider identity columns to be generated columns
boolean isGenerated = !rs.wasNull() || isIdentity;
valueBuilder.at(23).set(isGenerated ? YES_BYTES : NO_BYTES);
valueBuilder.at(24).set(isIdentity ? YES_BYTES : NO_BYTES);
valueBuilder.at(25).set(getBytes(rs.getString("JB_IDENTITY_TYPE")));
rows.add(valueBuilder.toRowValue(true));
} while (rs.next());
return new FBResultSet(rowDescriptor, rows);
}
}
private boolean hasIdentityColumns() throws SQLException {
return getOdsMajorVersion() >= 12;
}
/**
* Gets the name of the correct scope catalog column name based on the JDBC version for use in
* {@link #getColumns(String, String, String, String)}.
*
* Rationale: in older versions of JDBC this column was misspelled as "SCOPE_CATLOG"
instead of
* "SCOPE_CATALOG"
. This was fixed in JDBC 4.1
*
*
* @return The scope catalog name.
*/
private String getScopeCatalogColumnName() {
final String scopeCatalog;
if (getJDBCMajorVersion() > 4 || getJDBCMajorVersion() == 4 && getJDBCMinorVersion() >= 1) {
scopeCatalog = "SCOPE_CATALOG";
} else {
scopeCatalog = "SCOPE_CATLOG";
}
return scopeCatalog;
}
private static final int smallint_type = 7;
private static final int integer_type = 8;
private static final int quad_type = 9;
private static final int float_type = 10;
private static final int d_float_type = 11;
private static final int date_type = 12;
private static final int time_type = 13;
private static final int char_type = 14;
private static final int int64_type = 16;
private static final int double_type = 27;
private static final int timestamp_type = 35;
private static final int varchar_type = 37;
// private static final int cstring_type = 40;
private static final int blob_type = 261;
private static final short boolean_type = 23;
private static int getDataType(int fieldType, int fieldSubType, int fieldScale, int characterSetId) {
// TODO Preserved for backwards compatibility, is this really necessary?
if (fieldType == blob_type && fieldSubType > 1) {
return Types.OTHER;
}
final int jdbcType = JdbcTypeConverter.fromMetaDataToJdbcType(fieldType, fieldSubType, fieldScale);
// Metadata from RDB$ tables does not contain character set in subtype, manual fixup
if (characterSetId == CS_BINARY) {
if (jdbcType == Types.CHAR) {
return Types.BINARY;
} else if (jdbcType == Types.VARCHAR) {
return Types.VARBINARY;
}
}
return jdbcType;
}
// TODO Unify with AbstractFieldMetadata
private static String getDataTypeName(int sqltype, int sqlsubtype, int sqlscale) {
switch (sqltype) {
case smallint_type:
if (sqlsubtype == SUBTYPE_NUMERIC || (sqlsubtype == 0 && sqlscale < 0))
return "NUMERIC";
else if (sqlsubtype == SUBTYPE_DECIMAL)
return "DECIMAL";
else
return "SMALLINT";
case integer_type:
if (sqlsubtype == SUBTYPE_NUMERIC || (sqlsubtype == 0 && sqlscale < 0))
return "NUMERIC";
else if (sqlsubtype == SUBTYPE_DECIMAL)
return "DECIMAL";
else
return "INTEGER";
case double_type:
case d_float_type:
if (sqlsubtype == SUBTYPE_NUMERIC || (sqlsubtype == 0 && sqlscale < 0))
return "NUMERIC";
else if (sqlsubtype == SUBTYPE_DECIMAL)
return "DECIMAL";
else
return "DOUBLE PRECISION";
case float_type:
return "FLOAT";
case char_type:
return "CHAR";
case varchar_type:
return "VARCHAR";
case timestamp_type:
return "TIMESTAMP";
case time_type:
return "TIME";
case date_type:
return "DATE";
case int64_type:
if (sqlsubtype == SUBTYPE_NUMERIC || (sqlsubtype == 0 && sqlscale < 0))
return "NUMERIC";
else if (sqlsubtype == SUBTYPE_DECIMAL)
return "DECIMAL";
else
return "BIGINT";
case blob_type:
if (sqlsubtype < 0)
// TODO Include actual subtype?
return "BLOB SUB_TYPE <0";
else if (sqlsubtype == 0)
return "BLOB SUB_TYPE 0";
else if (sqlsubtype == 1)
return "BLOB SUB_TYPE 1";
else
return "BLOB SUB_TYPE " + sqlsubtype;
case quad_type:
return "ARRAY";
case boolean_type:
return "BOOLEAN";
default:
return "NULL";
}
}
private static final String GET_COLUMN_PRIVILEGES_START = "select "
/*+ "null as TABLE_CAT,"
+ "null as TABLE_SCHEM,"*/
+ "cast(RF.RDB$RELATION_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as TABLE_NAME,"
+ "cast(RF.RDB$FIELD_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as COLUMN_NAME,"
+ "cast(UP.RDB$GRANTOR as varchar(" + OBJECT_NAME_LENGTH + ")) as GRANTOR,"
+ "cast(UP.RDB$USER as varchar(" + OBJECT_NAME_LENGTH + ")) as GRANTEE,"
+ "cast(UP.RDB$PRIVILEGE as varchar(6)) as PRIVILEGE,"
+ "UP.RDB$GRANT_OPTION as IS_GRANTABLE "
+ "from "
+ "RDB$RELATION_FIELDS RF,"
+ "RDB$FIELDS F,"
+ "RDB$USER_PRIVILEGES UP "
+ "where "
+ "RF.RDB$RELATION_NAME = UP.RDB$RELATION_NAME and "
+ "RF.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME and "
+ "(UP.RDB$FIELD_NAME is null or "
+ "UP.RDB$FIELD_NAME = RF.RDB$FIELD_NAME) and "
+ "CAST(UP.RDB$RELATION_NAME AS VARCHAR(" + (OBJECT_NAME_LENGTH + 10) + ")) = ? and ((";
private static final String GET_COLUMN_PRIVILEGES_END = " UP.RDB$OBJECT_TYPE = 0) or "
+ "(RF.RDB$FIELD_NAME is null and UP.RDB$OBJECT_TYPE = 0)) "
+ "order by 2,5 ";
private static final Map PRIVILEGE_MAPPING;
static {
Map tempMapping = new HashMap<>(7);
tempMapping.put("A", getBytes("ALL"));
tempMapping.put("S", getBytes("SELECT"));
tempMapping.put("D", getBytes("DELETE"));
tempMapping.put("I", getBytes("INSERT"));
tempMapping.put("U", getBytes("UPDATE"));
tempMapping.put("R", getBytes("REFERENCE")); // TODO: JDBC apidoc specifies REFRENCES (yes: typo and + S)
tempMapping.put("M", getBytes("MEMBEROF"));
PRIVILEGE_MAPPING = Collections.unmodifiableMap(tempMapping);
}
/**
* Maps the (one character) Firebird privilege to the equivalent JDBC privilege.
*
* @param firebirdPrivilege Firebird privilege
* @return JDBC privilege encoded as byte array
*/
private static byte[] mapPrivilege(String firebirdPrivilege) {
return PRIVILEGE_MAPPING.get(firebirdPrivilege);
}
/**
* Gets a description of the access rights for a table's columns.
*
* Only privileges matching the column name criteria are
* returned. They are ordered by COLUMN_NAME and PRIVILEGE.
*
*
Each privilige description has the following columns:
*
* - TABLE_CAT String => table catalog (may be null)
*
- TABLE_SCHEM String => table schema (may be null)
*
- TABLE_NAME String => table name
*
- COLUMN_NAME String => column name
*
- GRANTOR => grantor of access (may be null)
*
- GRANTEE String => grantee of access
*
- PRIVILEGE String => name of access (SELECT,
* INSERT, UPDATE, REFRENCES, ...)
*
- IS_GRANTABLE String => "YES" if grantee is permitted
* to grant to others; "NO" if not; null if unknown
*
*
* @param catalog a catalog name; "" retrieves those without a
* catalog; null means drop catalog name from the selection criteria
* @param schema a schema name; "" retrieves those without a schema
* @param table a table name
* @param columnNamePattern a column name pattern
* @return ResultSet
- each row is a column privilege description
* @exception SQLException if a database access error occurs
* @see #getSearchStringEscape
*/
public ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern)
throws SQLException {
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(8, datatypeCoder)
.at(0).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_CAT", "COLUMNPRIV").addField()
.at(1).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_SCHEM", "COLUMNPRIV").addField()
.at(2).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_NAME", "COLUMNPRIV").addField()
.at(3).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "COLUMN_NAME", "COLUMNPRIV").addField()
.at(4).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "GRANTOR", "COLUMNPRIV").addField()
.at(5).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "GRANTEE", "COLUMNPRIV").addField()
.at(6).simple(SQL_VARYING, 31, "PRIVILEGE", "COLUMNPRIV").addField()
.at(7).simple(SQL_VARYING, 31, "IS_GRANTABLE", "COLUMNPRIV").addField()
.toRowDescriptor();
Clause columnClause = new Clause("RF.RDB$FIELD_NAME", columnNamePattern);
String sql = GET_COLUMN_PRIVILEGES_START;
sql += columnClause.getCondition();
sql += GET_COLUMN_PRIVILEGES_END;
List params = new ArrayList<>(2);
params.add(table);
if (columnClause.hasCondition()) {
params.add(columnClause.getValue());
}
try (ResultSet rs = doQuery(sql, params)) {
// return empty result set
if (!rs.next()) {
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
final List rows = new ArrayList<>();
final RowValueBuilder valueBuilder = new RowValueBuilder(rowDescriptor);
do {
rows.add(valueBuilder
.at(2).set(getBytes(rs.getString("TABLE_NAME")))
.at(3).set(getBytes(rs.getString("COLUMN_NAME")))
.at(4).set(getBytes(rs.getString("GRANTOR")))
.at(5).set(getBytes(rs.getString("GRANTEE")))
.at(6).set(mapPrivilege(rs.getString("PRIVILEGE")))
.at(7).set(rs.getShort("IS_GRANTABLE") == 0 ? NO_BYTES : YES_BYTES)
.toRowValue(true)
);
} while (rs.next());
return new FBResultSet(rowDescriptor, rows);
}
}
private static final String GET_TABLE_PRIVILEGES_START = "select "
/*+ " null as TABLE_CAT, "
+ " null as TABLE_SCHEM,"*/
+ "cast(RDB$RELATION_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as TABLE_NAME,"
+ "cast(RDB$GRANTOR as varchar(" + OBJECT_NAME_LENGTH + ")) as GRANTOR,"
+ "cast(RDB$USER as varchar(" + OBJECT_NAME_LENGTH + ")) as GRANTEE,"
+ "cast(RDB$PRIVILEGE as varchar(6)) as PRIVILEGE,"
+ "RDB$GRANT_OPTION as IS_GRANTABLE "
+ "from"
+ " RDB$USER_PRIVILEGES "
+ "where ";
private static final String GET_TABLE_PRIVILEGES_END = " RDB$OBJECT_TYPE = 0 and"
+ " RDB$FIELD_NAME is null "
+ "order by 1, 4";
/**
* Gets a description of the access rights for each table available
* in a catalog. Note that a table privilege applies to one or
* more columns in the table. It would be wrong to assume that
* this priviledge applies to all columns (this may be true for
* some systems but is not true for all.)
*
* Only privileges matching the schema and table name
* criteria are returned. They are ordered by TABLE_SCHEM,
* TABLE_NAME, and PRIVILEGE.
*
*
Each privilige description has the following columns:
*
* - TABLE_CAT String => table catalog (may be null)
*
- TABLE_SCHEM String => table schema (may be null)
*
- TABLE_NAME String => table name
*
- GRANTOR => grantor of access (may be null)
*
- GRANTEE String => grantee of access
*
- PRIVILEGE String => name of access (SELECT,
* INSERT, UPDATE, REFRENCES, ...)
*
- IS_GRANTABLE String => "YES" if grantee is permitted
* to grant to others; "NO" if not; null if unknown
*
*
* @param catalog a catalog name; "" retrieves those without a
* catalog; null means drop catalog name from the selection criteria
* @param schemaPattern a schema name pattern; "" retrieves those
* without a schema
* @param tableNamePattern a table name pattern
* @return ResultSet
- each row is a table privilege description
* @exception SQLException if a database access error occurs
* @see #getSearchStringEscape
*/
public ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern)
throws SQLException {
final RowDescriptor rowDescriptor = buildTablePrivilegeRSMetaData();
Clause tableClause = new Clause("RDB$RELATION_NAME", tableNamePattern);
String sql = GET_TABLE_PRIVILEGES_START;
sql += tableClause.getCondition();
sql += GET_TABLE_PRIVILEGES_END;
List params = tableClause.hasCondition()
? Collections.singletonList(tableClause.getValue())
: Collections.emptyList();
try (ResultSet rs = doQuery(sql, params)) {
// if nothing found, return an empty result set
if (!rs.next()) {
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
return processTablePrivileges(rowDescriptor, rs);
}
}
protected final RowDescriptor buildTablePrivilegeRSMetaData() {
return new RowDescriptorBuilder(7, datatypeCoder)
.at(0).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_CAT", "TABLEPRIV").addField()
.at(1).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_SCHEM", "TABLEPRIV").addField()
.at(2).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_NAME", "TABLEPRIV").addField()
.at(3).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "GRANTOR", "TABLEPRIV").addField()
.at(4).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "GRANTEE", "TABLEPRIV").addField()
.at(5).simple(SQL_VARYING, 31, "PRIVILEGE", "TABLEPRIV").addField()
.at(6).simple(SQL_VARYING, 31, "IS_GRANTABLE", "TABLEPRIV").addField()
.toRowDescriptor();
}
protected final FBResultSet processTablePrivileges(final RowDescriptor rowDescriptor, final ResultSet fbTablePrivileges) throws SQLException {
final List rows = new ArrayList<>();
final RowValueBuilder valueBuilder = new RowValueBuilder(rowDescriptor);
do {
rows.add(valueBuilder
.at(2).set(getBytes(fbTablePrivileges.getString("TABLE_NAME")))
.at(3).set(getBytes(fbTablePrivileges.getString("GRANTOR")))
.at(4).set(getBytes(fbTablePrivileges.getString("GRANTEE")))
.at(5).set(mapPrivilege(fbTablePrivileges.getString("PRIVILEGE")))
.at(6).set(fbTablePrivileges.getShort("IS_GRANTABLE") == 0 ? NO_BYTES : YES_BYTES)
.toRowValue(true)
);
} while (fbTablePrivileges.next());
return new FBResultSet(rowDescriptor, rows);
}
private static final String GET_BEST_ROW_IDENT =
"SELECT " +
"CAST(rf.rdb$field_name AS varchar(" + OBJECT_NAME_LENGTH + ")) AS column_name," +
"f.rdb$field_type AS field_type," +
"f.rdb$field_sub_type AS field_sub_type," +
"f.rdb$field_scale AS field_scale," +
"f.rdb$field_precision AS field_precision," +
"f.RDB$CHARACTER_SET_ID " +
"FROM rdb$relation_constraints rc " +
"INNER JOIN rdb$index_segments idx ON idx.rdb$index_name = rc.rdb$index_name " +
"INNER JOIN rdb$relation_fields rf ON rf.rdb$field_name = idx.rdb$field_name " +
" AND rf.rdb$relation_name = rc.rdb$relation_name " +
"INNER JOIN rdb$fields f ON f.rdb$field_name = rf.rdb$field_source " +
"WHERE " +
"CAST(rc.rdb$relation_name AS VARCHAR(" + (OBJECT_NAME_LENGTH + 10) + ")) = ? " +
"AND rc.rdb$constraint_type = 'PRIMARY KEY'";
/**
* Gets a description of a table's optimal set of columns that
* uniquely identifies a row. They are ordered by SCOPE.
*
* Each column description has the following columns:
*
* - SCOPE short => actual scope of result
*
* - bestRowTemporary - very temporary, while using row
*
- bestRowTransaction - valid for remainder of current transaction
*
- bestRowSession - valid for remainder of current session
*
* - COLUMN_NAME String => column name
*
- DATA_TYPE short => SQL data type from java.sql.Types
*
- TYPE_NAME String => Data source dependent type name,
* for a UDT the type name is fully qualified
*
- COLUMN_SIZE int => precision
*
- BUFFER_LENGTH int => not used
*
- DECIMAL_DIGITS short => scale
*
- PSEUDO_COLUMN short => is this a pseudo column
* like an Oracle ROWID
*
* - bestRowUnknown - may or may not be pseudo column
*
- bestRowNotPseudo - is NOT a pseudo column
*
- bestRowPseudo - is a pseudo column
*
*
*
* @param catalog a catalog name; "" retrieves those without a
* catalog; null means drop catalog name from the selection criteria
* @param schema a schema name; "" retrieves those without a schema
* @param table a table name
* @param scope the scope of interest; use same values as SCOPE
* @param nullable include columns that are nullable?
* @return ResultSet
- each row is a column description
* @exception SQLException if a database access error occurs
*/
public ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable)
throws SQLException {
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(8, datatypeCoder)
.at(0).simple(SQL_SHORT, 0, "SCOPE", "ROWIDENTIFIER").addField()
.at(1).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "COLUMN_NAME", "ROWIDENTIFIER").addField()
.at(2).simple(SQL_SHORT, 0, "DATA_TYPE", "ROWIDENTIFIER").addField()
.at(3).simple(SQL_VARYING, 31, "TYPE_NAME", "ROWIDENTIFIER").addField()
.at(4).simple(SQL_LONG, 0, "COLUMN_SIZE", "ROWIDENTIFIER").addField()
.at(5).simple(SQL_LONG, 0, "BUFFER_LENGTH", "ROWIDENTIFIER").addField()
.at(6).simple(SQL_SHORT, 0, "DECIMAL_DIGITS", "ROWIDENTIFIER").addField()
.at(7).simple(SQL_SHORT, 0, "PSEUDO_COLUMN", "ROWIDENTIFIER").addField()
.toRowDescriptor();
List rows;
final RowValueBuilder rowValueBuilder = new RowValueBuilder(rowDescriptor);
// Check if table exists, need to escape as getTables takes a pattern
String quoteLikeTable = escapeWildcards(table);
try (ResultSet tables = getTables(catalog, schema, quoteLikeTable, null)) {
if (!tables.next()) {
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
rows = getPrimaryKeyIdentifier(tables.getString(3), scope, rowValueBuilder);
}
// if no primary key exists, add RDB$DB_KEY as pseudo-column
// TODO Check actual column type of RDB$DB_KEY
if (rows.size() == 0) {
rows.add(rowValueBuilder
.at(0).set(createShort(scope))
.at(1).set(getBytes("RDB$DB_KEY"))
.at(2).set(createShort(getDataType(char_type, 0, 0, CS_BINARY)))
.at(3).set(getBytes(getDataTypeName(char_type, 0, 0)))
.at(4).set(createInt(0))
.at(6).set(createShort(0))
.at(7).set(createShort(bestRowPseudo))
.toRowValue(true)
);
}
return new FBResultSet(rowDescriptor, rows);
}
/**
* Get primary key of the table as best row identifier.
*
* @param table name of the table.
* @param scope scope, we just include it in the result set.
* @param valueBuilder Builder for row values
*
* @return list of result set values, when size is 0, no primary key has
* been defined for a table.
*
* @throws SQLException if something went wrong.
*/
private List getPrimaryKeyIdentifier(String table, int scope, final RowValueBuilder valueBuilder) throws SQLException {
try (ResultSet rs = doQuery(GET_BEST_ROW_IDENT, Collections.singletonList(table))) {
final List rows = new ArrayList<>();
while (rs.next()) {
short fieldType = rs.getShort("FIELD_TYPE");
short fieldSubType = rs.getShort("FIELD_SUB_TYPE");
short fieldScale = rs.getShort("FIELD_SCALE");
int characterSetId = rs.getInt("RDB$CHARACTER_SET_ID");
rows.add(valueBuilder
.at(0).set(createShort(scope))
.at(1).set(getBytes(rs.getString("COLUMN_NAME")))
.at(2).set(createShort(getDataType(fieldType, fieldSubType, fieldScale, characterSetId)))
.at(3).set(getBytes(getDataTypeName(fieldType, fieldSubType, fieldScale)))
.at(4).set(createInt(rs.getInt("FIELD_PRECISION")))
.at(6).set(createShort(fieldScale))
.at(7).set(createShort(bestRowNotPseudo))
.toRowValue(true)
);
}
return rows;
}
}
/**
* Gets a description of a table's columns that are automatically
* updated when any value in a row is updated. They are
* unordered.
*
* Each column description has the following columns:
*
* - SCOPE short => is not used
*
- COLUMN_NAME String => column name
*
- DATA_TYPE short => SQL data type from java.sql.Types
*
- TYPE_NAME String => Data source dependent type name
*
- COLUMN_SIZE int => precision
*
- BUFFER_LENGTH int => length of column value in bytes
*
- DECIMAL_DIGITS short => scale
*
- PSEUDO_COLUMN short => is this a pseudo column
* like an Oracle ROWID
*
* - versionColumnUnknown - may or may not be pseudo column
*
- versionColumnNotPseudo - is NOT a pseudo column
*
- versionColumnPseudo - is a pseudo column
*
*
*
* @param catalog a catalog name; "" retrieves those without a
* catalog; null means drop catalog name from the selection criteria
* @param schema a schema name; "" retrieves those without a schema
* @param table a table name
* @return ResultSet
- each row is a column description
* @exception SQLException if a database access error occurs
*/
public ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException {
// TODO Return FB 3 RDB$RECORD_VERSION
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(8, datatypeCoder)
.at(0).simple(SQL_SHORT, 0, "SCOPE", "VERSIONCOL").addField()
.at(1).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "COLUMN_NAME", "VERSIONCOL").addField()
.at(2).simple(SQL_SHORT, 0, "DATA_TYPE", "VERSIONCOL").addField()
.at(3).simple(SQL_VARYING, 31, "TYPE_NAME", "VERSIONCOL").addField()
.at(4).simple(SQL_LONG, 0, "COLUMN_SIZE", "VERSIONCOL").addField()
.at(5).simple(SQL_LONG, 0, "BUFFER_LENGTH", "VERSIONCOL").addField()
.at(6).simple(SQL_SHORT, 0, "DECIMAL_DIGITS", "VERSIONCOL").addField()
.at(7).simple(SQL_SHORT, 0, "PSEUDO_COLUMN", "VERSIONCOL").addField()
.toRowDescriptor();
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
private static final String GET_PRIMARY_KEYS = "select "
/*+ " null as TABLE_CAT, "
+ " null as TABLE_SCHEM, "*/
+ "cast(RC.RDB$RELATION_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as TABLE_NAME,"
+ "cast(ISGMT.RDB$FIELD_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as COLUMN_NAME,"
+ "CAST((ISGMT.RDB$FIELD_POSITION + 1) as SMALLINT) as KEY_SEQ,"
+ "cast(RC.RDB$CONSTRAINT_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as PK_NAME "
+ "from "
+ "RDB$RELATION_CONSTRAINTS RC "
+ "INNER JOIN RDB$INDEX_SEGMENTS ISGMT ON RC.RDB$INDEX_NAME = ISGMT.RDB$INDEX_NAME "
+ "where CAST(RC.RDB$RELATION_NAME AS VARCHAR(" + (OBJECT_NAME_LENGTH + 10) + ")) = ? and "
+ "RC.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY' "
+ "order by ISGMT.RDB$FIELD_NAME ";
/**
* Gets a description of a table's primary key columns. They
* are ordered by COLUMN_NAME.
*
* Each primary key column description has the following columns:
*
* - TABLE_CAT String => table catalog (may be null)
*
- TABLE_SCHEM String => table schema (may be null)
*
- TABLE_NAME String => table name
*
- COLUMN_NAME String => column name
*
- KEY_SEQ short => sequence number within primary key
*
- PK_NAME String => primary key name (may be null)
*
*
* @param catalog a catalog name; "" retrieves those without a
* catalog; null means drop catalog name from the selection criteria
* @param schema a schema name; "" retrieves those
* without a schema
* @param table a table name
* @return ResultSet
- each row is a primary key column description
* @exception SQLException if a database access error occurs
*/
public ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException {
RowDescriptor rowDescriptor = new RowDescriptorBuilder(6, datatypeCoder)
.at(0).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_CAT", "COLUMNINFO").addField()
.at(1).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_SCHEM", "COLUMNINFO").addField()
.at(2).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_NAME", "COLUMNINFO").addField()
.at(3).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "COLUMN_NAME", "COLUMNINFO").addField()
.at(4).simple(SQL_SHORT, 0, "KEY_SEQ", "COLUMNINFO").addField()
.at(5).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PK_NAME", "COLUMNINFO").addField()
.toRowDescriptor();
List params = Collections.singletonList(table);
try (ResultSet rs = doQuery(GET_PRIMARY_KEYS, params)) {
// if nothing found, return empty result set
if (!rs.next()) {
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
final List rows = new ArrayList<>();
final RowValueBuilder valueBuilder = new RowValueBuilder(rowDescriptor);
do {
rows.add(valueBuilder
.at(2).set(getBytes(rs.getString("TABLE_NAME")))
.at(3).set(getBytes(rs.getString("COLUMN_NAME")))
.at(4).set(createShort(rs.getShort("KEY_SEQ")))
.at(5).set(getBytes(rs.getString("PK_NAME")))
.toRowValue(true)
);
} while (rs.next());
return new FBResultSet(rowDescriptor, rows);
}
}
private static final String GET_IMPORTED_KEYS = "select "
/*+" null as PKTABLE_CAT "
+" ,null as PKTABLE_SCHEM "*/
+"cast(PK.RDB$RELATION_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as PKTABLE_NAME"
+",cast(ISP.RDB$FIELD_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as PKCOLUMN_NAME"
/*+" ,null as FKTABLE_CAT "
+" ,null as FKTABLE_SCHEM "*/
+",cast(FK.RDB$RELATION_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as FKTABLE_NAME"
+",cast(ISF.RDB$FIELD_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as FKCOLUMN_NAME"
+",CAST((ISP.RDB$FIELD_POSITION + 1) as SMALLINT) as KEY_SEQ"
+",cast(RC.RDB$UPDATE_RULE as varchar(11)) as UPDATE_RULE"
+",cast(RC.RDB$DELETE_RULE as varchar(11)) as DELETE_RULE"
+",cast(PK.RDB$CONSTRAINT_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as PK_NAME"
+",cast(FK.RDB$CONSTRAINT_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as FK_NAME "
/*+" ,null as DEFERRABILITY "*/
+"from "
+"RDB$RELATION_CONSTRAINTS PK"
+",RDB$RELATION_CONSTRAINTS FK"
+",RDB$REF_CONSTRAINTS RC"
+",RDB$INDEX_SEGMENTS ISP"
+",RDB$INDEX_SEGMENTS ISF "
+"WHERE CAST(FK.RDB$RELATION_NAME AS VARCHAR(" + (OBJECT_NAME_LENGTH + 10) + ")) = ? and "
+" FK.RDB$CONSTRAINT_NAME = RC.RDB$CONSTRAINT_NAME "
+"and PK.RDB$CONSTRAINT_NAME = RC.RDB$CONST_NAME_UQ "
+"and ISP.RDB$INDEX_NAME = PK.RDB$INDEX_NAME "
+"and ISF.RDB$INDEX_NAME = FK.RDB$INDEX_NAME "
+"and ISP.RDB$FIELD_POSITION = ISF.RDB$FIELD_POSITION "
+"order by 1, 5 ";
private static final Map ACTION_MAPPING;
static {
Map tempMap = new HashMap<>();
tempMap.put("NO ACTION", IMPORTED_KEY_NO_ACTION);
tempMap.put("RESTRICT", IMPORTED_KEY_NO_ACTION);
tempMap.put("CASCADE", IMPORTED_KEY_CASCADE);
tempMap.put("SET NULL", IMPORTED_KEY_SET_NULL);
tempMap.put("SET DEFAULT", IMPORTED_KEY_SET_DEFAULT);
ACTION_MAPPING = Collections.unmodifiableMap(tempMap);
}
/**
* Maps the Firebird action name to the equivalent JDBC action.
*
* @param fbAction Firebird action
* @return JDBC action encoded as byte array
*/
private static byte[] mapAction(String fbAction) {
return ACTION_MAPPING.get(fbAction);
}
/**
* Gets a description of the primary key columns that are
* referenced by a table's foreign key columns (the primary keys
* imported by a table). They are ordered by PKTABLE_CAT,
* PKTABLE_SCHEM, PKTABLE_NAME, and KEY_SEQ.
*
* Each primary key column description has the following columns:
*
* - PKTABLE_CAT String => primary key table catalog
* being imported (may be null)
*
- PKTABLE_SCHEM String => primary key table schema
* being imported (may be null)
*
- PKTABLE_NAME String => primary key table name
* being imported
*
- PKCOLUMN_NAME String => primary key column name
* being imported
*
- FKTABLE_CAT String => foreign key table catalog (may be null)
*
- FKTABLE_SCHEM String => foreign key table schema (may be null)
*
- FKTABLE_NAME String => foreign key table name
*
- FKCOLUMN_NAME String => foreign key column name
*
- KEY_SEQ short => sequence number within foreign key
*
- UPDATE_RULE short => What happens to
* foreign key when primary is updated:
*
* - importedNoAction - do not allow update of primary
* key if it has been imported
*
- importedKeyCascade - change imported key to agree
* with primary key update
*
- importedKeySetNull - change imported key to NULL if
* its primary key has been updated
*
- importedKeySetDefault - change imported key to default values
* if its primary key has been updated
*
- importedKeyRestrict - same as importedKeyNoAction
* (for ODBC 2.x compatibility)
*
* - DELETE_RULE short => What happens to
* the foreign key when primary is deleted.
*
* - importedKeyNoAction - do not allow delete of primary
* key if it has been imported
*
- importedKeyCascade - delete rows that import a deleted key
*
- importedKeySetNull - change imported key to NULL if
* its primary key has been deleted
*
- importedKeyRestrict - same as importedKeyNoAction
* (for ODBC 2.x compatibility)
*
- importedKeySetDefault - change imported key to default if
* its primary key has been deleted
*
* - FK_NAME String => foreign key name (may be null)
*
- PK_NAME String => primary key name (may be null)
*
- DEFERRABILITY short => can the evaluation of foreign key
* constraints be deferred until commit
*
* - importedKeyInitiallyDeferred - see SQL92 for definition
*
- importedKeyInitiallyImmediate - see SQL92 for definition
*
- importedKeyNotDeferrable - see SQL92 for definition
*
*
*
* @param catalog a catalog name; "" retrieves those without a
* catalog; null means drop catalog name from the selection criteria
* @param schema a schema name; "" retrieves those
* without a schema
* @param table a table name
* @return ResultSet
- each row is a primary key column description
* @exception SQLException if a database access error occurs
* @see #getExportedKeys
*/
public ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException {
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(14, datatypeCoder)
.at(0).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PKTABLE_CAT", "COLUMNINFO").addField()
.at(1).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PKTABLE_SCHEM", "COLUMNINFO").addField()
.at(2).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PKTABLE_NAME", "COLUMNINFO").addField()
.at(3).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PKCOLUMN_NAME", "COLUMNINFO").addField()
.at(4).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FKTABLE_CAT", "COLUMNINFO").addField()
.at(5).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FKTABLE_SCHEM", "COLUMNINFO").addField()
.at(6).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FKTABLE_NAME", "COLUMNINFO").addField()
.at(7).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FKCOLUMN_NAME", "COLUMNINFO").addField()
.at(8).simple(SQL_SHORT, 0, "KEY_SEQ", "COLUMNINFO").addField()
.at(9).simple(SQL_SHORT, 0, "UPDATE_RULE", "COLUMNINFO").addField()
.at(10).simple(SQL_SHORT, 0, "DELETE_RULE", "COLUMNINFO").addField()
.at(11).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FK_NAME", "COLUMNINFO").addField()
.at(12).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PK_NAME", "COLUMNINFO").addField()
.at(13).simple(SQL_SHORT, 0, "DEFERRABILITY", "COLUMNINFO").addField()
.toRowDescriptor();
List params = Collections.singletonList(table);
try (ResultSet rs = doQuery(GET_IMPORTED_KEYS, params)) {
// if nothing found, return an empty result set
if (!rs.next()) {
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
final List rows = new ArrayList<>();
final RowValueBuilder valueBuilder = new RowValueBuilder(rowDescriptor);
do {
rows.add(valueBuilder
.at(2).set(getBytes(rs.getString("PKTABLE_NAME")))
.at(3).set(getBytes(rs.getString("PKCOLUMN_NAME")))
.at(6).set(getBytes(rs.getString("FKTABLE_NAME")))
.at(7).set(getBytes(rs.getString("FKCOLUMN_NAME")))
.at(8).set(createShort(rs.getShort("KEY_SEQ")))
.at(9).set(mapAction(rs.getString("UPDATE_RULE")))
.at(10).set(mapAction(rs.getString("DELETE_RULE")))
.at(11).set(getBytes(rs.getString("FK_NAME")))
.at(12).set(getBytes(rs.getString("PK_NAME")))
.at(13).set(IMPORTED_KEY_NOT_DEFERRABLE)
.toRowValue(true)
);
} while (rs.next());
return new FBResultSet(rowDescriptor, rows);
}
}
private static final String GET_EXPORTED_KEYS = "select "
/*+" null as PKTABLE_CAT "
+" ,null as PKTABLE_SCHEM "*/
+"cast(PK.RDB$RELATION_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as PKTABLE_NAME"
+",cast(ISP.RDB$FIELD_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as PKCOLUMN_NAME"
/*+" ,null as FKTABLE_CAT "
+" ,null as FKTABLE_SCHEM "*/
+",cast(FK.RDB$RELATION_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as FKTABLE_NAME"
+",cast(ISF.RDB$FIELD_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as FKCOLUMN_NAME"
+",CAST((ISP.RDB$FIELD_POSITION + 1) as SMALLINT) as KEY_SEQ"
+",cast(RC.RDB$UPDATE_RULE as varchar(11)) as UPDATE_RULE"
+",cast(RC.RDB$DELETE_RULE as varchar(11)) as DELETE_RULE"
+",cast(PK.RDB$CONSTRAINT_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as PK_NAME"
+",cast(FK.RDB$CONSTRAINT_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as FK_NAME "
/*+" ,null as DEFERRABILITY "*/
+"from "
+"RDB$RELATION_CONSTRAINTS PK"
+",RDB$RELATION_CONSTRAINTS FK"
+",RDB$REF_CONSTRAINTS RC"
+",RDB$INDEX_SEGMENTS ISP"
+",RDB$INDEX_SEGMENTS ISF "
+"WHERE CAST(PK.RDB$RELATION_NAME AS VARCHAR(" + (OBJECT_NAME_LENGTH + 10) + ")) = ? "
+"and FK.RDB$CONSTRAINT_NAME = RC.RDB$CONSTRAINT_NAME "
+"and PK.RDB$CONSTRAINT_NAME = RC.RDB$CONST_NAME_UQ "
+"and ISP.RDB$INDEX_NAME = PK.RDB$INDEX_NAME "
+"and ISF.RDB$INDEX_NAME = FK.RDB$INDEX_NAME "
+"and ISP.RDB$FIELD_POSITION = ISF.RDB$FIELD_POSITION "
+"order by 3, 5 ";
/**
* Gets a description of the foreign key columns that reference a
* table's primary key columns (the foreign keys exported by a
* table). They are ordered by FKTABLE_CAT, FKTABLE_SCHEM,
* FKTABLE_NAME, and KEY_SEQ.
*
* Each foreign key column description has the following columns:
*
* - PKTABLE_CAT String => primary key table catalog (may be null)
*
- PKTABLE_SCHEM String => primary key table schema (may be null)
*
- PKTABLE_NAME String => primary key table name
*
- PKCOLUMN_NAME String => primary key column name
*
- FKTABLE_CAT String => foreign key table catalog (may be null)
* being exported (may be null)
*
- FKTABLE_SCHEM String => foreign key table schema (may be null)
* being exported (may be null)
*
- FKTABLE_NAME String => foreign key table name
* being exported
*
- FKCOLUMN_NAME String => foreign key column name
* being exported
*
- KEY_SEQ short => sequence number within foreign key
*
- UPDATE_RULE short => What happens to
* foreign key when primary is updated:
*
* - importedNoAction - do not allow update of primary
* key if it has been imported
*
- importedKeyCascade - change imported key to agree
* with primary key update
*
- importedKeySetNull - change imported key to NULL if
* its primary key has been updated
*
- importedKeySetDefault - change imported key to default values
* if its primary key has been updated
*
- importedKeyRestrict - same as importedKeyNoAction
* (for ODBC 2.x compatibility)
*
* - DELETE_RULE short => What happens to
* the foreign key when primary is deleted.
*
* - importedKeyNoAction - do not allow delete of primary
* key if it has been imported
*
- importedKeyCascade - delete rows that import a deleted key
*
- importedKeySetNull - change imported key to NULL if
* its primary key has been deleted
*
- importedKeyRestrict - same as importedKeyNoAction
* (for ODBC 2.x compatibility)
*
- importedKeySetDefault - change imported key to default if
* its primary key has been deleted
*
* - FK_NAME String => foreign key name (may be null)
*
- PK_NAME String => primary key name (may be null)
*
- DEFERRABILITY short => can the evaluation of foreign key
* constraints be deferred until commit
*
* - importedKeyInitiallyDeferred - see SQL92 for definition
*
- importedKeyInitiallyImmediate - see SQL92 for definition
*
- importedKeyNotDeferrable - see SQL92 for definition
*
*
*
* @param catalog a catalog name; "" retrieves those without a
* catalog; null means drop catalog name from the selection criteria
* @param schema a schema name; "" retrieves those
* without a schema
* @param table a table name
* @return ResultSet
- each row is a foreign key column description
* @exception SQLException if a database access error occurs
* @see #getImportedKeys
*/
public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException {
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(14, datatypeCoder)
.at(0).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PKTABLE_CAT", "COLUMNINFO").addField()
.at(1).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PKTABLE_SCHEM", "COLUMNINFO").addField()
.at(2).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PKTABLE_NAME", "COLUMNINFO").addField()
.at(3).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PKCOLUMN_NAME", "COLUMNINFO").addField()
.at(4).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FKTABLE_CAT", "COLUMNINFO").addField()
.at(5).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FKTABLE_SCHEM", "COLUMNINFO").addField()
.at(6).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FKTABLE_NAME", "COLUMNINFO").addField()
.at(7).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FKCOLUMN_NAME", "COLUMNINFO").addField()
.at(8).simple(SQL_SHORT, 0, "KEY_SEQ", "COLUMNINFO").addField()
.at(9).simple(SQL_SHORT, 0, "UPDATE_RULE", "COLUMNINFO").addField()
.at(10).simple(SQL_SHORT, 0, "DELETE_RULE", "COLUMNINFO").addField()
.at(11).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FK_NAME", "COLUMNINFO").addField()
.at(12).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PK_NAME", "COLUMNINFO").addField()
.at(13).simple(SQL_SHORT, 0, "DEFERRABILITY", "COLUMNINFO").addField()
.toRowDescriptor();
List params = Collections.singletonList(table);
try (ResultSet rs = doQuery(GET_EXPORTED_KEYS, params)) {
// if nothing found, return an empty result set
if (!rs.next()) {
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
List rows = new ArrayList<>();
final RowValueBuilder valueBuilder = new RowValueBuilder(rowDescriptor);
do {
rows.add(valueBuilder
.at(2).set(getBytes(rs.getString("PKTABLE_NAME")))
.at(3).set(getBytes(rs.getString("PKCOLUMN_NAME")))
.at(6).set(getBytes(rs.getString("FKTABLE_NAME")))
.at(7).set(getBytes(rs.getString("FKCOLUMN_NAME")))
.at(8).set(createShort(rs.getShort("KEY_SEQ")))
.at(9).set(mapAction(rs.getString("UPDATE_RULE")))
.at(10).set(mapAction(rs.getString("DELETE_RULE")))
.at(11).set(getBytes(rs.getString("FK_NAME")))
.at(12).set(getBytes(rs.getString("PK_NAME")))
.at(13).set(IMPORTED_KEY_NOT_DEFERRABLE)
.toRowValue(true)
);
} while (rs.next());
return new FBResultSet(rowDescriptor, rows);
}
}
private static final String GET_CROSS_KEYS = "select "
/*+" null as PKTABLE_CAT "
+" ,null as PKTABLE_SCHEM "*/
+"cast(PK.RDB$RELATION_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as PKTABLE_NAME"
+",cast(ISP.RDB$FIELD_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as PKCOLUMN_NAME"
/*+" ,null as FKTABLE_CAT "
+" ,null as FKTABLE_SCHEM "*/
+",cast(FK.RDB$RELATION_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as FKTABLE_NAME"
+",cast(ISF.RDB$FIELD_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as FKCOLUMN_NAME"
+",CAST((ISP.RDB$FIELD_POSITION + 1) as SMALLINT) as KEY_SEQ"
+",cast(RC.RDB$UPDATE_RULE as varchar(11)) as UPDATE_RULE"
+",cast(RC.RDB$DELETE_RULE as varchar(11)) as DELETE_RULE"
+",cast(PK.RDB$CONSTRAINT_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as PK_NAME"
+",cast(FK.RDB$CONSTRAINT_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as FK_NAME"
/*+" ,null as DEFERRABILITY "*/
+" from "
+"RDB$RELATION_CONSTRAINTS PK"
+",RDB$RELATION_CONSTRAINTS FK"
+",RDB$REF_CONSTRAINTS RC"
+",RDB$INDEX_SEGMENTS ISP"
+",RDB$INDEX_SEGMENTS ISF "
+"WHERE CAST(PK.RDB$RELATION_NAME AS VARCHAR(" + (OBJECT_NAME_LENGTH + 10) + ")) = ? and "
+"CAST(FK.RDB$RELATION_NAME AS VARCHAR(" + (OBJECT_NAME_LENGTH + 10) +")) = ? and "
+" FK.RDB$CONSTRAINT_NAME = RC.RDB$CONSTRAINT_NAME "
+"and PK.RDB$CONSTRAINT_NAME = RC.RDB$CONST_NAME_UQ "
+"and ISP.RDB$INDEX_NAME = PK.RDB$INDEX_NAME "
+"and ISF.RDB$INDEX_NAME = FK.RDB$INDEX_NAME "
+"and ISP.RDB$FIELD_POSITION = ISF.RDB$FIELD_POSITION "
+"order by 3, 5 ";
/**
* Gets a description of the foreign key columns in the foreign key
* table that reference the primary key columns of the primary key
* table (describe how one table imports another's key). This
* should normally return a single foreign key/primary key pair
* (most tables only import a foreign key from a table once.) They
* are ordered by FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, and
* KEY_SEQ.
*
* Each foreign key column description has the following columns:
*
* - PKTABLE_CAT String => primary key table catalog (may be null)
*
- PKTABLE_SCHEM String => primary key table schema (may be null)
*
- PKTABLE_NAME String => primary key table name
*
- PKCOLUMN_NAME String => primary key column name
*
- FKTABLE_CAT String => foreign key table catalog (may be null)
* being exported (may be null)
*
- FKTABLE_SCHEM String => foreign key table schema (may be null)
* being exported (may be null)
*
- FKTABLE_NAME String => foreign key table name
* being exported
*
- FKCOLUMN_NAME String => foreign key column name
* being exported
*
- KEY_SEQ short => sequence number within foreign key
*
- UPDATE_RULE short => What happens to
* foreign key when primary is updated:
*
* - importedNoAction - do not allow update of primary
* key if it has been imported
*
- importedKeyCascade - change imported key to agree
* with primary key update
*
- importedKeySetNull - change imported key to NULL if
* its primary key has been updated
*
- importedKeySetDefault - change imported key to default values
* if its primary key has been updated
*
- importedKeyRestrict - same as importedKeyNoAction
* (for ODBC 2.x compatibility)
*
* - DELETE_RULE short => What happens to
* the foreign key when primary is deleted.
*
* - importedKeyNoAction - do not allow delete of primary
* key if it has been imported
*
- importedKeyCascade - delete rows that import a deleted key
*
- importedKeySetNull - change imported key to NULL if
* its primary key has been deleted
*
- importedKeyRestrict - same as importedKeyNoAction
* (for ODBC 2.x compatibility)
*
- importedKeySetDefault - change imported key to default if
* its primary key has been deleted
*
* - FK_NAME String => foreign key name (may be null)
*
- PK_NAME String => primary key name (may be null)
*
- DEFERRABILITY short => can the evaluation of foreign key
* constraints be deferred until commit
*
* - importedKeyInitiallyDeferred - see SQL92 for definition
*
- importedKeyInitiallyImmediate - see SQL92 for definition
*
- importedKeyNotDeferrable - see SQL92 for definition
*
*
*
* @param primaryCatalog a catalog name; "" retrieves those without a
* catalog; null means drop catalog name from the selection criteria
* @param primarySchema a schema name; "" retrieves those
* without a schema
* @param primaryTable the table name that exports the key
* @param foreignCatalog a catalog name; "" retrieves those without a
* catalog; null means drop catalog name from the selection criteria
* @param foreignSchema a schema name; "" retrieves those
* without a schema
* @param foreignTable the table name that imports the key
* @return ResultSet
- each row is a foreign key column description
* @exception SQLException if a database access error occurs
* @see #getImportedKeys
*/
public ResultSet getCrossReference(
String primaryCatalog, String primarySchema, String primaryTable,
String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException {
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(14, datatypeCoder)
.at(0).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PKTABLE_CAT", "COLUMNINFO").addField()
.at(1).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PKTABLE_SCHEM", "COLUMNINFO").addField()
.at(2).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PKTABLE_NAME", "COLUMNINFO").addField()
.at(3).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PKCOLUMN_NAME", "COLUMNINFO").addField()
.at(4).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FKTABLE_CAT", "COLUMNINFO").addField()
.at(5).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FKTABLE_SCHEM", "COLUMNINFO").addField()
.at(6).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FKTABLE_NAME", "COLUMNINFO").addField()
.at(7).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FKCOLUMN_NAME", "COLUMNINFO").addField()
.at(8).simple(SQL_SHORT, 0, "KEY_SEQ", "COLUMNINFO").addField()
.at(9).simple(SQL_SHORT, 0, "UPDATE_RULE", "COLUMNINFO").addField()
.at(10).simple(SQL_SHORT, 0, "DELETE_RULE", "COLUMNINFO").addField()
.at(11).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FK_NAME", "COLUMNINFO").addField()
.at(12).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PK_NAME", "COLUMNINFO").addField()
.at(13).simple(SQL_SHORT, 0, "DEFERRABILITY", "COLUMNINFO").addField()
.toRowDescriptor();
final List params = Arrays.asList(primaryTable, foreignTable);
try (ResultSet rs = doQuery(GET_CROSS_KEYS, params)) {
// return empty result set if nothing found
if (!rs.next()) {
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
final List rows = new ArrayList<>();
final RowValueBuilder valueBuilder = new RowValueBuilder(rowDescriptor);
do {
rows.add(valueBuilder
.at(2).set(getBytes(rs.getString("PKTABLE_NAME")))
.at(3).set(getBytes(rs.getString("PKCOLUMN_NAME")))
.at(6).set(getBytes(rs.getString("FKTABLE_NAME")))
.at(7).set(getBytes(rs.getString("FKCOLUMN_NAME")))
.at(8).set(createShort(rs.getShort("KEY_SEQ")))
.at(9).set(mapAction(rs.getString("UPDATE_RULE")))
.at(10).set(mapAction(rs.getString("DELETE_RULE")))
.at(11).set(getBytes(rs.getString("FK_NAME")))
.at(12).set(getBytes(rs.getString("PK_NAME")))
.at(13).set(IMPORTED_KEY_NOT_DEFERRABLE)
.toRowValue(true)
);
} while (rs.next());
return new FBResultSet(rowDescriptor, rows);
}
}
/**
* Function to convert integer values to encoded byte arrays for shorts.
*
* @param value integer value to convert
* @return encoded byte array representing the value
*/
private static byte[] createShort(int value) {
assert (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) : String.format("Value \"%d\" outside range of short", value);
return datatypeCoder.encodeShort(value);
}
/**
* Function to convert integer values to encoded byte arrays for integers.
*
* @param value integer value to convert
* @return encoded byte array representing the value
*/
private static byte[] createInt(int value) {
return datatypeCoder.encodeInt(value);
}
/**
* Gets a description of all the standard SQL types supported by
* this database. They are ordered by DATA_TYPE and then by how
* closely the data type maps to the corresponding JDBC SQL type.
*
* Each type description has the following columns:
*
* - TYPE_NAME String => Type name
*
- DATA_TYPE short => SQL data type from java.sql.Types
*
- PRECISION int => maximum precision
*
- LITERAL_PREFIX String => prefix used to quote a literal
* (may be null)
*
- LITERAL_SUFFIX String => suffix used to quote a literal
(may be null)
*
- CREATE_PARAMS String => parameters used in creating
* the type (may be null)
*
- NULLABLE short => can you use NULL for this type?
*
* - typeNoNulls - does not allow NULL values
*
- typeNullable - allows NULL values
*
- typeNullableUnknown - nullability unknown
*
* - CASE_SENSITIVE boolean=> is it case sensitive?
*
- SEARCHABLE short => can you use "WHERE" based on this type:
*
* - typePredNone - No support
*
- typePredChar - Only supported with WHERE .. LIKE
*
- typePredBasic - Supported except for WHERE .. LIKE
*
- typeSearchable - Supported for all WHERE ..
*
* - UNSIGNED_ATTRIBUTE boolean => is it unsigned?
*
- FIXED_PREC_SCALE boolean => can it be a money value?
*
- AUTO_INCREMENT boolean => can it be used for an
* auto-increment value?
*
- LOCAL_TYPE_NAME String => localized version of type name
* (may be null)
*
- MINIMUM_SCALE short => minimum scale supported
*
- MAXIMUM_SCALE short => maximum scale supported
*
- SQL_DATA_TYPE int => unused
*
- SQL_DATETIME_SUB int => unused
*
- NUM_PREC_RADIX int => usually 2 or 10
*
*
* @return ResultSet
- each row is an SQL type description
* @exception SQLException if a database access error occurs
*/
public ResultSet getTypeInfo() throws SQLException {
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(18, datatypeCoder)
.at(0).simple(SQL_VARYING, 31, "TYPE_NAME", "TYPEINFO").addField()
.at(1).simple(SQL_SHORT, 0, "DATA_TYPE", "TYPEINFO").addField()
.at(2).simple(SQL_LONG, 0, "PRECISION", "TYPEINFO").addField()
.at(3).simple(SQL_VARYING, 1, "LITERAL_PREFIX", "TYPEINFO").addField()
.at(4).simple(SQL_VARYING, 1, "LITERAL_SUFFIX", "TYPEINFO").addField()
.at(5).simple(SQL_VARYING, 31, "CREATE_PARAMS", "TYPEINFO").addField()
.at(6).simple(SQL_SHORT, 0, "NULLABLE", "TYPEINFO").addField()
.at(7).simple(SQL_TEXT, 1, "CASE_SENSITIVE", "TYPEINFO").addField()
.at(8).simple(SQL_SHORT, 0, "SEARCHABLE", "TYPEINFO").addField()
.at(9).simple(SQL_TEXT, 1, "UNSIGNED_ATTRIBUTE", "TYPEINFO").addField()
.at(10).simple(SQL_TEXT, 1, "FIXED_PREC_SCALE", "TYPEINFO").addField()
.at(11).simple(SQL_TEXT, 1, "AUTO_INCREMENT", "TYPEINFO").addField()
.at(12).simple(SQL_VARYING, 31, "LOCAL_TYPE_NAME", "TYPEINFO").addField()
.at(13).simple(SQL_SHORT, 0, "MINIMUM_SCALE", "TYPEINFO").addField()
.at(14).simple(SQL_SHORT, 0, "MAXIMUM_SCALE", "TYPEINFO").addField()
.at(15).simple(SQL_LONG, 0, "SQL_DATA_TYPE", "TYPEINFO").addField()
.at(16).simple(SQL_LONG, 0, "SQL_DATETIME_SUB", "TYPEINFO").addField()
.at(17).simple(SQL_LONG, 0, "NUM_PREC_RADIX", "TYPEINFO").addField()
.toRowDescriptor();
final byte[] blobTypePred = firebirdSupportInfo.supportsFullSearchableBlobs()
? TYPE_SEARCHABLE
: TYPE_PRED_BASIC;
//dialect 3 only
final List rows = new ArrayList<>(19);
//BIGINT=-5
rows.add(RowValue.of(rowDescriptor,
getBytes("BIGINT"), createShort(Types.BIGINT), BIGINT_PRECISION, null, null, null,
TYPE_NULLABLE, CASEINSENSITIVE, TYPE_SEARCHABLE, SIGNED, FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO,
SHORT_ZERO, createInt(SQL_INT64), null, RADIX_TEN));
//LONGVARBINARY=-4
rows.add(RowValue.of(rowDescriptor,
getBytes("BLOB SUB_TYPE BINARY"), createShort(Types.LONGVARBINARY), INT_ZERO, null, null,
null, TYPE_NULLABLE, CASESENSITIVE, blobTypePred, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null,
SHORT_ZERO, SHORT_ZERO, createInt(SQL_BLOB), null, RADIX_TEN));
//VARBINARY=-3
rows.add(RowValue.of(rowDescriptor,
getBytes("VARCHAR"), createShort(Types.VARBINARY), createInt(32765), null, null, null, TYPE_NULLABLE,
CASESENSITIVE, TYPE_SEARCHABLE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO, SHORT_ZERO,
createInt(SQL_VARYING), null, RADIX_TEN));
//BINARY=-2
rows.add(RowValue.of(rowDescriptor,
getBytes("CHAR"), createShort(Types.BINARY), createInt(32767), null, null, null, TYPE_NULLABLE,
CASESENSITIVE, TYPE_SEARCHABLE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO, SHORT_ZERO,
createInt(SQL_TEXT), null, RADIX_TEN));
//LONGVARCHAR=-1
rows.add(RowValue.of(rowDescriptor,
getBytes("BLOB SUB_TYPE TEXT"), createShort(Types.LONGVARCHAR), INT_ZERO, null, null,
null, TYPE_NULLABLE, CASESENSITIVE, blobTypePred, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null,
SHORT_ZERO, SHORT_ZERO, createInt(SQL_BLOB), null, RADIX_TEN));
//CHAR=1
rows.add(RowValue.of(rowDescriptor,
getBytes("CHAR"), createShort(Types.CHAR), createInt(32767), getBytes("'"),
getBytes("'"), getBytes("length"), TYPE_NULLABLE, CASESENSITIVE, TYPE_SEARCHABLE, UNSIGNED,
FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO, SHORT_ZERO, createInt(SQL_TEXT), null,
RADIX_TEN));
//NUMERIC=2
rows.add(RowValue.of(rowDescriptor,
getBytes("NUMERIC"), createShort(Types.NUMERIC), NUMERIC_PRECISION, null, null,
getBytes("precision,scale"), TYPE_NULLABLE, CASEINSENSITIVE, TYPE_SEARCHABLE, SIGNED, FIXEDSCALE,
NOTAUTOINC, null, SHORT_ZERO, NUMERIC_PRECISION, createInt(SQL_INT64), null, RADIX_TEN));
//DECIMAL=3
rows.add(RowValue.of(rowDescriptor,
getBytes("DECIMAL"), createShort(Types.DECIMAL), DECIMAL_PRECISION, null, null,
getBytes("precision,scale"), TYPE_NULLABLE, CASEINSENSITIVE, TYPE_SEARCHABLE, SIGNED, FIXEDSCALE,
NOTAUTOINC, null, SHORT_ZERO, DECIMAL_PRECISION, createInt(SQL_INT64), null, RADIX_TEN));
//INTEGER=4
rows.add(RowValue.of(rowDescriptor,
getBytes("INTEGER"), createShort(Types.INTEGER), INTEGER_PRECISION, null, null, null,
TYPE_NULLABLE, CASEINSENSITIVE, TYPE_SEARCHABLE, SIGNED, FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO,
SHORT_ZERO, createInt(SQL_LONG), null, RADIX_TEN));
//SMALLINT=5
rows.add(RowValue.of(rowDescriptor,
getBytes("SMALLINT"), createShort(Types.SMALLINT), SMALLINT_PRECISION, null, null,
null, TYPE_NULLABLE, CASEINSENSITIVE, TYPE_SEARCHABLE, SIGNED, FIXEDSCALE, NOTAUTOINC, null,
SHORT_ZERO, SHORT_ZERO, createInt(SQL_SHORT), null, RADIX_TEN));
//FLOAT=6
rows.add(RowValue.of(rowDescriptor,
getBytes("FLOAT"), createShort(Types.FLOAT), FLOAT_PRECISION, null, null, null,
TYPE_NULLABLE, CASEINSENSITIVE, TYPE_SEARCHABLE, SIGNED, VARIABLESCALE, NOTAUTOINC, null, SHORT_ZERO,
SHORT_ZERO, createInt(SQL_FLOAT), null, RADIX_TEN));
//DOUBLE=8
rows.add(RowValue.of(rowDescriptor,
getBytes("DOUBLE PRECISION"), createShort(Types.DOUBLE), DOUBLE_PRECISION, null, null,
null, TYPE_NULLABLE, CASEINSENSITIVE, TYPE_SEARCHABLE, SIGNED, VARIABLESCALE, NOTAUTOINC, null,
SHORT_ZERO, SHORT_ZERO, createInt(SQL_DOUBLE), null, RADIX_TEN));
//VARCHAR=12
rows.add(RowValue.of(rowDescriptor,
getBytes("VARCHAR"), createShort(Types.VARCHAR), createInt(32765), getBytes("'"),
getBytes("'"), getBytes("length"), TYPE_NULLABLE, CASESENSITIVE, TYPE_SEARCHABLE, UNSIGNED,
FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO, SHORT_ZERO, createInt(SQL_VARYING), null,
RADIX_TEN));
//BOOLEAN=16
if (getDatabaseMajorVersion() >= 3) {
rows.add(RowValue.of(rowDescriptor,
getBytes("BOOLEAN"), createShort(Types.BOOLEAN), BOOLEAN_PRECISION,
null, null, null, TYPE_NULLABLE, CASEINSENSITIVE, TYPE_PRED_BASIC, UNSIGNED, FIXEDSCALE,
NOTAUTOINC, null, SHORT_ZERO, SHORT_ZERO, createInt(SQL_BOOLEAN), null, RADIX_BINARY));
}
//DATE=91
rows.add(RowValue.of(rowDescriptor,
getBytes("DATE"), createShort(Types.DATE), DATE_PRECISION, null, null, null,
TYPE_NULLABLE, CASEINSENSITIVE, TYPE_SEARCHABLE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO,
SHORT_ZERO, createInt(SQL_TYPE_DATE), null, RADIX_TEN));
//TIME=92
rows.add(RowValue.of(rowDescriptor,
getBytes("TIME"), createShort(Types.TIME), TIME_PRECISION, null, null, null,
TYPE_NULLABLE, CASEINSENSITIVE, TYPE_SEARCHABLE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO,
SHORT_ZERO, createInt(SQL_TYPE_TIME), null, RADIX_TEN));
//TIMESTAMP=93
rows.add(RowValue.of(rowDescriptor,
getBytes("TIMESTAMP"), createShort(Types.TIMESTAMP), TIMESTAMP_PRECISION, null, null,
null, TYPE_NULLABLE, CASEINSENSITIVE, TYPE_SEARCHABLE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null,
SHORT_ZERO, SHORT_ZERO, createInt(SQL_TIMESTAMP), null, RADIX_TEN));
//OTHER=1111
rows.add(RowValue.of(rowDescriptor,
getBytes("ARRAY"), createShort(Types.OTHER), INT_ZERO, null, null, null, TYPE_NULLABLE,
CASESENSITIVE, TYPE_PRED_NONE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO, SHORT_ZERO,
createInt(SQL_ARRAY), null, RADIX_TEN));
//BLOB=2004
// Should we split this into all negative blob types currently known in the DB?
// Blob is potentially searchable with like, etc, acting as if it isn't.
rows.add(RowValue.of(rowDescriptor,
getBytes("BLOB SUB_TYPE <0 "), createShort(Types.BLOB), INT_ZERO, null, null, null,
TYPE_NULLABLE, CASESENSITIVE, TYPE_PRED_NONE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO,
SHORT_ZERO, createInt(SQL_BLOB), null, RADIX_TEN));
return new FBResultSet(rowDescriptor, rows);
}
private static final String GET_INDEX_INFO = "SELECT "
+ "cast(ind.RDB$RELATION_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) AS TABLE_NAME"
+ ",ind.RDB$UNIQUE_FLAG AS UNIQUE_FLAG"
+ ",cast(ind.RDB$INDEX_NAME as varchar(" + OBJECT_NAME_LENGTH + ")) as INDEX_NAME"
+ ",ise.rdb$field_position + 1 as ORDINAL_POSITION"
+ ",cast(ise.rdb$field_name as varchar(" + OBJECT_NAME_LENGTH + ")) as COLUMN_NAME"
+ ",ind.RDB$EXPRESSION_SOURCE as EXPRESSION_SOURCE"
+ ",ind.RDB$INDEX_TYPE as ASC_OR_DESC "
+ "FROM "
+ "rdb$indices ind "
+ "LEFT JOIN rdb$index_segments ise ON ind.rdb$index_name = ise.rdb$index_name "
+ "WHERE "
+ "CAST(ind.rdb$relation_name AS VARCHAR(" + (OBJECT_NAME_LENGTH + 10) + ")) = ? "
+ "ORDER BY 2, 3, 4";
/**
* Gets a description of a table's indices and statistics. They are
* ordered by NON_UNIQUE, TYPE, INDEX_NAME, and ORDINAL_POSITION.
*
* Each index column description has the following columns:
*
* - TABLE_CAT String => table catalog (may be null)
*
- TABLE_SCHEM String => table schema (may be null)
*
- TABLE_NAME String => table name
*
- NON_UNIQUE boolean => Can index values be non-unique?
* false when TYPE is tableIndexStatistic
*
- INDEX_QUALIFIER String => index catalog (may be null);
* null when TYPE is tableIndexStatistic
*
- INDEX_NAME String => index name; null when TYPE is
* tableIndexStatistic
*
- TYPE short => index type:
*
* - tableIndexStatistic - this identifies table statistics that are
* returned in conjuction with a table's index descriptions
*
- tableIndexClustered - this is a clustered index
*
- tableIndexHashed - this is a hashed index
*
- tableIndexOther - this is some other style of index
*
* - ORDINAL_POSITION short => column sequence number
* within index; zero when TYPE is tableIndexStatistic
*
- COLUMN_NAME String => column name; null when TYPE is
* tableIndexStatistic
*
- ASC_OR_DESC String => column sort sequence, "A" => ascending,
* "D" => descending, may be null if sort sequence is not supported;
* null when TYPE is tableIndexStatistic
*
- CARDINALITY int => When TYPE is tableIndexStatistic, then
* this is the number of rows in the table; otherwise, it is the
* number of unique values in the index.
*
- PAGES int => When TYPE is tableIndexStatisic then
* this is the number of pages used for the table, otherwise it
* is the number of pages used for the current index.
*
- FILTER_CONDITION String => Filter condition, if any.
* (may be null)
*
*
* @param catalog a catalog name; "" retrieves those without a
* catalog; null means drop catalog name from the selection criteria
* @param schema a schema name; "" retrieves those without a schema
* @param table a table name
* @param unique when true, return only indices for unique values;
* when false, return indices regardless of whether unique or not
* @param approximate when true, result is allowed to reflect approximate
* or out of data values; when false, results are requested to be
* accurate
* @return ResultSet
- each row is an index column description
* @exception SQLException if a database access error occurs
*/
public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate)
throws SQLException {
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(13, datatypeCoder)
.at(0).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_CAT", "INDEXINFO").addField()
.at(1).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_SCHEM", "INDEXINFO").addField()
.at(2).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_NAME", "INDEXINFO").addField()
.at(3).simple(SQL_TEXT, 1, "NON_UNIQUE", "INDEXINFO").addField()
.at(4).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "INDEX_QUALIFIER", "INDEXINFO").addField()
.at(5).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "INDEX_NAME", "INDEXINFO").addField()
.at(6).simple(SQL_SHORT, 0, "TYPE", "INDEXINFO").addField()
.at(7).simple(SQL_SHORT, 0, "ORDINAL_POSITION", "INDEXINFO").addField()
// Field with EXPRESSION_SOURCE (used for expression indexes) in Firebird is actually a blob, using Integer.MAX_VALUE for length
.at(8).simple(SQL_VARYING, Integer.MAX_VALUE, "COLUMN_NAME", "INDEXINFO").addField()
.at(9).simple(SQL_VARYING, 31, "ASC_OR_DESC", "INDEXINFO").addField()
.at(10).simple(SQL_LONG, 0, "CARDINALITY", "INDEXINFO").addField()
.at(11).simple(SQL_LONG, 0, "PAGES", "INDEXINFO").addField()
.at(12).simple(SQL_VARYING, 31, "FILTER_CONDITION", "INDEXINFO").addField()
.toRowDescriptor();
List params = Collections.singletonList(table);
try (ResultSet rs = doQuery(GET_INDEX_INFO, params)) {
if (!rs.next()) {
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
final List rows = new ArrayList<>();
final RowValueBuilder valueBuilder = new RowValueBuilder(rowDescriptor);
do {
final boolean isNotUnique = rs.getInt("UNIQUE_FLAG") == 0;
if (unique && isNotUnique) {
// Skip indices that are not unique, as requested
continue;
}
valueBuilder
.at(2).set(getBytes(rs.getString("TABLE_NAME")))
.at(3).set(isNotUnique ? TRUE_BYTES : FALSE_BYTES)
.at(5).set(getBytes(rs.getString("INDEX_NAME")))
.at(6).set(TABLE_INDEX_OTHER);
String columnName = rs.getString("COLUMN_NAME");
if (rs.wasNull()) {
valueBuilder.at(7).set(SHORT_ONE);
String expressionSource = rs.getString("EXPRESSION_SOURCE");
if (expressionSource != null) {
valueBuilder.at(8).set(getBytes(expressionSource));
}
} else {
valueBuilder
.at(7).set(createShort(rs.getShort("ORDINAL_POSITION")))
.at(8).set(getBytes(columnName));
}
int ascOrDesc = rs.getInt("ASC_OR_DESC");
if (ascOrDesc == 0) {
valueBuilder.at(9).set(ASC_BYTES);
} else if (ascOrDesc == 1) {
valueBuilder.at(9).set(DESC_BYTES);
}
// NOTE: We are setting CARDINALITY and PAGES to NULL as we don't have this info; might contravene JDBC spec
// TODO index 10: use 1 / RDB$STATISTICS for approximation of CARDINALITY?
// TODO index 11: query RDB$PAGES for PAGES information?
rows.add(valueBuilder.toRowValue(true));
} while (rs.next());
return new FBResultSet(rowDescriptor, rows);
}
}
/**
* Does the database support the given result set type?
*
* @param type defined in java.sql.ResultSet
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
* @see Connection
* @since 1.2
* @see What Is in the JDBC 2.0 API
*/
public boolean supportsResultSetType(int type) throws SQLException {
switch (type){
case ResultSet.TYPE_FORWARD_ONLY:
case ResultSet.TYPE_SCROLL_INSENSITIVE :
case ResultSet.TYPE_SCROLL_SENSITIVE :
return true;
default:
return false;
}
}
/**
* Does the database support the concurrency type in combination
* with the given result set type?
*
* @param type defined in java.sql.ResultSet
* @param concurrency type defined in java.sql.ResultSet
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
* @see Connection
* @since 1.2
* @see What Is in the JDBC 2.0 API
*/
public boolean supportsResultSetConcurrency(int type, int concurrency) throws SQLException {
switch(type) {
case ResultSet.TYPE_FORWARD_ONLY:
case ResultSet.TYPE_SCROLL_INSENSITIVE :
case ResultSet.TYPE_SCROLL_SENSITIVE :
return concurrency == ResultSet.CONCUR_READ_ONLY ||
concurrency == ResultSet.CONCUR_UPDATABLE;
default:
return false;
}
}
/**
*
* Indicates whether a result set's own updates are visible.
*
* @param type result set type, i.e. ResultSet.TYPE_XXX
* @return true
if updates are visible for the result set type;
* false
otherwise
* @exception SQLException if a database access error occurs
* @since 1.2
* @see What Is in the JDBC 2.0 API
*/
public boolean ownUpdatesAreVisible(int type) throws SQLException {
return ResultSet.TYPE_SCROLL_INSENSITIVE == type ||
ResultSet.TYPE_SCROLL_SENSITIVE == type;
}
/**
*
* Indicates whether a result set's own deletes are visible.
*
* @param type result set type, i.e. ResultSet.TYPE_XXX
* @return true
if deletes are visible for the result set type;
* false
otherwise
* @exception SQLException if a database access error occurs
* @since 1.2
* @see What Is in the JDBC 2.0 API
*/
public boolean ownDeletesAreVisible(int type) throws SQLException {
return ResultSet.TYPE_SCROLL_INSENSITIVE == type ||
ResultSet.TYPE_SCROLL_SENSITIVE == type;
}
/**
*
* Indicates whether a result set's own inserts are visible.
*
* @param type result set type, i.e. ResultSet.TYPE_XXX
* @return true
if inserts are visible for the result set type;
* false
otherwise
* @exception SQLException if a database access error occurs
* @since 1.2
* @see What Is in the JDBC 2.0 API
*/
public boolean ownInsertsAreVisible(int type) throws SQLException {
return ResultSet.TYPE_SCROLL_INSENSITIVE == type ||
ResultSet.TYPE_SCROLL_SENSITIVE == type;
}
/**
*
* Indicates whether updates made by others are visible.
*
* @param type result set type, i.e. ResultSet.TYPE_XXX
* @return true
if updates made by others
* are visible for the result set type;
* false
otherwise
* @exception SQLException if a database access error occurs
* @since 1.2
* @see What Is in the JDBC 2.0 API
*/
public boolean othersUpdatesAreVisible(int type) throws SQLException {
return false;
}
/**
*
* Indicates whether deletes made by others are visible.
*
* @param type result set type, i.e. ResultSet.TYPE_XXX
* @return true
if deletes made by others
* are visible for the result set type;
* false
otherwise
* @exception SQLException if a database access error occurs
* @since 1.2
* @see What Is in the JDBC 2.0 API
*/
public boolean othersDeletesAreVisible(int type) throws SQLException {
return false;
}
/**
*
* Indicates whether inserts made by others are visible.
*
* @param type result set type, i.e. ResultSet.TYPE_XXX
* @return true
if inserts made by others
* are visible for the result set type;
* false
otherwise
* @exception SQLException if a database access error occurs
* @since 1.2
* @see What Is in the JDBC 2.0 API
*/
public boolean othersInsertsAreVisible(int type) throws SQLException {
return false;
}
/**
*
* Indicates whether or not a visible row update can be detected by
* calling the method ResultSet.rowUpdated
.
*
* @param type result set type, i.e. ResultSet.TYPE_XXX
* @return true
if changes are detected by the result set type;
* false
otherwise
* @exception SQLException if a database access error occurs
* @since 1.2
* @see What Is in the JDBC 2.0 API
*/
public boolean updatesAreDetected(int type) throws SQLException {
return false;
}
/**
*
* Indicates whether or not a visible row delete can be detected by
* calling ResultSet.rowDeleted(). If deletesAreDetected()
* returns false, then deleted rows are removed from the result set.
*
* @param type result set type, i.e. ResultSet.TYPE_XXX
* @return true if changes are detected by the resultset type
* @exception SQLException if a database access error occurs
* @since 1.2
* @see What Is in the JDBC 2.0 API
*/
public boolean deletesAreDetected(int type) throws SQLException {
return false;
}
/**
*
* Indicates whether or not a visible row insert can be detected
* by calling ResultSet.rowInserted().
*
* @param type result set type, i.e. ResultSet.TYPE_XXX
* @return true if changes are detected by the resultset type
* @exception SQLException if a database access error occurs
* @since 1.2
* @see What Is in the JDBC 2.0 API
*/
public boolean insertsAreDetected(int type) throws SQLException {
return false;
}
/**
*
* Indicates whether the driver supports batch updates.
* @return true if the driver supports batch updates; false otherwise
* @since 1.2
* @see What Is in the JDBC 2.0 API
*/
public boolean supportsBatchUpdates() throws SQLException {
return true;
}
/**
* UDTs are not supported by Firebird. This method will always return an empty ResultSet.
*
* {@inheritDoc}
*/
public ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) throws SQLException {
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(7, datatypeCoder)
.at(0).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TYPE_CAT", "UDT").addField()
.at(1).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TYPE_SCHEM", "UDT").addField()
.at(2).simple(SQL_VARYING, 31, "TYPE_NAME", "UDT").addField()
.at(3).simple(SQL_VARYING, 31, "CLASS_NAME", "UDT").addField()
.at(4).simple(SQL_LONG, 0, "DATA_TYPE", "UDT").addField()
.at(5).simple(SQL_VARYING, 31, "REMARKS", "UDT").addField()
.at(6).simple(SQL_SHORT, 0, "BASE_TYPE", "UDT").addField()
.toRowDescriptor();
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
/**
* Retrieves the connection that produced this metadata object.
*
* @return the connection that produced this metadata object
* @since 1.2
* @see What Is in the JDBC 2.0 API
*/
public Connection getConnection() throws SQLException {
return connection;
}
//jdbc 3 methods
/**
* UDTs are not supported by Firebird. This method will always return an empty ResultSet.
*
* {@inheritDoc}
*/
public ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern,
String attributeNamePattern) throws SQLException {
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(21, datatypeCoder)
.at(0).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TYPE_CAT", "ATTRIBUTES").addField()
.at(1).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TYPE_SCHEM", "ATTRIBUTES").addField()
.at(2).simple(SQL_VARYING, 31, "TYPE_NAME", "ATTRIBUTES").addField()
.at(3).simple(SQL_VARYING, 31, "ATTR_NAME", "ATTRIBUTES").addField()
.at(4).simple(SQL_LONG, 0, "DATA_TYPE", "ATTRIBUTES").addField()
.at(5).simple(SQL_VARYING, 31, "ATTR_TYPE_NAME", "ATTRIBUTES").addField()
.at(6).simple(SQL_LONG, 0, "ATTR_SIZE", "ATTRIBUTES").addField()
.at(7).simple(SQL_LONG, 0, "DECIMAL_DIGITS", "ATTRIBUTES").addField()
.at(8).simple(SQL_LONG, 0, "NUM_PREC_RADIX", "ATTRIBUTES").addField()
.at(9).simple(SQL_LONG, 0, "NULLABLE", "ATTRIBUTES").addField()
.at(10).simple(SQL_VARYING, 80, "REMARKS", "ATTRIBUTES").addField()
.at(11).simple(SQL_VARYING, 31, "ATTR_DEF", "ATTRIBUTES").addField()
.at(12).simple(SQL_LONG, 0, "SQL_DATA_TYPE", "ATTRIBUTES").addField()
.at(13).simple(SQL_LONG, 0, "SQL_DATETIME_SUB", "ATTRIBUTES").addField()
.at(14).simple(SQL_LONG, 0, "CHAR_OCTET_LENGTH", "ATTRIBUTES").addField()
.at(15).simple(SQL_SHORT, 0, "ORDINAL_POSITION", "ATTRIBUTES").addField()
.at(16).simple(SQL_VARYING, 31, "IS_NULLABLE", "ATTRIBUTES").addField()
.at(17).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "SCOPE_CATALOG", "ATTRIBUTES").addField()
.at(18).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "SCOPE_SCHEMA", "ATTRIBUTES").addField()
.at(19).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "SCOPE_TABLE", "ATTRIBUTES").addField()
.at(20).simple(SQL_SHORT, 0, "SOURCE_DATA_TYPE", "ATTRIBUTES").addField()
.toRowDescriptor();
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
/**
* Retrieves whether this database supports savepoints.
*
* @return true if savepoints are supported; false otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsSavepoints() throws SQLException {
return firebirdSupportInfo.supportsSavepoint();
}
/**
* Retrieve whether this database supports named parameters.
*
* @return true if named parameters are supported, false otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsNamedParameters() throws SQLException {
return false;
}
/**
* Retrieves whether it is possible to have multiple ResultSet
* objects returned from a CallableStatement
object
* simultaneously.
*
* @return true if multiple open ResultSets are supported, false otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsMultipleOpenResults() throws SQLException {
return false;
}
/**
* Retrieves whether auto-generated keys can be retrieved after creation.
*
* @return true if auto-generated keys can be retrieved, false otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsGetGeneratedKeys() throws SQLException {
return AbstractGeneratedKeysQuery.isGeneratedKeysSupportLoaded()
&& firebirdSupportInfo.supportsInsertReturning();
}
/**
* Supertypes are not supported by Firebird. This method will always return an empty ResultSet.
*
* {@inheritDoc}
*/
public ResultSet getSuperTypes(String catalog, String schemaPattern, String tableNamePattern) throws SQLException {
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(6, datatypeCoder)
.at(0).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TYPE_CAT", "SUPERTYPES").addField()
.at(1).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TYPE_SCHEM", "SUPERTYPES").addField()
.at(2).simple(SQL_VARYING, 31, "TYPE_NAME", "SUPERTYPES").addField()
.at(3).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "SUPERTYPE_CAT", "SUPERTYPES").addField()
.at(4).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "SUPERTYPE_SCHEM", "SUPERTYPES").addField()
.at(5).simple(SQL_VARYING, 31, "SUPERTYPE_NAME", "SUPERTYPES").addField()
.toRowDescriptor();
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
/**
* Supertables are not supported by Firebird. This method will always return an empty ResultSet.
*
* {@inheritDoc}
*/
public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) throws SQLException {
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(4, datatypeCoder)
.at(0).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_CAT", "SUPERTABLES").addField()
.at(1).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_SCHEM", "SUPERTABLES").addField()
.at(2).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_NAME", "SUPERTABLES").addField()
.at(3).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "SUPERTABLE_NAME", "SUPERTABLES").addField()
.toRowDescriptor();
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
/**
* Retrieves whether this database supports the given results holdability.
*
* @param holdability one of the following constants:
* ResultSet.HOLD_CURSORS_OVER_COMMIT
or
* ResultSet.CLOSE_CURSORS_AT_COMMIT
* @return true
if the holdability is supported,
* false
otherwise
* @exception SQLException if a database access error occurs
*/
public boolean supportsResultSetHoldability(int holdability) throws SQLException {
return holdability == ResultSet.CLOSE_CURSORS_AT_COMMIT ||
holdability == ResultSet.HOLD_CURSORS_OVER_COMMIT;
}
/**
* Retrieves the default holdability of this ResultSet
.
*
* @return the default holdability
* @exception SQLException if a database access error occurs
*/
public int getResultSetHoldability() throws SQLException {
return ResultSet.CLOSE_CURSORS_AT_COMMIT;
}
@Override
public int getDatabaseMajorVersion() throws SQLException {
return gdsHelper.getDatabaseProductMajorVersion();
}
@Override
public int getDatabaseMinorVersion() throws SQLException {
return gdsHelper.getDatabaseProductMinorVersion();
}
@Override
public int getOdsMajorVersion() throws SQLException {
return gdsHelper.getCurrentDatabase().getOdsMajor();
}
@Override
public int getOdsMinorVersion() throws SQLException {
return gdsHelper.getCurrentDatabase().getOdsMinor();
}
@Override
public int getDatabaseDialect() throws SQLException {
return gdsHelper.getCurrentDatabase().getDatabaseDialect();
}
@Override
public int getConnectionDialect() throws SQLException {
return gdsHelper.getCurrentDatabase().getConnectionDialect();
}
/**
* Indicates whether the SQLSTATEs returned by SQLException.getSQLState is
* X/Open (now known as Open Group) SQL CLI or SQL99
*
* @return the type of SQLSTATEs
* @exception SQLException should never be thrown in this implementation
*/
public int getSQLStateType() throws SQLException {
return DatabaseMetaData.sqlStateSQL99;
}
//-------------------------- JDBC 4.0 -------------------------------------
public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException {
return false;
}
public boolean autoCommitFailureClosesAllResultSets() throws SQLException {
// the holdable result sets remain open, others are closed, but this
// happens before the statement is executed
return false;
}
/**
* Retrieves a list of the client info properties
* that the driver supports. The result set contains the following columns
*
*
* - NAME String=> The name of the client info property
* - MAX_LEN int=> The maximum length of the value for the property
* - DEFAULT_VALUE String=> The default value of the property
* - DESCRIPTION String=> A description of the property. This will typically
* contain information as to where this property is
* stored in the database.
*
*
* The ResultSet
is sorted by the NAME column
*
* @return A ResultSet
object; each row is a supported client info
* property
*
* @exception SQLException if a database access error occurs
*
* @since 1.6
*/
public ResultSet getClientInfoProperties() throws SQLException {
// TODO Return context info?
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(4, datatypeCoder)
.at(0).simple(SQL_VARYING, 31, "NAME", "CLIENTINFO").addField()
.at(1).simple(SQL_LONG, 4, "MAX_LEN", "CLIENTINFO").addField()
.at(2).simple(SQL_VARYING, 31, "DEFAULT", "CLIENTINFO").addField()
.at(3).simple(SQL_VARYING, 31, "DESCRIPTION", "CLIENTINFO").addField()
.toRowDescriptor();
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
/**
* Retrieves a description of the given catalog's system or user
* function parameters and return type.
*
* Only descriptions matching the schema, function and
* parameter name criteria are returned. They are ordered by
* FUNCTION_CAT
, FUNCTION_SCHEM
,
* FUNCTION_NAME
and
* SPECIFIC_ NAME
. Within this, the return value,
* if any, is first. Next are the parameter descriptions in call
* order. The column descriptions follow in column number order.
*
*
Each row in the ResultSet
* is a parameter description, column description or
* return type description with the following fields:
*
* - FUNCTION_CAT String => function catalog (may be
null
)
* - FUNCTION_SCHEM String => function schema (may be
null
)
* - FUNCTION_NAME String => function name. This is the name
* used to invoke the function
*
- COLUMN_NAME String => column/parameter name
*
- COLUMN_TYPE Short => kind of column/parameter:
*
* - functionColumnUnknown - nobody knows
*
- functionColumnIn - IN parameter
*
- functionColumnInOut - INOUT parameter
*
- functionColumnOut - OUT parameter
*
- functionColumnReturn - function return value
*
- functionColumnResult - Indicates that the parameter or column
* is a column in the
ResultSet
*
* - DATA_TYPE int => SQL type from java.sql.Types
*
- TYPE_NAME String => SQL type name, for a UDT type the
* type name is fully qualified
*
- PRECISION int => precision
*
- LENGTH int => length in bytes of data
*
- SCALE short => scale - null is returned for data types where
* SCALE is not applicable.
*
- RADIX short => radix
*
- NULLABLE short => can it contain NULL.
*
* - functionNoNulls - does not allow NULL values
*
- functionNullable - allows NULL values
*
- functionNullableUnknown - nullability unknown
*
* - REMARKS String => comment describing column/parameter
*
- CHAR_OCTET_LENGTH int => the maximum length of binary
* and character based parameters or columns. For any other datatype the returned value
* is a NULL
*
- ORDINAL_POSITION int => the ordinal position, starting
* from 1, for the input and output parameters. A value of 0
* is returned if this row describes the function's return value.
* For result set columns, it is the
* ordinal position of the column in the result set starting from 1.
*
- IS_NULLABLE String => ISO rules are used to determine
* the nullability for a parameter or column.
*
* - YES --- if the parameter or column can include NULLs
*
- NO --- if the parameter or column cannot include NULLs
*
- empty string --- if the nullability for the
* parameter or column is unknown
*
* - SPECIFIC_NAME String => the name which uniquely identifies
* this function within its schema. This is a user specified, or DBMS
* generated, name that may be different then the
FUNCTION_NAME
* for example with overload functions
*
*
* The PRECISION column represents the specified column size for the given
* parameter or column.
* For numeric data, this is the maximum precision. For character data, this is the length in characters.
* For datetime datatypes, this is the length in characters of the String representation (assuming the
* maximum allowed precision of the fractional seconds component). For binary data, this is the length in bytes. For the ROWID datatype,
* this is the length in bytes. Null is returned for data types where the
* column size is not applicable.
* @param catalog a catalog name; must match the catalog name as it
* is stored in the database; "" retrieves those without a catalog;
* null
means that the catalog name should not be used to narrow
* the search
* @param schemaPattern a schema name pattern; must match the schema name
* as it is stored in the database; "" retrieves those without a schema;
* null
means that the schema name should not be used to narrow
* the search
* @param functionNamePattern a procedure name pattern; must match the
* function name as it is stored in the database
* @param columnNamePattern a parameter name pattern; must match the
* parameter or column name as it is stored in the database
* @return ResultSet
- each row describes a
* user function parameter, column or return type
*
* @exception SQLException if a database access error occurs
* @see #getSearchStringEscape
* @since 1.6
*/
public ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern,
String columnNamePattern) throws SQLException {
// FIXME implement this method to return actual result
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(17, datatypeCoder)
.at(0).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FUNCTION_CAT", "FUNCTION_COLUMNS").addField()
.at(1).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FUNCTION_SCHEM", "FUNCTION_COLUMNS").addField()
.at(2).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FUNCTION_NAME", "FUNCTION_COLUMNS").addField()
.at(3).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "COLUMN_NAME", "FUNCTION_COLUMNS").addField()
.at(4).simple(SQL_SHORT, 0, "COLUMN_TYPE", "FUNCTION_COLUMNS").addField()
.at(5).simple(SQL_LONG, 0, "DATA_TYPE", "FUNCTION_COLUMNS").addField()
.at(6).simple(SQL_VARYING, 31, "TYPE_NAME", "FUNCTION_COLUMNS").addField()
.at(7).simple(SQL_LONG, 0, "PRECISION", "FUNCTION_COLUMNS").addField()
.at(8).simple(SQL_LONG, 0, "LENGTH", "FUNCTION_COLUMNS").addField()
.at(9).simple(SQL_SHORT, 0, "SCALE", "FUNCTION_COLUMNS").addField()
.at(10).simple(SQL_SHORT, 0, "RADIX", "FUNCTION_COLUMNS").addField()
.at(11).simple(SQL_SHORT, 0, "NULLABLE", "FUNCTION_COLUMNS").addField()
.at(12).simple(SQL_VARYING, 80, "REMARKS", "FUNCTION_COLUMNS").addField()
.at(13).simple(SQL_LONG, 0, "CHAR_OCTET_LENGTH", "FUNCTION_COLUMNS").addField()
.at(14).simple(SQL_LONG, 0, "ORDINAL_POSITION", "FUNCTION_COLUMNS").addField()
.at(15).simple(SQL_VARYING, 31, "IS_NULLABLE", "FUNCTION_COLUMNS").addField()
.at(16).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "SPECIFIC_NAME", "FUNCTION_COLUMNS").addField()
.toRowDescriptor();
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
/**
* Retrieves a description of the system and user functions available
* in the given catalog.
*
* Only system and user function descriptions matching the schema and
* function name criteria are returned. They are ordered by
* FUNCTION_CAT
, FUNCTION_SCHEM
,
* FUNCTION_NAME
and
* SPECIFIC_ NAME
.
*
*
Each function description has the the following columns:
*
* - FUNCTION_CAT String => function catalog (may be
null
)
* - FUNCTION_SCHEM String => function schema (may be
null
)
* - FUNCTION_NAME String => function name. This is the name
* used to invoke the function
*
- REMARKS String => explanatory comment on the function
*
- FUNCTION_TYPE short => kind of function:
*
* - functionResultUnknown - Cannot determine if a return value
* or table will be returned
*
- functionNoTable- Does not return a table
*
- functionReturnsTable - Returns a table
*
* - SPECIFIC_NAME String => the name which uniquely identifies
* this function within its schema. This is a user specified, or DBMS
* generated, name that may be different then the
FUNCTION_NAME
* for example with overload functions
*
*
* A user may not have permission to execute any of the functions that are
* returned by getFunctions
*
* @param catalog a catalog name; must match the catalog name as it
* is stored in the database; "" retrieves those without a catalog;
* null
means that the catalog name should not be used to narrow
* the search
* @param schemaPattern a schema name pattern; must match the schema name
* as it is stored in the database; "" retrieves those without a schema;
* null
means that the schema name should not be used to narrow
* the search
* @param functionNamePattern a function name pattern; must match the
* function name as it is stored in the database
* @return ResultSet
- each row is a function description
* @exception SQLException if a database access error occurs
* @see #getSearchStringEscape
* @since 1.6
*/
public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern)
throws SQLException {
// FIXME implement this method to return actual result
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(6, datatypeCoder)
.at(0).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FUNCTION_CAT", "FUNCTIONS").addField()
.at(1).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FUNCTION_SCHEM", "FUNCTIONS").addField()
.at(2).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FUNCTION_NAME", "FUNCTIONS").addField()
.at(3).simple(SQL_VARYING, 80, "REMARKS", "FUNCTIONS").addField()
.at(4).simple(SQL_SHORT, 0, "FUNCTION_TYPE", "FUNCTIONS").addField()
.at(5).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "SPECIFIC_NAME", "FUNCTIONS").addField()
.toRowDescriptor();
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
/**
* Retrieves the schema names available in this database. The results
* are ordered by TABLE_CATALOG
and
* TABLE_SCHEM
.
*
* The schema columns are:
*
* - TABLE_SCHEM String => schema name
*
- TABLE_CATALOG String => catalog name (may be
null
)
*
*
*
* @param catalog a catalog name; must match the catalog name as it is stored
* in the database;"" retrieves those without a catalog; null means catalog
* name should not be used to narrow down the search.
* @param schemaPattern a schema name; must match the schema name as it is
* stored in the database; null means
* schema name should not be used to narrow down the search.
* @return a ResultSet
object in which each row is a
* schema description
* @exception SQLException if a database access error occurs
* @see #getSearchStringEscape
* @since 1.6
*/
public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException {
final RowDescriptor rowDescriptor = new RowDescriptorBuilder(2, datatypeCoder)
.at(0).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_SCHEM", "TABLESCHEMAS").addField()
.at(1).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_CATALOG", "TABLESCHEMAS").addField()
.toRowDescriptor();
return new FBResultSet(rowDescriptor, Collections.emptyList());
}
public boolean isWrapperFor(Class> iface) throws SQLException {
return iface != null && iface.isAssignableFrom(FBDatabaseMetaData.class);
}
public T unwrap(Class iface) throws SQLException {
if (!isWrapperFor(iface))
throw new SQLException("Unable to unwrap to class " + iface.getName());
return iface.cast(this);
}
protected static boolean isAllCondition(String pattern) {
return pattern == null || "%".equals(pattern);
}
/**
* Determine if there are no SQL wildcard characters ('%' or '_') in the
* given pattern.
*
* @param pattern The pattern to be checked for wildcards
* @return true
if there are no wildcards in the pattern,
* false
otherwise
*/
public static boolean hasNoWildcards(String pattern) {
if (pattern == null) return true;
for (int pos = 0; pos < pattern.length(); pos++) {
char ch = pattern.charAt(pos);
if (ch == '_' || ch == '%') {
return false;
} else if (ch == '\\' && pos < pattern.length() - 1) {
char nextCh = pattern.charAt(pos + 1);
if (nextCh == '\\' || nextCh == '%' || nextCh == '_') {
// We were an escape, skip next character
pos += 1;
}
}
}
return true;
}
/**
* Strips all backslash-escapes from a string.
*
* @param pattern The string to be stripped
* @return pattern with all backslash-escapes removed
*/
public static String stripEscape(String pattern) {
if (pattern == null) return null;
StringBuilder stripped = new StringBuilder(pattern.length());
for (int pos = 0; pos < pattern.length(); pos++) {
char ch = pattern.charAt(pos);
if (ch != '\\') {
stripped.append(ch);
} else if (pos < pattern.length() - 1 && pattern.charAt(pos + 1) == '\\') {
// We are an escape for a backslash, append backslash and skip next position
stripped.append('\\');
pos += 1;
}
}
return stripped.toString();
}
/**
* Escapes the like wildcards and escape ({@code \_%} in the provided search string with a {@code \}.
*
* @param objectName Object name to escape (not {@code null}).
* @return Object name with wildcards escaped.
*/
public static String escapeWildcards(String objectName) {
return objectName != null ? objectName.replaceAll("([\\\\_%])", "\\\\$1") : null;
}
protected String getWantsSystemTables(String[] types) {
for (String type : types) {
if (SYSTEM_TABLE.equals(type)) {
return "T";
}
}
return "F";
}
protected String getWantsTables(String[] types) {
for (String type : types) {
if (TABLE.equals(type)) {
return "T";
}
}
return "F";
}
protected String getWantsViews(String[] types) {
for (String type : types) {
if (VIEW.equals(type)) {
return "T";
}
}
return "F";
}
public ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern,
String columnNamePattern) throws SQLException {
// TODO Write implementation
throw new FBDriverNotCapableException();
}
public boolean generatedKeyAlwaysReturned() throws SQLException {
// TODO Double check if this is correct
return false;
}
public String getProcedureSourceCode(String procedureName) throws SQLException {
String sResult = null;
String sql = "Select RDB$PROCEDURE_SOURCE From RDB$PROCEDURES Where "
+ "RDB$PROCEDURE_NAME = ?";
List params = new ArrayList<>();
params.add(procedureName);
try (ResultSet rs = doQuery(sql, params)) {
if (rs.next()) sResult = rs.getString(1);
}
return sResult;
}
public String getTriggerSourceCode(String triggerName) throws SQLException {
String sResult = null;
String sql = "Select RDB$TRIGGER_SOURCE From RDB$TRIGGERS Where RDB$TRIGGER_NAME = ?";
List params = new ArrayList<>();
params.add(triggerName);
try (ResultSet rs = doQuery(sql, params)) {
if (rs.next()) sResult = rs.getString(1);
}
return sResult;
}
public String getViewSourceCode(String viewName) throws SQLException {
String sResult = null;
String sql = "Select RDB$VIEW_SOURCE From RDB$RELATIONS Where RDB$RELATION_NAME = ?";
List params = new ArrayList<>();
params.add(viewName);
try (ResultSet rs = doQuery(sql, params)) {
if (rs.next()) sResult = rs.getString(1);
}
return sResult;
}
protected static final class Clause {
private final String condition;
private final String value;
public Clause(String columnName, String pattern) {
if (isAllCondition(pattern)) {
condition = "";
value = null;
} else if (hasNoWildcards(pattern)) {
value = stripEscape(pattern);
// We are casting to VARCHAR( max object length + 10) to accommodate slightly larger object names
condition = "CAST(" + columnName + " AS VARCHAR(" + (OBJECT_NAME_LENGTH + 10) + ")) = ? and ";
} else {
// We are padding the column with 31 spaces to accommodate arguments longer than the actual column length.
// The argument itself is padded with 15 spaces and a % to prevent false positives, this allows 15 character longer patterns
value = pattern + SPACES_15 + "%";
condition = columnName + " || '" + SPACES_31 + "' like ? escape '\\' and ";
}
}
public String getCondition() {
return condition;
}
public String getValue() {
return value;
}
public boolean hasCondition() {
return !condition.isEmpty();
}
}
protected static byte[] getBytes(String value){
return value != null ? value.getBytes(StandardCharsets.UTF_8): null;
}
private FBPreparedStatement getStatement(String sql, boolean standalone) throws SQLException {
synchronized (statements) {
if (!standalone) {
// Check cache
FBPreparedStatement cachedStatement = statements.get(sql);
if (cachedStatement != null) {
if (cachedStatement.isClosed()) {
statements.remove(sql);
} else {
return cachedStatement;
}
}
}
InternalTransactionCoordinator.MetaDataTransactionCoordinator metaDataTransactionCoordinator =
new InternalTransactionCoordinator.MetaDataTransactionCoordinator(connection.txCoordinator);
FBPreparedStatement newStatement = new FBPreparedStatement(gdsHelper, sql,
ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT,
metaDataTransactionCoordinator, metaDataTransactionCoordinator, true, standalone, false);
if (!standalone) {
statements.put(sql, newStatement);
}
return newStatement;
}
}
/**
* Execute an sql query with a given set of parameters.
*
* @param sql
* The sql statement to be used for the query
* @param params
* The parameters to be used in the query
* @throws SQLException
* if a database access error occurs
*/
protected ResultSet doQuery(String sql, List params) throws SQLException {
return doQuery(sql, params, false);
}
/**
* Execute an sql query with a given set of parameters.
*
* @param sql
* The sql statement to be used for the query
* @param params
* The parameters to be used in the query
* @param standalone
* The query to be executed is a standalone query (should not be cached and be closed asap)
* @throws SQLException
* if a database access error occurs
*/
protected ResultSet doQuery(String sql, List params, boolean standalone) throws SQLException {
FBPreparedStatement s = getStatement(sql, standalone);
for (int i = 0; i < params.size(); i++)
s.setStringForced(i + 1, params.get(i));
return s.executeMetaDataQuery();
}
/**
* Indicates whether or not this data source supports the SQL ROWID
type,
* and if so the lifetime for which a RowId
object remains valid.
*
* The returned int values have the following relationship:
*
* ROWID_UNSUPPORTED < ROWID_VALID_OTHER < ROWID_VALID_TRANSACTION
* < ROWID_VALID_SESSION < ROWID_VALID_FOREVER
*
* so conditional logic such as
*
* if (metadata.getRowIdLifetime() > DatabaseMetaData.ROWID_VALID_TRANSACTION)
*
* can be used. Valid Forever means valid across all Sessions, and valid for
* a Session means valid across all its contained Transactions.
*
* @return the status indicating the lifetime of a RowId
* @throws SQLException if a database access error occurs
* @since 1.6
*/
public RowIdLifetime getRowIdLifetime() throws SQLException {
return RowIdLifetime.ROWID_UNSUPPORTED;
}
private static final int JDBC_MAJOR_VERSION = 4;
private static final int JDBC_MINOR_VERSION;
static {
int tempVersion;
try {
String javaImplementation = getSystemPropertyPrivileged("java.specification.version");
if (javaImplementation == null) {
// Assume minimum: JDBC 4.1
tempVersion = 1;
} else {
int javaVersionMajor;
try {
javaVersionMajor = (int) Double.parseDouble(javaImplementation);
} catch (NumberFormatException e) {
javaVersionMajor = 1;
}
if (javaVersionMajor >= 9) {
// JDK 9 or higher: JDBC 4.3
tempVersion = 3;
} else if ("1.8".compareTo(javaImplementation) <= 0) {
// JDK 1.8 or higher: JDBC 4.2
tempVersion = 2;
} else {
// JDK 1.7 (or lower): JDBC 4.1
tempVersion = 1;
}
}
} catch (RuntimeException ex) {
// default to 1 (JDBC 4.1) when privileged call fails
tempVersion = 1;
}
JDBC_MINOR_VERSION = tempVersion;
}
public int getJDBCMajorVersion() {
return JDBC_MAJOR_VERSION;
}
public int getJDBCMinorVersion() {
return JDBC_MINOR_VERSION;
}
private static String getSystemPropertyPrivileged(final String propertyName) {
return AccessController.doPrivileged(new PrivilegedAction() {
public String run() {
return System.getProperty(propertyName);
}
});
}
private static class LruPreparedStatementCache extends LinkedHashMap {
private final int maxCapacity;
private LruPreparedStatementCache(int maxCapacity) {
super(16, 0.75f, true);
this.maxCapacity = maxCapacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
if (size() <= maxCapacity) {
return false;
}
try {
FBPreparedStatement statement = eldest.getValue();
statement.close();
} catch (Exception e) {
log.debug("Closing eldest cached metadata statement yielded an exception; ignored", e);
}
return true;
}
}
}