Skip to content

Commit

Permalink
#801 Backport #797 to Jaybird 5
Browse files Browse the repository at this point in the history
  • Loading branch information
mrotteveel committed Apr 3, 2024
1 parent a867cc6 commit 101bdaa
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 52 deletions.
3 changes: 3 additions & 0 deletions src/docs/asciidoc/release_notes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ This feature was backported from Jaybird 6.
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.
* Improvement: The `FILTER_CONDITION` of `DatabaseMetaData.getIndexInfo` is populated for Firebird 5.0 partial indices (https://github.com/FirebirdSQL/jaybird/issues/797[#797])
+
This improvement was backported from Jaybird 6.
* ...
[#jaybird-5-0-4-changelog]
Expand Down
10 changes: 10 additions & 0 deletions src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java
Original file line number Diff line number Diff line change
Expand Up @@ -1325,6 +1325,16 @@ public ResultSet getTypeInfo() throws SQLException {
return GetTypeInfo.create(getDbMetadataMediator()).getTypeInfo();
}

/**
* {@inheritDoc}
* <p>
* <b>Implementation note:</b> The value of {@code FILTER_CONDITION} is populated with the value of
* {@code RDB$INDICES.RDB$CONDITION_SOURCE}, which includes the {@code WHERE} keyword and comments before
* the {@code WHERE} keyword. This is an implementation detail which may change in the future. That is, Jaybird may
* change in the future to only include the condition itself, not the {@code WHERE} keyword, and/or may remove some
* or all comments.
* </p>
*/
@Override
public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate)
throws SQLException {
Expand Down
156 changes: 113 additions & 43 deletions src/main/org/firebirdsql/jdbc/metadata/GetIndexInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.firebirdsql.gds.ng.fields.RowDescriptorBuilder;
import org.firebirdsql.gds.ng.fields.RowValue;
import org.firebirdsql.jdbc.metadata.DbMetadataMediator.MetadataQuery;
import org.firebirdsql.util.FirebirdSupportInfo;

import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
Expand All @@ -32,65 +33,52 @@
import static org.firebirdsql.gds.ISCConstants.SQL_TEXT;
import static org.firebirdsql.gds.ISCConstants.SQL_VARYING;
import static org.firebirdsql.jdbc.metadata.FbMetadataConstants.OBJECT_NAME_LENGTH;
import static org.firebirdsql.util.StringUtils.isNullOrEmpty;

/**
* @author <a href="mailto:[email protected]">Mark Rotteveel</a>
* Provides the implementation of {@link DatabaseMetaData#getIndexInfo(String, String, String, boolean, boolean)}.
*
* @author Mark Rotteveel
*/
public final class GetIndexInfo extends AbstractMetadataMethod {
@SuppressWarnings("java:S1192")
public abstract class GetIndexInfo extends AbstractMetadataMethod {

private static final String INDEXINFO = "INDEXINFO";

private static final RowDescriptor ROW_DESCRIPTOR = new RowDescriptorBuilder(13, DbMetadataMediator.datatypeCoder)
.at(0).simple(SQL_VARYING | 1, OBJECT_NAME_LENGTH, "TABLE_CAT", "INDEXINFO").addField()
.at(1).simple(SQL_VARYING | 1, 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()
.at(0).simple(SQL_VARYING | 1, OBJECT_NAME_LENGTH, "TABLE_CAT", INDEXINFO).addField()
.at(1).simple(SQL_VARYING | 1, 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_TEXT | 1, 1, "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 | 1, 31, "FILTER_CONDITION", "INDEXINFO").addField()
.at(8).simple(SQL_VARYING, Integer.MAX_VALUE, "COLUMN_NAME", INDEXINFO).addField()
.at(9).simple(SQL_TEXT | 1, 1, "ASC_OR_DESC", INDEXINFO).addField()
.at(10).simple(SQL_LONG, 0, "CARDINALITY", INDEXINFO).addField()
.at(11).simple(SQL_LONG, 0, "PAGES", INDEXINFO).addField()
// Field with CONDITION_SOURCE (used for partial indexes) in Firebird is actually a blob, using Integer.MAX_VALUE for length
.at(12).simple(SQL_VARYING | 1, Integer.MAX_VALUE, "FILTER_CONDITION", INDEXINFO).addField()
.toRowDescriptor();

//@formatter:off
private static final String GET_INDEX_INFO_START =
"select\n"
+ " IND.RDB$RELATION_NAME as TABLE_NAME,\n"
+ " IND.RDB$UNIQUE_FLAG as UNIQUE_FLAG,\n"
+ " IND.RDB$INDEX_NAME as INDEX_NAME,\n"
+ " ISE.RDB$FIELD_POSITION + 1 as ORDINAL_POSITION,\n"
+ " ISE.RDB$FIELD_NAME as COLUMN_NAME,\n"
+ " IND.RDB$EXPRESSION_SOURCE as EXPRESSION_SOURCE,\n"
+ " IND.RDB$INDEX_TYPE as ASC_OR_DESC\n"
+ "from RDB$INDICES IND\n"
+ "left join RDB$INDEX_SEGMENTS ISE on IND.RDB$INDEX_NAME = ISE.RDB$INDEX_NAME "
+ "where ";

private static final String GET_INDEX_INFO_END =
"\norder by IND.RDB$UNIQUE_FLAG, IND.RDB$INDEX_NAME, ISE.RDB$FIELD_POSITION";
//@formatter:on

private GetIndexInfo(DbMetadataMediator mediator) {
super(ROW_DESCRIPTOR, mediator);
}

@SuppressWarnings("unused")
public ResultSet getIndexInfo(String table, boolean unique, boolean approximate) throws SQLException {
if (table == null || "".equals(table)) {
if (isNullOrEmpty(table)) {
return createEmpty();
}

Clause tableClause = Clause.equalsClause("IND.RDB$RELATION_NAME", table);
String sql = GET_INDEX_INFO_START
+ tableClause.getCondition(unique)
+ (unique ? "IND.RDB$UNIQUE_FLAG = 1" : "")
+ GET_INDEX_INFO_END;
MetadataQuery metadataQuery = new MetadataQuery(sql, Clause.parameters(tableClause));
MetadataQuery metadataQuery = createIndexInfoQuery(table, unique);
return createMetaDataResultSet(metadataQuery);
}

abstract MetadataQuery createIndexInfoQuery(String table, boolean unique);

@Override
RowValue createMetadataRow(ResultSet rs, RowValueBuilder valueBuilder) throws SQLException {
valueBuilder
Expand Down Expand Up @@ -128,13 +116,95 @@ RowValue createMetadataRow(ResultSet rs, RowValueBuilder valueBuilder) throws SQ
.at(10).set(null)
// TODO index 11: query RDB$PAGES for PAGES information?
.at(11).set(null)
// Firebird has no filtered indexes
.at(12).set(null);
.at(12).setString(rs.getString("CONDITION_SOURCE"));

return valueBuilder.toRowValue(false);
}

public static GetIndexInfo create(DbMetadataMediator mediator) {
return new GetIndexInfo(mediator);
FirebirdSupportInfo firebirdSupportInfo = mediator.getFirebirdSupportInfo();
// NOTE: Indirection through static method prevents unnecessary classloading
if (firebirdSupportInfo.isVersionEqualOrAbove(5, 0)) {
return FB5.createInstance(mediator);
} else {
return FB2_5.createInstance(mediator);
}
}

private static final class FB2_5 extends GetIndexInfo {

private static final String GET_INDEX_INFO_START_2_5 =
"select\n" +
" IND.RDB$RELATION_NAME as TABLE_NAME,\n" +
" IND.RDB$UNIQUE_FLAG as UNIQUE_FLAG,\n" +
" IND.RDB$INDEX_NAME as INDEX_NAME,\n" +
" ISE.RDB$FIELD_POSITION + 1 as ORDINAL_POSITION,\n" +
" ISE.RDB$FIELD_NAME as COLUMN_NAME,\n" +
" IND.RDB$EXPRESSION_SOURCE as EXPRESSION_SOURCE,\n" +
" IND.RDB$INDEX_TYPE as ASC_OR_DESC,\n" +
" null as CONDITION_SOURCE\n" +
"from RDB$INDICES IND\n" +
"left join RDB$INDEX_SEGMENTS ISE on IND.RDB$INDEX_NAME = ISE.RDB$INDEX_NAME where ";

private static final String GET_INDEX_INFO_END_2_5 =
"\norder by IND.RDB$UNIQUE_FLAG, IND.RDB$INDEX_NAME, ISE.RDB$FIELD_POSITION";

private FB2_5(DbMetadataMediator mediator) {
super(mediator);
}

private static GetIndexInfo createInstance(DbMetadataMediator mediator) {
return new FB2_5(mediator);
}

@Override
MetadataQuery createIndexInfoQuery(String table, boolean unique) {
Clause tableClause = Clause.equalsClause("IND.RDB$RELATION_NAME", table);
String sql = GET_INDEX_INFO_START_2_5
+ tableClause.getCondition(unique)
+ (unique ? "IND.RDB$UNIQUE_FLAG = 1" : "")
+ GET_INDEX_INFO_END_2_5;
return new MetadataQuery(sql, Clause.parameters(tableClause));
}

}

private static final class FB5 extends GetIndexInfo {

private static final String GET_INDEX_INFO_START_5 =
"select\n" +
" trim(trailing from IND.RDB$RELATION_NAME) as TABLE_NAME,\n" +
" IND.RDB$UNIQUE_FLAG as UNIQUE_FLAG,\n" +
" trim(trailing from IND.RDB$INDEX_NAME) as INDEX_NAME,\n" +
" ISE.RDB$FIELD_POSITION + 1 as ORDINAL_POSITION,\n" +
" trim(trailing from ISE.RDB$FIELD_NAME) as COLUMN_NAME,\n" +
" IND.RDB$EXPRESSION_SOURCE as EXPRESSION_SOURCE,\n" +
" IND.RDB$INDEX_TYPE as ASC_OR_DESC,\n" +
" IND.RDB$CONDITION_SOURCE as CONDITION_SOURCE\n" +
"from RDB$INDICES IND\n" +
"left join RDB$INDEX_SEGMENTS ISE on IND.RDB$INDEX_NAME = ISE.RDB$INDEX_NAME where ";

private static final String GET_INDEX_INFO_END_5 =
"\norder by IND.RDB$UNIQUE_FLAG, IND.RDB$INDEX_NAME, ISE.RDB$FIELD_POSITION";

private FB5(DbMetadataMediator mediator) {
super(mediator);
}

private static GetIndexInfo createInstance(DbMetadataMediator mediator) {
return new FB5(mediator);
}

@Override
MetadataQuery createIndexInfoQuery(String table, boolean unique) {
Clause tableClause = Clause.equalsClause("IND.RDB$RELATION_NAME", table);
String sql = GET_INDEX_INFO_START_5
+ tableClause.getCondition(unique)
+ (unique ? "IND.RDB$UNIQUE_FLAG = 1" : "")
+ GET_INDEX_INFO_END_5;
return new MetadataQuery(sql, Clause.parameters(tableClause));
}

}

}
7 changes: 7 additions & 0 deletions src/main/org/firebirdsql/util/FirebirdSupportInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,13 @@ public boolean supportsFixIcu() {
return isVersionEqualOrAbove(3, 0);
}

/**
* @return {@code true} if partial indices are supported
*/
public boolean supportsPartialIndices() {
return isVersionEqualOrAbove(5, 0);
}

/**
* @return {@code true} if the default ODS of this Firebird version has column {@code RDB$PROCEDURE_TYPE}
*/
Expand Down
51 changes: 42 additions & 9 deletions src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataIndexInfoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import java.util.*;

import static org.firebirdsql.common.FBTestProperties.getConnectionViaDriverManager;
import static org.firebirdsql.common.FBTestProperties.getDefaultSupportInfo;
import static org.firebirdsql.common.JdbcResourceHelper.closeQuietly;
import static org.junit.jupiter.api.Assertions.assertEquals;

Expand Down Expand Up @@ -81,22 +82,17 @@ class FBDatabaseMetaDataIndexInfoTest {

private static final String CREATE_UQ_DESC_IDX_TBL_2_COL3_AND_COL2 =
"CREATE UNIQUE DESCENDING INDEX uq_desc_idx_tbl2_col3_col2 ON index_test_table_2 (column3, column2)";

private static final String CREATE_PARTIAL_IDX_TBL_2 =
"create index IDX_PARTIAL_IDX_TBL_2 on INDEX_TEST_TABLE_2 (COLUMN1) where COLUMN2 is not null";
//@formatter:on

private static final MetaDataTestSupport<IndexInfoMetaData> metaDataTestSupport =
new MetaDataTestSupport<>(IndexInfoMetaData.class);

@RegisterExtension
static final UsesDatabaseExtension.UsesDatabaseForAll usesDatabase = UsesDatabaseExtension.usesDatabaseForAll(
CREATE_INDEX_TEST_TABLE_1,
CREATE_INDEX_TEST_TABLE_2,
CREATE_COMPUTED_IDX_TBL_1,
CREATE_UQ_COMPUTED_IDX_TBL_1,
CREATE_ASC_IDX_TBL_1_COLUMN2,
CREATE_DESC_IDX_TBL_2_ID,
CREATE_IDX_TBL_2_COL1_AND_2,
CREATE_DESC_COMPUTED_IDX_TBL_2,
CREATE_UQ_DESC_IDX_TBL_2_COL3_AND_COL2);
getCreateStatements());

private static Connection con;
private static DatabaseMetaData dbmd;
Expand All @@ -117,6 +113,23 @@ static void tearDownAll() throws SQLException {
}
}

private static List<String> getCreateStatements() {
List<String> statements = new ArrayList<>(Arrays.asList(
CREATE_INDEX_TEST_TABLE_1,
CREATE_INDEX_TEST_TABLE_2,
CREATE_COMPUTED_IDX_TBL_1,
CREATE_UQ_COMPUTED_IDX_TBL_1,
CREATE_ASC_IDX_TBL_1_COLUMN2,
CREATE_DESC_IDX_TBL_2_ID,
CREATE_IDX_TBL_2_COL1_AND_2,
CREATE_DESC_COMPUTED_IDX_TBL_2,
CREATE_UQ_DESC_IDX_TBL_2_COL3_AND_COL2));
if (getDefaultSupportInfo().supportsPartialIndices()) {
statements.add(CREATE_PARTIAL_IDX_TBL_2);
}
return statements;
}

/**
* Tests the ordinal positions and types for the metadata columns of
* getIndexInfo().
Expand Down Expand Up @@ -181,6 +194,11 @@ void testIndexInfo_table2_all() throws Exception {
expectedIndexInfo.add(createRule(tableName, true, "CMP_IDX_DESC_TEST_TABLE2", "(UPPER(column1))", 1, false));
expectedIndexInfo.add(createRule(tableName, true, "FK_IDX_TEST_2_COLUMN2_TEST_1", "COLUMN2", 1, true));
expectedIndexInfo.add(createRule(tableName, true, "IDX_DESC_IDX_TBL2_ID", "ID", 1, false));
if (getDefaultSupportInfo().supportsPartialIndices()) {
expectedIndexInfo.add(withFilterCondition(
createRule(tableName, true, "IDX_PARTIAL_IDX_TBL_2", "COLUMN1", 1, true),
"where COLUMN2 is not null"));
}
expectedIndexInfo.add(createRule(tableName, true, "IDX_TBL_2_COL1_COL2", "COLUMN1", 1, true));
expectedIndexInfo.add(createRule(tableName, true, "IDX_TBL_2_COL1_COL2", "COLUMN2", 2, true));
expectedIndexInfo.add(createRule(tableName, false, "PK_IDX_TEST_2_ID", "ID", 1, true));
Expand Down Expand Up @@ -241,6 +259,21 @@ private Map<IndexInfoMetaData, Object> createRule(String tableName, boolean nonU
indexRules.put(IndexInfoMetaData.ASC_OR_DESC, ascending ? "A" : "D");
return indexRules;
}

/**
* Updates {@code rule}, populating its {@code FILTER_CONDITION} rule.
*
* @param rule
* rule to update
* @param filterCondition
* filter condition value
* @return {@code rule}
*/
private Map<IndexInfoMetaData, Object> withFilterCondition(Map<IndexInfoMetaData, Object> rule,
String filterCondition) {
rule.put(IndexInfoMetaData.FILTER_CONDITION, filterCondition);
return rule;
}

private static final Map<IndexInfoMetaData, Object> DEFAULT_COLUMN_VALUES;
static {
Expand Down

0 comments on commit 101bdaa

Please sign in to comment.