Skip to content

Commit be8e146

Browse files
committed
Implement CassandraDatabaseMetaData.getFunctionColumns()
1 parent 96eafa2 commit be8e146

File tree

5 files changed

+233
-8
lines changed

5 files changed

+233
-8
lines changed

CHANGELOG.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1616
- Implement the methods `isSigned()` and `isSearchable()` in the different `ResultSetMetaData` implementations.
1717
- Implement the method `isValid(int)` in `CassandraConnection`.
1818
- Implement the following methods of `CassandraDatabaseMetaData`: `getFunctions(String, String, String)`,
19-
`getNumericFunctions()`, `getSQLKeywords()`, `getStringFunctions()`, `getSystemFunctions()`, `getTimeDateFunctions()`,
20-
`getTypeInfo()` and `getUDTs(String, String, String, int[])`.
19+
`getFunctionColumns(String, String, String, String)`, `getNumericFunctions()`, `getSQLKeywords()`,
20+
`getStringFunctions()`, `getSystemFunctions()`, `getTimeDateFunctions()`, `getTypeInfo()` and
21+
`getUDTs(String, String, String, int[])`.
2122
### Changed
2223
- Harmonize the implementations of `Wrapper` interface.
2324
- Rewrite the tests using Testcontainers with Apache Cassandra(R) 4.1.0 image.

src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -328,8 +328,15 @@ public String getExtraNameCharacters() {
328328
public ResultSet getFunctionColumns(final String catalog, final String schemaPattern,
329329
final String functionNamePattern, final String columnNamePattern)
330330
throws SQLException {
331-
// TODO: method to implement
332331
checkStatementClosed();
332+
if (catalog == null || catalog.equals(this.connection.getCatalog())) {
333+
String schemaName = schemaPattern;
334+
if (schemaPattern == null) {
335+
schemaName = this.connection.getSchema(); // limit to current schema if defined.
336+
}
337+
return MetadataResultSets.INSTANCE.makeFunctionColumns(this.statement, schemaName, functionNamePattern,
338+
columnNamePattern);
339+
}
333340
return CassandraResultSet.EMPTY_RESULT_SET;
334341
}
335342

src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java

+175-4
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,14 @@
3434
import java.util.ArrayList;
3535
import java.util.Arrays;
3636
import java.util.Comparator;
37+
import java.util.List;
3738
import java.util.Map;
3839

3940
import static com.ing.data.cassandra.jdbc.AbstractJdbcType.DEFAULT_PRECISION;
4041
import static com.ing.data.cassandra.jdbc.AbstractJdbcType.DEFAULT_SCALE;
4142
import static com.ing.data.cassandra.jdbc.TypesMap.getTypeForComparator;
43+
import static java.sql.DatabaseMetaData.functionColumnIn;
44+
import static java.sql.DatabaseMetaData.functionReturn;
4245
import static java.sql.DatabaseMetaData.typeNullable;
4346
import static java.sql.DatabaseMetaData.typePredBasic;
4447
import static java.sql.Types.JAVA_OBJECT;
@@ -64,6 +67,7 @@ public final class MetadataResultSets {
6467
static final String COLUMN_DEFAULT = "COLUMN_DEF";
6568
static final String COLUMN_NAME = "COLUMN_NAME";
6669
static final String COLUMN_SIZE = "COLUMN_SIZE";
70+
static final String COLUMN_TYPE = "COLUMN_TYPE";
6771
static final String CREATE_PARAMS = "CREATE_PARAMS";
6872
static final String DATA_TYPE = "DATA_TYPE";
6973
static final String DECIMAL_DIGITS = "DECIMAL_DIGITS";
@@ -79,6 +83,7 @@ public final class MetadataResultSets {
7983
static final String IS_GENERATED_COLUMN = "IS_GENERATEDCOLUMN";
8084
static final String IS_NULLABLE = "IS_NULLABLE";
8185
static final String KEY_SEQ = "KEY_SEQ";
86+
static final String LENGTH = "LENGTH";
8287
static final String LITERAL_PREFIX = "LITERAL_PREFIX";
8388
static final String LITERAL_SUFFIX = "LITERAL_SUFFIX";
8489
static final String LOCALIZED_TYPE_NAME = "LOCAL_TYPE_NAME";
@@ -92,8 +97,10 @@ public final class MetadataResultSets {
9297
static final String PAGES = "PAGES";
9398
static final String PRECISION = "PRECISION";
9499
static final String PRIMARY_KEY_NAME = "PK_NAME";
100+
static final String RADIX = "RADIX";
95101
static final String REF_GENERATION = "REF_GENERATION";
96102
static final String REMARKS = "REMARKS";
103+
static final String SCALE = "SCALE";
97104
static final String SCOPE_CATALOG = "SCOPE_CATALOG";
98105
static final String SCOPE_SCHEMA = "SCOPE_SCHEMA";
99106
static final String SCOPE_TABLE = "SCOPE_TABLE";
@@ -115,6 +122,7 @@ public final class MetadataResultSets {
115122
static final String TYPE_SCHEMA = "TYPE_SCHEM";
116123
static final String UNSIGNED_ATTRIBUTE = "UNSIGNED_ATTRIBUTE";
117124
static final String WILDCARD_CHAR = "%";
125+
static final String YES_VALUE = "YES";
118126

119127
private static final Logger LOG = LoggerFactory.getLogger(MetadataResultSets.class);
120128

@@ -865,11 +873,174 @@ public CassandraMetadataResultSet makeFunctions(final CassandraStatement stateme
865873
}
866874
}
867875

868-
// Results should all have the same FUNCTION_CAT, so just sort them by FUNCTION_SCHEM, then FUNCTION_NAME and
869-
// finally SPECIFIC_NAME.
876+
// Results should all have the same FUNCTION_CAT, so just sort them by FUNCTION_SCHEM then FUNCTION_NAME (since
877+
// here SPECIFIC_NAME is equal to FUNCTION_NAME).
870878
functionsRows.sort(Comparator.comparing(row -> ((MetadataRow) row).getString(FUNCTION_SCHEMA))
871-
.thenComparing(row -> ((MetadataRow) row).getString(FUNCTION_NAME))
872-
.thenComparing(row -> ((MetadataRow) row).getString(SPECIFIC_NAME)));
879+
.thenComparing(row -> ((MetadataRow) row).getString(FUNCTION_NAME)));
873880
return new CassandraMetadataResultSet(statement, new MetadataResultSet().setRows(functionsRows));
874881
}
882+
883+
/**
884+
* Builds a valid result set of the given catalog's system or user function parameters and return type.
885+
* This method is used to implement the method
886+
* {@link DatabaseMetaData#getFunctionColumns(String, String, String, String)}.
887+
* <p>
888+
* Only descriptions matching the schema, function and parameter name criteria are returned. They are ordered by
889+
* {@code FUNCTION_CAT}, {@code FUNCTION_SCHEM}, {@code FUNCTION_NAME} and {@code SPECIFIC_NAME}. Within this, the
890+
* return value, if any, is first. Next are the parameter descriptions in call order. The column descriptions
891+
* follow in column number order.
892+
* </p>
893+
* <p>
894+
* The columns of this result set are:
895+
* <ol>
896+
* <li><b>FUNCTION_CAT</b> String => function catalog, may be {@code null}: here is the Cassandra cluster
897+
* name (if available).</li>
898+
* <li><b>FUNCTION_SCHEM</b> String => function schema, may be {@code null}: here is the keyspace the table
899+
* is member of.</li>
900+
* <li><b>FUNCTION_NAME</b> String => function name. This is the name used to invoke the function.</li>
901+
* <li><b>COLUMN_NAME</b> String => column/parameter name.</li>
902+
* <li><b>COLUMN_TYPE</b> short => kind of column/parameter:
903+
* <ul>
904+
* <li>{@link DatabaseMetaData#functionColumnUnknown} - unknown type</li>
905+
* <li>{@link DatabaseMetaData#functionColumnIn} - {@code IN} parameter</li>
906+
* <li>{@link DatabaseMetaData#functionColumnInOut} - {@code INOUT} parameter</li>
907+
* <li>{@link DatabaseMetaData#functionColumnOut} - {@code OUT} parameter</li>
908+
* <li>{@link DatabaseMetaData#functionReturn} - function return value</li>
909+
* <li>{@link DatabaseMetaData#functionColumnResult} - indicates that the parameter or column is a
910+
* column in the {@code ResultSet}</li>
911+
* </ul>
912+
* </li>
913+
* <li><b>DATA_TYPE</b> int => SQL data type from {@link Types}.</li>
914+
* <li><b>TYPE_NAME</b> String => SQL type name, for a UDT type the type name is fully qualified.</li>
915+
* <li><b>PRECISION</b> int => maximum precision.</li>
916+
* <li><b>LENGTH</b> int => length in bytes of data.</li>
917+
* <li><b>SCALE</b> int => scale, {@code null} is returned for data types where SCALE is not
918+
* applicable.</li>
919+
* <li><b>RADIX</b> short => precision radix.</li>
920+
* <li><b>NULLABLE</b> short => can you use {@code NULL} for this type:
921+
* <ul>
922+
* <li>{@link DatabaseMetaData#typeNoNulls} - does not allow {@code NULL} values</li>
923+
* <li>{@link DatabaseMetaData#typeNullable} - allows {@code NULL} values</li>
924+
* <li>{@link DatabaseMetaData#typeNullableUnknown} - nullability unknown</li>
925+
* </ul>
926+
* </li>
927+
* <li><b>REMARKS</b> String => comment describing column/parameter (always empty, Cassandra does not
928+
* allow to describe columns with a comment).</li>
929+
* <li><b>CHAR_OCTET_LENGTH</b> int => the maximum length of binary and character based parameters or
930+
* columns. For any other datatype the returned value is a {@code NULL}.</li>
931+
* <li><b>ORDINAL_POSITION</b> int => the ordinal position, starting from 1, for the input and output
932+
* parameters. A value of 0 is returned if this row describes the function's return value. For result set
933+
* columns, it is the ordinal position of the column in the result set starting from 1.</li>
934+
* <li><b>IS_NULLABLE</b> String => "YES" if a parameter or column accepts {@code NULL} values, "NO"
935+
* if not and empty if the nullability is unknown.</li>
936+
* <li><b>SPECIFIC_NAME</b> String => the name which uniquely identifies this function within its schema.
937+
* This is a user specified, or DBMS generated, name that may be different then the {@code FUNCTION_NAME}
938+
* for example with overload functions.</li>
939+
* </ol>
940+
* </p>
941+
* <p>
942+
* The {@code PRECISION} column represents the maximum column size that the server supports for the given datatype.
943+
* For numeric data, this is the maximum precision. For character data, this is the length in characters. For
944+
* datetime data types, this is the length in characters of the {@code String} representation (assuming the maximum
945+
* allowed precision of the fractional seconds component). For binary data, this is the length in bytes.
946+
* For the {@code ROWID} datatype (not supported by Cassandra), this is the length in bytes. The value {@code null}
947+
* is returned for data types where the column size is not applicable.
948+
* </p>
949+
*
950+
* @param statement The statement.
951+
* @param schemaPattern A schema name pattern. It must match the schema name as it is stored in the
952+
* database; {@code ""} retrieves those without a schema and {@code null} means that
953+
* the schema name should not be used to narrow down the search.
954+
* @param functionNamePattern A function name pattern; must match the function name as it is stored in the
955+
* database.
956+
* @param columnNamePattern A parameter name pattern; must match the parameter or column name as it is stored
957+
* in the database.
958+
* @return A valid result set for implementation of
959+
* {@link DatabaseMetaData#getFunctionColumns(String, String, String, String)}.
960+
* @throws SQLException when something went wrong during the creation of the result set.
961+
*/
962+
public CassandraMetadataResultSet makeFunctionColumns(final CassandraStatement statement,
963+
final String schemaPattern,
964+
final String functionNamePattern,
965+
final String columnNamePattern) throws SQLException {
966+
final ArrayList<MetadataRow> functionParamsRows = new ArrayList<>();
967+
final Map<CqlIdentifier, KeyspaceMetadata> keyspaces = statement.connection.getClusterMetadata().getKeyspaces();
968+
969+
for (final Map.Entry<CqlIdentifier, KeyspaceMetadata> keyspace : keyspaces.entrySet()) {
970+
final KeyspaceMetadata keyspaceMetadata = keyspace.getValue();
971+
String schemaNamePattern = schemaPattern;
972+
if (WILDCARD_CHAR.equals(schemaPattern)) {
973+
schemaNamePattern = keyspaceMetadata.getName().asInternal();
974+
}
975+
if (schemaNamePattern == null || schemaNamePattern.equals(keyspaceMetadata.getName().asInternal())) {
976+
final Map<FunctionSignature, FunctionMetadata> functions = keyspaceMetadata.getFunctions();
977+
978+
for (final Map.Entry<FunctionSignature, FunctionMetadata> function : functions.entrySet()) {
979+
final FunctionSignature functionSignature = function.getKey();
980+
final FunctionMetadata functionMetadata = function.getValue();
981+
if (WILDCARD_CHAR.equals(functionNamePattern) || functionNamePattern == null
982+
|| functionNamePattern.equals(functionSignature.getName().asInternal())) {
983+
// Function return type.
984+
final AbstractJdbcType<?> returnJdbcType =
985+
getTypeForComparator(functionMetadata.getReturnType().asCql(false, true));
986+
final MetadataRow row = new MetadataRow()
987+
.addEntry(FUNCTION_CATALOG, statement.connection.getCatalog())
988+
.addEntry(FUNCTION_SCHEMA, keyspaceMetadata.getName().asInternal())
989+
.addEntry(FUNCTION_NAME, functionSignature.getName().asInternal())
990+
.addEntry(COLUMN_NAME, StringUtils.EMPTY)
991+
.addEntry(COLUMN_TYPE, String.valueOf(functionReturn))
992+
.addEntry(DATA_TYPE, String.valueOf(returnJdbcType.getJdbcType()))
993+
.addEntry(TYPE_NAME, functionMetadata.getReturnType().toString())
994+
.addEntry(PRECISION, String.valueOf(returnJdbcType.getPrecision(null)))
995+
.addEntry(LENGTH, String.valueOf(Integer.MAX_VALUE))
996+
.addEntry(SCALE, String.valueOf(returnJdbcType.getScale(null)))
997+
.addEntry(RADIX, String.valueOf(returnJdbcType.getPrecision(null)))
998+
.addEntry(NULLABLE, String.valueOf(typeNullable))
999+
.addEntry(REMARKS, StringUtils.EMPTY)
1000+
.addEntry(CHAR_OCTET_LENGTH, null)
1001+
.addEntry(ORDINAL_POSITION, "0")
1002+
.addEntry(IS_NULLABLE, YES_VALUE)
1003+
.addEntry(SPECIFIC_NAME, functionSignature.getName().asInternal());
1004+
functionParamsRows.add(row);
1005+
// Function input parameters.
1006+
final List<CqlIdentifier> paramNames = functionMetadata.getParameterNames();
1007+
for (int i = 0; i < paramNames.size(); i++) {
1008+
if (WILDCARD_CHAR.equals(columnNamePattern) || columnNamePattern == null
1009+
|| columnNamePattern.equals(paramNames.get(i).asInternal())) {
1010+
final AbstractJdbcType<?> paramJdbcType = getTypeForComparator(
1011+
functionSignature.getParameterTypes().get(i).asCql(false, true));
1012+
final MetadataRow paramRow = new MetadataRow()
1013+
.addEntry(FUNCTION_CATALOG, statement.connection.getCatalog())
1014+
.addEntry(FUNCTION_SCHEMA, keyspaceMetadata.getName().asInternal())
1015+
.addEntry(FUNCTION_NAME, functionSignature.getName().asInternal())
1016+
.addEntry(COLUMN_NAME, paramNames.get(i).asInternal())
1017+
.addEntry(COLUMN_TYPE, String.valueOf(functionColumnIn))
1018+
.addEntry(DATA_TYPE, String.valueOf(paramJdbcType.getJdbcType()))
1019+
.addEntry(TYPE_NAME, functionSignature.getParameterTypes().get(i).toString())
1020+
.addEntry(PRECISION, String.valueOf(paramJdbcType.getPrecision(null)))
1021+
.addEntry(LENGTH, String.valueOf(Integer.MAX_VALUE))
1022+
.addEntry(SCALE, String.valueOf(paramJdbcType.getScale(null)))
1023+
.addEntry(RADIX, String.valueOf(paramJdbcType.getPrecision(null)))
1024+
.addEntry(NULLABLE, String.valueOf(typeNullable))
1025+
.addEntry(REMARKS, StringUtils.EMPTY)
1026+
.addEntry(CHAR_OCTET_LENGTH, null)
1027+
.addEntry(ORDINAL_POSITION, String.valueOf(i + 1))
1028+
.addEntry(IS_NULLABLE, YES_VALUE)
1029+
.addEntry(SPECIFIC_NAME, functionSignature.getName().asInternal());
1030+
functionParamsRows.add(paramRow);
1031+
}
1032+
}
1033+
}
1034+
}
1035+
}
1036+
}
1037+
1038+
// Results should all have the same FUNCTION_CAT, so just sort them by FUNCTION_SCHEM then FUNCTION_NAME (since
1039+
// here SPECIFIC_NAME is equal to FUNCTION_NAME), and finally by ORDINAL_POSITION.
1040+
functionParamsRows.sort(Comparator.comparing(row -> ((MetadataRow) row).getString(FUNCTION_SCHEMA))
1041+
.thenComparing(row -> ((MetadataRow) row).getString(FUNCTION_NAME))
1042+
.thenComparing(row -> ((MetadataRow) row).getString(SPECIFIC_NAME))
1043+
.thenComparing(row -> Integer.valueOf(((MetadataRow) row).getString(ORDINAL_POSITION))));
1044+
return new CassandraMetadataResultSet(statement, new MetadataResultSet().setRows(functionParamsRows));
1045+
}
8751046
}

0 commit comments

Comments
 (0)