Skip to content

Commit

Permalink
#793 Report true for ResultSetMetaData.isAutoIncrement for identity c…
Browse files Browse the repository at this point in the history
…olumns
  • Loading branch information
mrotteveel committed Mar 31, 2024
1 parent 153a01d commit acc7c7e
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 53 deletions.
1 change: 1 addition & 0 deletions src/docs/asciidoc/release_notes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,7 @@ We do not plan to remove this API at this time, but we recommend that you switch
+
This change was also backported to Jaybird 5.0.4.
* Improvement: `sessionTimeZone` now also accepts the Java offset names (`GMT[{plus}-]HH:MM`), which will be automatically converted to the Firebird compatible name (`[{plus}-]HH:MM`).
* 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])
[#potentially-breaking-changes]
=== Potentially breaking changes
Expand Down
6 changes: 3 additions & 3 deletions src/main/org/firebirdsql/jdbc/AbstractFieldMetaData.java
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,9 @@ protected final ExtendedFieldInfo getExtFieldInfo(int columnIndex) throws SQLExc
/**
* Stores additional information about fields in a database.
*/
protected record ExtendedFieldInfo(FieldKey fieldKey, int fieldPrecision) {
public ExtendedFieldInfo(String relationName, String fieldName, int precision) {
this(new FieldKey(relationName, fieldName), precision);
protected record ExtendedFieldInfo(FieldKey fieldKey, int fieldPrecision, boolean autoIncrement) {
public ExtendedFieldInfo(String relationName, String fieldName, int precision, boolean autoIncrement) {
this(new FieldKey(relationName, fieldName), precision, autoIncrement);
}
}

Expand Down
49 changes: 35 additions & 14 deletions src/main/org/firebirdsql/jdbc/FBResultSetMetaData.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,16 @@ public int getColumnCount() throws SQLException {

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

/**
Expand Down Expand Up @@ -254,12 +257,24 @@ public String getColumnClassName(int column) throws SQLException {
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;

private static final String GET_FIELD_INFO_25 = """
select
RF.RDB$RELATION_NAME as RELATION_NAME,
RF.RDB$FIELD_NAME as FIELD_NAME,
F.RDB$FIELD_PRECISION as FIELD_PRECISION,
'F' as FIELD_AUTO_INC
from RDB$RELATION_FIELDS RF inner join RDB$FIELDS F
on RF.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME
where RF.RDB$FIELD_NAME = ? and RF.RDB$RELATION_NAME = ?""";

private static final String GET_FIELD_INFO = """
private static final String GET_FIELD_INFO_30 = """
select
RF.RDB$RELATION_NAME as RELATION_NAME,
RF.RDB$FIELD_NAME as FIELD_NAME,
F.RDB$FIELD_PRECISION as FIELD_PRECISION
F.RDB$FIELD_PRECISION as FIELD_PRECISION,
RF.RDB$IDENTITY_TYPE is not null as FIELD_AUTO_INC
from RDB$RELATION_FIELDS RF inner join RDB$FIELDS F
on RF.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME
where RF.RDB$FIELD_NAME = ? and RF.RDB$RELATION_NAME = ?""";
Expand All @@ -279,13 +294,15 @@ protected Map<FieldKey, ExtendedFieldInfo> getExtendedFieldInfo(FBConnection con
FBDatabaseMetaData metaData = (FBDatabaseMetaData) connection.getMetaData();
var params = new ArrayList<String>();
var 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);

for (int unionCount = 0; currentColumn <= fieldCount && unionCount < MAX_FIELD_INFO_UNIONS; currentColumn++) {
FieldDescriptor fieldDescriptor = getFieldDescriptor(currentColumn);
if (!needsExtendedFieldInfo(fieldDescriptor)) continue;
if (!needsExtendedFieldInfo(fieldDescriptor, fb3OrHigher)) continue;

String relationName = fieldDescriptor.getOriginalTableName();
String fieldName = fieldDescriptor.getOriginalName();
Expand All @@ -295,7 +312,7 @@ protected Map<FieldKey, ExtendedFieldInfo> getExtendedFieldInfo(FBConnection con
if (unionCount != 0) {
sb.append("\nunion all\n");
}
sb.append(GET_FIELD_INFO);
sb.append(getFieldInfoQuery);

params.add(fieldName);
params.add(relationName);
Expand All @@ -307,7 +324,7 @@ protected Map<FieldKey, ExtendedFieldInfo> getExtendedFieldInfo(FBConnection con

try (ResultSet rs = metaData.doQuery(sb.toString(), params, true)) {
while (rs.next()) {
var fieldInfo = extractExtendedFieldInfo(rs);
ExtendedFieldInfo fieldInfo = extractExtendedFieldInfo(rs);
result.put(fieldInfo.fieldKey(), fieldInfo);
}
}
Expand All @@ -317,15 +334,19 @@ protected Map<FieldKey, ExtendedFieldInfo> getExtendedFieldInfo(FBConnection con

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.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)
* @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) {
int jdbcType = JdbcTypeConverter.toJdbcType(fieldDescriptor);
return jdbcType == Types.NUMERIC || jdbcType == Types.DECIMAL;
private static boolean needsExtendedFieldInfo(FieldDescriptor fieldDescriptor, boolean fb3OrHigher) {
return switch (JdbcTypeConverter.toJdbcType(fieldDescriptor)) {
case Types.NUMERIC, Types.DECIMAL -> true;
case Types.INTEGER, Types.BIGINT, Types.SMALLINT -> fb3OrHigher;
default -> false;
};
}

/**
Expand Down
103 changes: 67 additions & 36 deletions src/test/org/firebirdsql/jdbc/FBResultSetMetaDataTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import org.firebirdsql.util.FirebirdSupportInfo;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

import javax.sql.rowset.CachedRowSet;
import javax.sql.rowset.RowSetFactory;
Expand All @@ -35,10 +37,13 @@
import java.util.Properties;

import static org.firebirdsql.common.FBTestProperties.*;
import static org.firebirdsql.common.FbAssumptions.assumeFeature;
import static org.firebirdsql.gds.ISCConstants.SQL_DOUBLE;
import static org.firebirdsql.gds.ISCConstants.SQL_FLOAT;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

Expand All @@ -50,30 +55,35 @@
*/
class FBResultSetMetaDataTest {

//@formatter:off
private static final String CREATE_TABLE =
"CREATE TABLE test_rs_metadata (" +
" id INTEGER NOT NULL PRIMARY KEY, " +
" simple_field VARCHAR(60) CHARACTER SET WIN1250, " +
" two_byte_field VARCHAR(60) CHARACTER SET BIG_5, " +
" three_byte_field VARCHAR(60) CHARACTER SET UNICODE_FSS, " +
" long_field NUMERIC(15, 2), " +
" int_field NUMERIC(8, 2), " +
" short_field NUMERIC(4, 2), " +
" char_octets_field CHAR(10) CHARACTER SET OCTETS, " +
" varchar_octets_field VARCHAR(15) CHARACTER SET OCTETS, " +
" calculated_field computed by (int_field + short_field) " +
")";
private static final String CREATE_TABLE = """
CREATE TABLE test_rs_metadata (
id INTEGER NOT NULL PRIMARY KEY,
simple_field VARCHAR(60) CHARACTER SET WIN1250,
two_byte_field VARCHAR(60) CHARACTER SET BIG_5,
three_byte_field VARCHAR(60) CHARACTER SET UNICODE_FSS,
long_field NUMERIC(15, 2),
int_field NUMERIC(8, 2),
short_field NUMERIC(4, 2),
char_octets_field CHAR(10) CHARACTER SET OCTETS,
varchar_octets_field VARCHAR(15) CHARACTER SET OCTETS,
calculated_field computed by (int_field + short_field)
)""";

private static final String TEST_QUERY =
"SELECT " +
"simple_field, two_byte_field, three_byte_field, " +
"long_field, int_field, short_field " +
"FROM test_rs_metadata";
private static final String TEST_QUERY = """
SELECT
simple_field, two_byte_field, three_byte_field,
long_field, int_field, short_field
FROM test_rs_metadata""";

private static final String RECREATE_AUTO_INC_TABLE_TEMPLATE = """
recreate table TEST_AUTO_INC_RS_METADATA (
ID %s generated always as identity primary key
)
""";

private static final String TEST_QUERY_AUTO_INC = "select ID from TEST_AUTO_INC_RS_METADATA";

private static final String TEST_QUERY2 =
"SELECT * from RDB$DATABASE";
//@formatter:on
private static final String TEST_QUERY2 = "SELECT * from RDB$DATABASE";

@RegisterExtension
static final UsesDatabaseExtension.UsesDatabaseForAll usesDatabase = UsesDatabaseExtension.usesDatabaseForAll(
Expand All @@ -95,6 +105,11 @@ void testResultSetMetaData() throws Exception {
assertEquals(15, metaData.getPrecision(4), "long_field must have precision 15");
assertEquals(8, metaData.getPrecision(5), "int_field must have precision 8");
assertEquals(4, metaData.getPrecision(6), "short_field must have precision 4");

for (int idx = 1; idx <= 6; idx++) {
assertFalse(metaData.isAutoIncrement(idx),
"Expected autoIncrement is false for " + metaData.getColumnName(idx));
}
}
}

Expand Down Expand Up @@ -339,20 +354,21 @@ void core5713() throws Exception {
assumeTrue(supportInfo.isVersionEqualOrAbove(3, 0, 3), "Test for CORE-5713, broken in version before 3.0.3");
try (Connection connection = getConnectionViaDriverManager();
Statement stmt = connection.createStatement()) {
for (String query : new String[] {
"select 1 a1, 2 a2\n"
+ "from rdb$database\n"
+ "union all\n"
+ "select 1 a1, coalesce(cast(null as varchar(64)), 0) a2\n"
+ "from rdb$database ",
"select a1, a2\n"
+ "from\n"
+ " (select 1 a1, 2 a2\n"
+ " from rdb$database)\n"
+ "group by 1, 2\n"
+ "union all\n"
+ "select 1 a1, coalesce(cast(null as varchar(64)), 0) a2\n"
+ "from rdb$database"
for (String query : new String[] { """
select 1 a1, 2 a2
from rdb$database
union all
select 1 a1, coalesce(cast(null as varchar(64)), 0) a2
from rdb$database""",
"""
select a1, a2
from
(select 1 a1, 2 a2
from rdb$database)
group by 1, 2
union all
select 1 a1, coalesce(cast(null as varchar(64)), 0) a2
from rdb$database"""
}) {
try (ResultSet rs = stmt.executeQuery(query)) {
FirebirdResultSetMetaData rsmd = rs.getMetaData().unwrap(FirebirdResultSetMetaData.class);
Expand Down Expand Up @@ -409,4 +425,19 @@ void extendedFieldInfo_moreThan70Columns_731() throws Exception {
}
}
}

@ParameterizedTest
@EnumSource(value = JDBCType.class, names = { "SMALLINT", "INTEGER", "BIGINT" })
void isAutoIncrement_identityColumn(JDBCType columnType) throws Exception {
assumeFeature(FirebirdSupportInfo::supportsIdentityColumns, "Test requires identity column support");
try (var connection = getConnectionViaDriverManager();
var stmt = connection.createStatement()) {
stmt.execute(RECREATE_AUTO_INC_TABLE_TEMPLATE.formatted(columnType.name()));

var rs = stmt.executeQuery(TEST_QUERY_AUTO_INC);
ResultSetMetaData rsmd = rs.getMetaData();
assertTrue(rsmd.isAutoIncrement(1), "Expected autoIncrement true");
}
}

}

0 comments on commit acc7c7e

Please sign in to comment.