org.firebirdsql.jdbc.GeneratedKeysSupportFactory Maven / Gradle / Ivy
Show all versions of jaybird Show documentation
/*
* Firebird Open Source 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.gds.JaybirdErrorCodes;
import org.firebirdsql.gds.ng.FbExceptionBuilder;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Locale;
import java.util.Set;
/**
* Factory to create appropriate {@code GeneratedKeysSupport} based on the configuration, Firebird version and parser
* availability.
*
* @author Mark Rotteveel
* @since 4.0
*/
final class GeneratedKeysSupportFactory {
static final String REASON_NO_RETURNING_SUPPORT =
"This version of Firebird does not support retrieving generated keys (support was added in Firebird 2.0)";
static final String REASON_EXPLICITLY_DISABLED = "disabled through connection property";
private static final String GENERATED_KEYS_ENABLED_DEFAULT = "default";
private static final String GENERATED_KEYS_DISABLED = "disabled";
private static final String GENERATED_KEYS_IGNORED = "ignored";
private GeneratedKeysSupportFactory() {
// No instances
}
/**
* Create generated keys support based on the provided config and database metadata.
*
* The supported values for {@code generatedKeysEnabled} are
*
* - {@code default}
* - Support all query types supported by Firebird version (if none are supported, will throw
* {@code SQLFeatureNotSupportedException} for attempts to use generated keys facility, except
* {@code Statement.RETURN_NO_GENERATED_KEYS})
* - {@code disabled}
* - Explicitly disabled support, will throw {@code SQLFeatureNotSupportedException} for attempts to use
* generated keys facility, except {@code Statement.RETURN_NO_GENERATED_KEYS})
* - {@code ignored}
* - Ignore generated keys support. Any attempt to use generated keys will be ignored and executed as a
* normal non-generated keys statement.
* - Comma-separated list of query types
* - Only enable support for the specified query types. All other query types are handled as non-generated
* keys statement
*
*
*
* The last option is a comma-separated list of query types to enable for support. Primary use-case is to enable
* only {@code insert} to avoid the singleton limitation for the other DML types. Other example
* {@code insert,update} enables only insert and update. Supported query type values (case insensitive) are:
*
* - {@code insert}
* - {@code update}
* - {@code delete}
* - {@code update_or_insert}
* - {@code merge}
*
*
*
* Supplying a list of query types will only enable the query types that are specified in the list (if supported by
* the connected Firebird version). Unknown query types are ignored. If all specified query types are unknown or not
* supported, then this behaves as {@code ignored}. NOTE: For Firebird 1.5 and earlier (unsupported) this will -
* same as for {@code default} - behave as {@code disabled} and attempts to use generated keys (except
* {@code Statement.RETURN_NO_GENERATED_KEYS}) will always throw a {@code SQLFeatureNotSupportedException}.
*
*
* @param generatedKeysEnabled
* Generated keys enabled value (case insensitive: {@code disabled}, {@code ignored},
* {@code default} / {@code null} / empty string, or a list of query types to enable
* @param fbDatabaseMetaData
* Database metadata object
* @return Appropriate generated keys support determined by config value, and Firebird feature support.
* @throws SQLException
* if a database access error occurs while determining feature support
*/
static GeneratedKeysSupport createFor(String generatedKeysEnabled, FirebirdDatabaseMetaData fbDatabaseMetaData)
throws SQLException {
String normalizedConfigValue = generatedKeysEnabled != null && !generatedKeysEnabled.isEmpty()
? generatedKeysEnabled.toLowerCase(Locale.ROOT)
: GENERATED_KEYS_ENABLED_DEFAULT;
if (GENERATED_KEYS_DISABLED.equals(normalizedConfigValue)) {
return DisabledGeneratedKeysSupport.EXPLICITLY_DISABLED;
} else if (GENERATED_KEYS_IGNORED.equals(normalizedConfigValue)) {
return IgnoredGeneratedKeysSupport.INSTANCE;
} else {
Set returningSupport =
GeneratedKeysSupport.QueryType.returningSupportForVersion(
fbDatabaseMetaData.getDatabaseMajorVersion(),
fbDatabaseMetaData.getDatabaseMinorVersion());
if (returningSupport.isEmpty()) {
return new DisabledGeneratedKeysSupport(REASON_NO_RETURNING_SUPPORT);
} else if (GENERATED_KEYS_ENABLED_DEFAULT.equals(normalizedConfigValue)) {
return new DefaultGeneratedKeysSupport(fbDatabaseMetaData, returningSupport);
} else {
Set enabledTypes =
getEnabledTypes(normalizedConfigValue, returningSupport);
if (enabledTypes.isEmpty()) {
return IgnoredGeneratedKeysSupport.INSTANCE;
}
return new DefaultGeneratedKeysSupport(fbDatabaseMetaData, enabledTypes);
}
}
}
private static Set getEnabledTypes(String normalizedConfigValue,
Set returningSupport) {
String[] queryTypeNames = normalizedConfigValue.split(",");
EnumSet enabledTypes = EnumSet.noneOf(GeneratedKeysSupport.QueryType.class);
// Given the limited number of values of queryType, double iteration is acceptable
for (String queryTypeName : queryTypeNames) {
for (GeneratedKeysSupport.QueryType queryType : returningSupport) {
if (queryType.matches(queryTypeName.trim())) {
enabledTypes.add(queryType);
}
}
}
return enabledTypes;
}
/**
* The default generated keys support, either based on the capabilities of Firebird or on a reduced set based on
* configuration.
*
* @since 4.0
*/
private static final class DefaultGeneratedKeysSupport implements GeneratedKeysSupport {
private final FirebirdDatabaseMetaData fbDatabaseMetaData;
private final Set supportedQueryTypes;
private DefaultGeneratedKeysSupport(FirebirdDatabaseMetaData fbDatabaseMetaData,
Set supportedQueryTypes) {
assert !supportedQueryTypes.contains(QueryType.UNSUPPORTED)
: "supportedQueryTypes should not contain UNSUPPORTED";
assert !supportedQueryTypes.isEmpty() : "At least one query type should be present";
this.fbDatabaseMetaData = fbDatabaseMetaData;
this.supportedQueryTypes = supportedQueryTypes;
}
@Override
public Query buildQuery(String sql, int autoGeneratedKeys) throws SQLException {
switch (autoGeneratedKeys) {
case Statement.NO_GENERATED_KEYS:
return GeneratedKeysQueryBuilder
.create(sql, supportedQueryTypes)
.forNoGeneratedKeysOption();
case Statement.RETURN_GENERATED_KEYS:
return GeneratedKeysQueryBuilder
.create(sql, supportedQueryTypes)
.forReturnGeneratedKeysOption(fbDatabaseMetaData);
default:
throw FbExceptionBuilder.forNonTransientException(JaybirdErrorCodes.jb_invalidGeneratedKeysOption)
.toSQLException();
}
}
@Override
public Query buildQuery(String sql, int[] columnIndexes) throws SQLException {
return GeneratedKeysQueryBuilder
.create(sql, supportedQueryTypes)
.forColumnsByIndex(columnIndexes, fbDatabaseMetaData);
}
@Override
public Query buildQuery(String sql, String[] columnNames) throws SQLException {
return GeneratedKeysQueryBuilder
.create(sql, supportedQueryTypes)
.forColumnsByName(columnNames);
}
@Override
public Set supportedQueryTypes() {
return Collections.unmodifiableSet(supportedQueryTypes);
}
@Override
public boolean supportsGetGeneratedKeys() {
return true;
}
}
/**
* Disabled generated keys support.
*
* Throws a {@code SQLFeatureNotSupportedException} with a configured reason for most options except
* using {@code autoGeneratedKeys} with value {@link Statement#NO_GENERATED_KEYS}.
*
*
* @since 4.0
*/
private static final class DisabledGeneratedKeysSupport implements GeneratedKeysSupport {
private static final DisabledGeneratedKeysSupport EXPLICITLY_DISABLED =
new DisabledGeneratedKeysSupport(REASON_EXPLICITLY_DISABLED);
private final String reasonDisabled;
public DisabledGeneratedKeysSupport(String reasonDisabled) {
this.reasonDisabled = reasonDisabled;
}
@Override
public Query buildQuery(String sql, int autoGeneratedKeys) throws SQLException {
switch (autoGeneratedKeys) {
case Statement.NO_GENERATED_KEYS:
return new Query(false, sql);
case Statement.RETURN_GENERATED_KEYS:
throw disabled();
default:
throw FbExceptionBuilder.forNonTransientException(JaybirdErrorCodes.jb_invalidGeneratedKeysOption)
.toSQLException();
}
}
@Override
public Query buildQuery(String sql, int[] columnIndexes) throws SQLException {
throw disabled();
}
@Override
public Query buildQuery(String sql, String[] columnNames) throws SQLException {
throw disabled();
}
@Override
public Set supportedQueryTypes() {
return Collections.emptySet();
}
@Override
public boolean supportsGetGeneratedKeys() {
return false;
}
private SQLException disabled() {
return FbExceptionBuilder.forException(JaybirdErrorCodes.jb_generatedKeysSupportNotAvailable)
.messageParameter(reasonDisabled)
.toSQLException();
}
}
/**
* Ignores generated keys support and processes queries without producing a generated keys result set.
*
* This ignores requests for generated keys handling and acts as if no query types support generated keys at all.
* This allows executing all queries normally without support for {@code getGeneratedKeys}. This option will
* yield errors when executing statements with a {@code RETURNING} clause with {@code executeUpdate} or
* {@code executeLargeUpdate}.
*
*
* @since 4.0
*/
private static final class IgnoredGeneratedKeysSupport implements GeneratedKeysSupport {
private static final IgnoredGeneratedKeysSupport INSTANCE = new IgnoredGeneratedKeysSupport();
@Override
public Query buildQuery(String sql, int autoGeneratedKeys) {
// NOTE: Violates JDBC spec: allow values other than NO_GENERATED_KEYS and RETURN_GENERATED_KEYS for ignored
return createNonGeneratedKeysQuery(sql);
}
@Override
public Query buildQuery(String sql, int[] columnIndexes) {
return createNonGeneratedKeysQuery(sql);
}
@Override
public Query buildQuery(String sql, String[] columnNames) {
return createNonGeneratedKeysQuery(sql);
}
@Override
public Set supportedQueryTypes() {
return Collections.emptySet();
}
@Override
public boolean supportsGetGeneratedKeys() {
return false;
}
private Query createNonGeneratedKeysQuery(String sql) {
return new Query(false, sql);
}
}
}