Skip to content

Commit

Permalink
#796 Backport #731, #732, #793 and #795 to Jaybird 5
Browse files Browse the repository at this point in the history
Includes:
#731 FBResultSetMetaData.getExtendedFieldInfo will query same set of fields when there are more than 70 fields
#732 Optimize FBResultSetMetaData.getExtendedFieldInfo
#793 Report true for ResultSetMetaData.isAutoIncrement for identity columns
#795 Add connection property to disable retrieval of extended field info for ResultSetMetaData
#795 Implement disabling of retrieval of extended field info for ResultSetMetaData
  • Loading branch information
mrotteveel committed Apr 1, 2024
1 parent 1ecccac commit eec6fcb
Show file tree
Hide file tree
Showing 12 changed files with 280 additions and 45 deletions.
14 changes: 14 additions & 0 deletions src/docs/asciidoc/release_notes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,23 @@ For known issues, consult <<known-issues>>.

The following has been changed or fixed since Jaybird 5.0.4:

* Fixed: `FBResultSetMetaData.getPrecision` would always estimate the precision of `NUMERIC` or `DECIMAL` columns instead of obtaining the actual precision if the column position was 71 or higher (https://github.com/FirebirdSQL/jaybird/issues/731[#731])
+
This fix was backported from Jaybird 6.
* Optimized the query to retrieve extended field info for `ResultSetMetaData.getPrecision` to only retrieve columns of type `NUMERIC` or `DECIMAL` (https://github.com/FirebirdSQL/jaybird/issues/732[#732])
+
This improvement was backported from Jaybird 6.
* Fixed: `PreparedStatement.executeBatch()` of statement without parameters throws "`Statement used in batch must have parameters [SQLState:07001, ISC error code:335545186]`" on Firebird 4.0 or higher (https://github.com/FirebirdSQL/jaybird/issues/788[#788])
+
Given the Firebird server-side batch facility doesn't support executing parameterless statements, the implementation now falls back to emulated batch execution for this case.
* New feature: `ResultSetMetaData.isAutoIncrement(int)` reports `true` for identity columns *if* Jaybird can identify the underlying table and column (https://github.com/FirebirdSQL/jaybird/issues/793[#793])
+
This feature was backported from Jaybird 6.
* New feature: Boolean connection property `extendedMetadata` (default `true`) to disable querying of extended metadata for `getPrecision(int)` and `isAutoIncrement(int)` of `ResultSetMetaData` (https://github.com/FirebirdSQL/jaybird/issues/795[#795])
+
Disabling extended metadata may improve performance of these `ResultSetMetaData` methods in exchange for estimated precision information of `NUMERIC` and `DECIMAL` columns, and not being able to determine the auto-increment status of `INTEGER`, `BIGINT` or `SMALLINT` columns.
+
This feature was backported from Jaybird 6.
* ...
[#jaybird-5-0-4-changelog]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,16 @@ public void setParallelWorkers(int parallelWorkers) {
FirebirdConnectionProperties.super.setParallelWorkers(parallelWorkers);
}

@Override
public boolean isExtendedMetadata() {
return FirebirdConnectionProperties.super.isExtendedMetadata();
}

@Override
public void setExtendedMetadata(boolean extendedMetadata) {
FirebirdConnectionProperties.super.setExtendedMetadata(extendedMetadata);
}

@SuppressWarnings("deprecation")
@Deprecated
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -567,4 +567,35 @@ default void setServerBatchBufferSize(int serverBatchBufferSize) {
setIntProperty(PropertyNames.serverBatchBufferSize, serverBatchBufferSize);
}

/**
* @return {@code true} (default) if metadata (e.g. {@code ResultSetMetaData}) will perform additional queries for
* more detailed information, {@code false} if only the available bind information will be used
* @see #setExtendedMetadata(boolean)
* @since 5.0.5
*/
default boolean isExtendedMetadata() {
return getBooleanProperty(PropertyNames.extendedMetadata, PropertyConstants.DEFAULT_EXTENDED_METADATA);
}

/**
* Sets if certain metadata classes will perform additional queries to enrich the information for certain types.
* <p>
* Currently this is used only by {@code ResultSetMetaData} for its {@code getPrecision} and {@code isAutoIncrement}
* methods. If disabled, these methods will return an estimated precision, or {@code false} for auto-increment
* instead of actual precision and identity column state information.
* </p>
* <p>
* Disabling this setting may improve performance of querying metadata information, in exchange for less precise
* information.
* </p>
*
* @param extendedMetadata
* {@code true} (default) - metadata (e.g. {@code ResultSetMetaData}) will perform additional queries for
* more detailed information, {@code false} - only the available bind information will be used
* @since 5.0.5
*/
default void setExtendedMetadata(boolean extendedMetadata) {
setBooleanProperty(PropertyNames.extendedMetadata, extendedMetadata);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public final class PropertyConstants {
static final boolean DEFAULT_USE_SERVER_BATCH = true;
public static final int DEFAULT_SERVER_BATCH_BUFFER_SIZE = 0;
static final boolean DEFAULT_TIMESTAMP_USES_LOCAL = false;
static final boolean DEFAULT_EXTENDED_METADATA = true;

public static final int DEFAULT_TRANSACTION_ISOLATION_VALUE = Connection.TRANSACTION_READ_COMMITTED;
public static final String DEFAULT_TRANSACTION_ISOLATION_NAME = TransactionNameMapping.TRANSACTION_READ_COMMITTED;
Expand Down
1 change: 1 addition & 0 deletions src/main/org/firebirdsql/jaybird/props/PropertyNames.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public final class PropertyNames {
public static final String scrollableCursor = "scrollableCursor";
public static final String useServerBatch = "useServerBatch";
public static final String serverBatchBufferSize = "serverBatchBufferSize";
public static final String extendedMetadata = "extendedMetadata";

// service connection
public static final String expectedDb = "expectedDb";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ public Stream<ConnectionProperty> defineProperties() {
builder(scrollableCursor).choices(SCROLLABLE_CURSOR_EMULATED, SCROLLABLE_CURSOR_SERVER),
builder(useServerBatch).type(BOOLEAN),
builder(serverBatchBufferSize).type(INT),
builder(extendedMetadata).type(BOOLEAN),
// TODO Property should be considered deprecated, remove in Jaybird 6
builder("timestampUsesLocalTimezone").type(BOOLEAN).aliases("timestamp_uses_local_timezone"),

Expand Down
4 changes: 3 additions & 1 deletion src/main/org/firebirdsql/jdbc/AbstractFieldMetaData.java
Original file line number Diff line number Diff line change
Expand Up @@ -438,10 +438,12 @@ protected final ExtendedFieldInfo getExtFieldInfo(int columnIndex) throws SQLExc
protected static class ExtendedFieldInfo {
final FieldKey fieldKey;
final int fieldPrecision;
final boolean autoIncrement;

public ExtendedFieldInfo(String relationName, String fieldName, int precision) {
public ExtendedFieldInfo(String relationName, String fieldName, int precision, boolean autoIncrement) {
fieldKey = new FieldKey(relationName, fieldName);
fieldPrecision = precision;
this.autoIncrement = autoIncrement;
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/main/org/firebirdsql/jdbc/FBConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -1348,4 +1348,9 @@ int getServerBatchBufferSize() {
return props != null ? props.getServerBatchBufferSize() : PropertyConstants.DEFAULT_SERVER_BATCH_BUFFER_SIZE;
}

boolean isExtendedMetadata() {
DatabaseConnectionProperties props = connectionProperties();
return props != null && props.isExtendedMetadata();
}

}
130 changes: 86 additions & 44 deletions src/main/org/firebirdsql/jdbc/FBResultSetMetaData.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@
import org.firebirdsql.gds.impl.GDSHelper;
import org.firebirdsql.gds.ng.fields.FieldDescriptor;
import org.firebirdsql.gds.ng.fields.RowDescriptor;
import org.firebirdsql.jdbc.field.JdbcTypeConverter;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.util.*;

import static org.firebirdsql.util.StringUtils.isNullOrEmpty;

/**
* An object that can be used to get information about the types and properties of the columns in
* a <code>ResultSet</code> object.
Expand Down Expand Up @@ -80,13 +83,18 @@ public int getColumnCount() throws SQLException {

/**
* {@inheritDoc}
* <p>
* The current implementation always returns <code>false</code>.
* </p>
*/
@Override
public boolean isAutoIncrement(int column) throws SQLException {
return false;
switch (getColumnType(column)) {
case Types.SMALLINT:
case Types.INTEGER:
case Types.BIGINT:
ExtendedFieldInfo extFieldInfo = getExtFieldInfo(column);
return extFieldInfo != null && extFieldInfo.autoIncrement;
default:
return false;
}
}

/**
Expand Down Expand Up @@ -259,74 +267,108 @@ public String getColumnClassName(int column) throws SQLException {
return getFieldClassName(column);
}

private static final int FIELD_INFO_RELATION_NAME = 1;
private static final int FIELD_INFO_FIELD_NAME = 2;
private static final int FIELD_INFO_FIELD_PRECISION = 3;
private static final int FIELD_INFO_FIELD_AUTO_INC = 4;

//@formatter:off
private static final String GET_FIELD_INFO =
"SELECT "
+ " RF.RDB$RELATION_NAME as RELATION_NAME"
+ ", RF.RDB$FIELD_NAME as FIELD_NAME"
+ ", F.RDB$FIELD_PRECISION as FIELD_PRECISION"
+ " FROM"
+ " RDB$RELATION_FIELDS RF "
+ ", RDB$FIELDS F "
+ " WHERE "
+ " RF.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME"
+ " AND"
+ " RF.RDB$FIELD_NAME = ?"
+ " AND"
+ " RF.RDB$RELATION_NAME = ?";
private static final String GET_FIELD_INFO_25 =
"select\n" +
" RF.RDB$RELATION_NAME as RELATION_NAME,\n" +
" RF.RDB$FIELD_NAME as FIELD_NAME,\n" +
" F.RDB$FIELD_PRECISION as FIELD_PRECISION,\n" +
" 'F' as FIELD_AUTO_INC\n" +
"from RDB$RELATION_FIELDS RF inner join RDB$FIELDS F\n" +
" on RF.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME\n" +
"where RF.RDB$FIELD_NAME = ? and RF.RDB$RELATION_NAME = ?";;

private static final String GET_FIELD_INFO_30 =
"select\n" +
" RF.RDB$RELATION_NAME as RELATION_NAME,\n" +
" RF.RDB$FIELD_NAME as FIELD_NAME,\n" +
" F.RDB$FIELD_PRECISION as FIELD_PRECISION,\n" +
" RF.RDB$IDENTITY_TYPE is not null as FIELD_AUTO_INC\n" +
"from RDB$RELATION_FIELDS RF inner join RDB$FIELDS F\n" +
" on RF.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME\n" +
"where RF.RDB$FIELD_NAME = ? and RF.RDB$RELATION_NAME = ?";
//@formatter:on

// Apparently there is a limit in the UNION. It is necessary to split in several queries. Although the problem
// reported with 93 UNION, use only 70.
private static final int MAX_FIELD_INFO_UNIONS = 70;

@Override
protected Map<FieldKey, ExtendedFieldInfo> getExtendedFieldInfo(FBConnection connection) throws SQLException {
if (connection == null) return Collections.emptyMap();
if (connection == null || !connection.isExtendedMetadata()) return Collections.emptyMap();

// Apparently there is a limit in the UNION
// It is necessary to split in several queries
// Although the problem reported with 93 UNION use only 70
int pending = getFieldCount();
final int fieldCount = getFieldCount();
int currentColumn = 1;
Map<FieldKey, ExtendedFieldInfo> result = new HashMap<>();
final FBDatabaseMetaData metaData = (FBDatabaseMetaData) connection.getMetaData();
while (pending > 0) {
StringBuilder sb = new StringBuilder();
FBDatabaseMetaData metaData = (FBDatabaseMetaData) connection.getMetaData();
List<String> params = new ArrayList<>();
StringBuilder sb = new StringBuilder();
boolean fb3OrHigher = metaData.getDatabaseMajorVersion() >= 3;
String getFieldInfoQuery = fb3OrHigher ? GET_FIELD_INFO_30 : GET_FIELD_INFO_25;
while (currentColumn <= fieldCount) {
params.clear();
sb.setLength(0);

int maxLength = Math.min(pending, 70);
List<String> params = new ArrayList<>(2 * maxLength);
for (int i = 1; i <= maxLength; i++) {
for (int unionCount = 0; currentColumn <= fieldCount && unionCount < MAX_FIELD_INFO_UNIONS; currentColumn++) {
FieldDescriptor fieldDescriptor = getFieldDescriptor(currentColumn);
if (!needsExtendedFieldInfo(fieldDescriptor, fb3OrHigher)) continue;

String relationName = getFieldDescriptor(i).getOriginalTableName();
String fieldName = getFieldDescriptor(i).getOriginalName();
String relationName = fieldDescriptor.getOriginalTableName();
String fieldName = fieldDescriptor.getOriginalName();

if (relationName == null || relationName.equals("")
|| fieldName == null || fieldName.equals("")) continue;
if (isNullOrEmpty(relationName) || isNullOrEmpty(fieldName)) continue;

if (sb.length() > 0) {
sb.append('\n').append("UNION ALL").append('\n');
if (unionCount != 0) {
sb.append("\nunion all\n");
}
sb.append(GET_FIELD_INFO);
sb.append(getFieldInfoQuery);

params.add(fieldName);
params.add(relationName);
}

pending -= maxLength;
unionCount++;
}

if (sb.length() == 0) continue;

try (ResultSet rs = metaData.doQuery(sb.toString(), params, true)) {
while (rs.next()) {
String relationName = rs.getString("RELATION_NAME");
String fieldName = rs.getString("FIELD_NAME");
int precision = rs.getInt("FIELD_PRECISION");
ExtendedFieldInfo fieldInfo = new ExtendedFieldInfo(relationName, fieldName, precision);

ExtendedFieldInfo fieldInfo = extractExtendedFieldInfo(rs);
result.put(fieldInfo.fieldKey, fieldInfo);
}
}
params.clear();
}
return result;
}

private static ExtendedFieldInfo extractExtendedFieldInfo(ResultSet rs) throws SQLException {
return new ExtendedFieldInfo(rs.getString(FIELD_INFO_RELATION_NAME), rs.getString(FIELD_INFO_FIELD_NAME),
rs.getInt(FIELD_INFO_FIELD_PRECISION), rs.getBoolean(FIELD_INFO_FIELD_AUTO_INC));
}

/**
* @return {@code true} when the field descriptor needs extended field info (currently only NUMERIC and DECIMAL,
* and - when {@code fb3OrHigher == true} - INTEGER, BIGINT and SMALLINT)
*/
private static boolean needsExtendedFieldInfo(FieldDescriptor fieldDescriptor, boolean fb3OrHigher) {
switch (JdbcTypeConverter.toJdbcType(fieldDescriptor)) {
case Types.NUMERIC:
case Types.DECIMAL:
return true;
case Types.INTEGER:
case Types.BIGINT:
case Types.SMALLINT:
return fb3OrHigher;
default:
return false;
}
}

/**
* Strategy for retrieving column labels and column names
*/
Expand Down
13 changes: 13 additions & 0 deletions src/main/org/firebirdsql/util/StringUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,17 @@ public static String trimToNull(String value) {
}
return null;
}

/**
* Checks if {@code value} is {@code null} or empty.
*
* @param value
* value to test
* @return {@code true} if {@code value} is {@code null} or emoty, {@code false} for non-empty strings
* @since 6
*/
public static boolean isNullOrEmpty(String value) {
return value == null || value.isEmpty();
}

}
Loading

0 comments on commit eec6fcb

Please sign in to comment.