Skip to content

Commit

Permalink
JDBC-540 Time zones for database, result set and parameter metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
mrotteveel committed Mar 10, 2019
1 parent 50f2478 commit 47039b3
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 18 deletions.
14 changes: 14 additions & 0 deletions src/main/org/firebirdsql/jdbc/AbstractFieldMetaData.java
Expand Up @@ -171,6 +171,12 @@ protected final String getFieldClassName(int field) throws SQLException {
case Types.DATE:
return SQL_DATE_CLASS_NAME;

case JaybirdTypeCodes.TIME_WITH_TIMEZONE:
return OFFSET_TIME_CLASS_NAME;

case JaybirdTypeCodes.TIMESTAMP_WITH_TIMEZONE:
return OFFSET_DATE_TIME_CLASS_NAME;

case Types.NUMERIC:
case Types.DECIMAL:
case JaybirdTypeCodes.DECFLOAT:
Expand Down Expand Up @@ -253,6 +259,10 @@ protected final String getFieldTypeName(int field) {
return "TIME";
case ISCConstants.SQL_TYPE_DATE:
return "DATE";
case ISCConstants.SQL_TIMESTAMP_TZ:
return "TIMESTAMP WITH TIME ZONE";
case ISCConstants.SQL_TIME_TZ:
return "TIME WITH TIME ZONE";
case ISCConstants.SQL_BLOB:
if (sqlSubtype < 0) {
return "BLOB SUB_TYPE <0"; // TODO report actual subtype
Expand Down Expand Up @@ -347,6 +357,10 @@ protected final int getPrecisionInternal(int field) throws SQLException {
return 8;
case Types.TIMESTAMP:
return 19;
case JaybirdTypeCodes.TIMESTAMP_WITH_TIMEZONE:
return 30;
case JaybirdTypeCodes.TIME_WITH_TIMEZONE:
return 19;
case Types.BOOLEAN:
return 1;
default:
Expand Down
111 changes: 100 additions & 11 deletions src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java
Expand Up @@ -109,6 +109,8 @@ public class FBDatabaseMetaData implements FirebirdDatabaseMetaData {
private static final byte[] DATE_PRECISION = createInt(10);
private static final byte[] TIME_PRECISION = createInt(8);
private static final byte[] TIMESTAMP_PRECISION = createInt(19);
private static final byte[] TIME_WITH_TIMEZONE_PRECISION = createInt(19);
private static final byte[] TIMESTAMP_WITH_TIMEZONE_PRECISION = createInt(30);
private static final byte[] BOOLEAN_PRECISION = createInt(1);
private static final byte[] DECFLOAT_16_PRECISION = createInt(16);
private static final byte[] DECFLOAT_34_PRECISION = createInt(34);
Expand Down Expand Up @@ -417,7 +419,7 @@ public boolean supportsConvert() throws SQLException {
/**
* {@inheritDoc}
* <p>
* See also {@link org.firebirdsql.jdbc.escape.ConvertFunction} for caveats.
* See also {@code org.firebirdsql.jdbc.escape.ConvertFunction} for caveats.
* </p>
*/
@Override
Expand Down Expand Up @@ -522,8 +524,7 @@ public boolean supportsConvert(int fromType, int toType) throws SQLException {
return false;
case JaybirdTypeCodes.TIME_WITH_TIMEZONE:
case JaybirdTypeCodes.TIMESTAMP_WITH_TIMEZONE:
// TODO JDBC-540
return false;
return fromType != Types.ROWID && firebirdSupportInfo.supportsTimeZones();
default:
return false;
}
Expand All @@ -537,8 +538,7 @@ public boolean supportsConvert(int fromType, int toType) throws SQLException {
case JaybirdTypeCodes.TIME_WITH_TIMEZONE:
return false;
case JaybirdTypeCodes.TIMESTAMP_WITH_TIMEZONE:
// TODO JDBC-540
return false;
return firebirdSupportInfo.supportsTimeZones();
case Types.CHAR:
case Types.VARCHAR:
case Types.LONGVARCHAR:
Expand Down Expand Up @@ -566,8 +566,7 @@ public boolean supportsConvert(int fromType, int toType) throws SQLException {
return false;
case JaybirdTypeCodes.TIME_WITH_TIMEZONE:
case JaybirdTypeCodes.TIMESTAMP_WITH_TIMEZONE:
// TODO JDBC-540
return false;
return firebirdSupportInfo.supportsTimeZones();
case Types.CHAR:
case Types.VARCHAR:
case Types.LONGVARCHAR:
Expand All @@ -594,8 +593,7 @@ public boolean supportsConvert(int fromType, int toType) throws SQLException {
return true;
case JaybirdTypeCodes.TIME_WITH_TIMEZONE:
case JaybirdTypeCodes.TIMESTAMP_WITH_TIMEZONE:
// TODO JDBC-540
return false;
return firebirdSupportInfo.supportsTimeZones();
case Types.CHAR:
case Types.VARCHAR:
case Types.LONGVARCHAR:
Expand Down Expand Up @@ -645,8 +643,64 @@ public boolean supportsConvert(int fromType, int toType) throws SQLException {
return false;

case JaybirdTypeCodes.TIME_WITH_TIMEZONE:
if (firebirdSupportInfo.supportsTimeZones()) {
switch (toType) {
case Types.TIME:
case Types.TIMESTAMP:
return true;
case Types.DATE:
return false;
case JaybirdTypeCodes.TIME_WITH_TIMEZONE:
case JaybirdTypeCodes.TIMESTAMP_WITH_TIMEZONE:
return true;
case Types.CHAR:
case Types.VARCHAR:
case Types.LONGVARCHAR:
case Types.CLOB:
case Types.NCHAR:
case Types.LONGNVARCHAR:
case Types.NVARCHAR:
case Types.NCLOB:
return true;
// casting date/time values to binary types will result in ASCII bytes of string conversion
case Types.BINARY:
case Types.VARBINARY:
case Types.LONGVARBINARY:
case Types.BLOB:
return true;
default:
return false;
}
}
return false;
case JaybirdTypeCodes.TIMESTAMP_WITH_TIMEZONE:
// TODO JDBC-540
if (firebirdSupportInfo.supportsTimeZones()) {
switch (toType) {
case Types.TIME:
case Types.TIMESTAMP:
case Types.DATE:
case JaybirdTypeCodes.TIME_WITH_TIMEZONE:
case JaybirdTypeCodes.TIMESTAMP_WITH_TIMEZONE:
return true;
case Types.CHAR:
case Types.VARCHAR:
case Types.LONGVARCHAR:
case Types.CLOB:
case Types.NCHAR:
case Types.LONGNVARCHAR:
case Types.NVARCHAR:
case Types.NCLOB:
return true;
// casting date/time values to binary types will result in ASCII bytes of string conversion
case Types.BINARY:
case Types.VARBINARY:
case Types.LONGVARBINARY:
case Types.BLOB:
return true;
default:
return false;
}
}
return false;

case Types.ARRAY:
Expand Down Expand Up @@ -1345,6 +1399,12 @@ public ResultSet getProcedureColumns(String catalog, String schemaPattern, Strin
case Types.TIMESTAMP:
valueBuilder.at(7).set(TIMESTAMP_PRECISION);
break;
case JaybirdTypeCodes.TIME_WITH_TIMEZONE:
valueBuilder.at(7).set(TIME_WITH_TIMEZONE_PRECISION);
break;
case JaybirdTypeCodes.TIMESTAMP_WITH_TIMEZONE:
valueBuilder.at(7).set(TIMESTAMP_WITH_TIMEZONE_PRECISION);
break;
case Types.BOOLEAN:
valueBuilder
.at(7).set(BOOLEAN_PRECISION)
Expand Down Expand Up @@ -1820,6 +1880,12 @@ public ResultSet getColumns(String catalog, String schemaPattern, String tableNa
case Types.TIMESTAMP:
valueBuilder.at(6).set(TIMESTAMP_PRECISION);
break;
case JaybirdTypeCodes.TIMESTAMP_WITH_TIMEZONE:
valueBuilder.at(6).set(TIMESTAMP_WITH_TIMEZONE_PRECISION);
break;
case JaybirdTypeCodes.TIME_WITH_TIMEZONE:
valueBuilder.at(6).set(TIME_WITH_TIMEZONE_PRECISION);
break;
case Types.BOOLEAN:
valueBuilder
.at(6).set(BOOLEAN_PRECISION)
Expand Down Expand Up @@ -1927,6 +1993,7 @@ private String getScopeCatalogColumnName() {
return scopeCatalog;
}

// TODO Duplicates JdbcTypeConverter (and probably BlrConstants)
private static final int smallint_type = 7;
private static final int integer_type = 8;
private static final int quad_type = 9;
Expand All @@ -1944,7 +2011,9 @@ private String getScopeCatalogColumnName() {
private static final int varchar_type = 37;
// private static final int cstring_type = 40;
private static final int blob_type = 261;
private static final short boolean_type = 23;
private static final int boolean_type = 23;
private static final int time_tz_type = 28;
private static final int timestamp_tz_type = 29;

private static int getDataType(int fieldType, int fieldSubType, int fieldScale, int characterSetId) {
// TODO Preserved for backwards compatibility, is this really necessary?
Expand Down Expand Up @@ -2003,6 +2072,10 @@ private static String getDataTypeName(int sqltype, int sqlsubtype, int sqlscale)
return "TIME";
case date_type:
return "DATE";
case time_tz_type:
return "TIME WITH TIME ZONE";
case timestamp_tz_type:
return "TIMESTAMP WITH TIME ZONE";
case int64_type:
if (sqlsubtype == SUBTYPE_NUMERIC || (sqlsubtype == 0 && sqlscale < 0)) {
return "NUMERIC";
Expand Down Expand Up @@ -2845,6 +2918,22 @@ FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO, SHORT_ZERO, createInt(SQL_VARYING), nu
TYPE_NULLABLE, CASESENSITIVE, TYPE_PRED_NONE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO,
SHORT_ZERO, createInt(SQL_BLOB), null, RADIX_TEN));

if (firebirdSupportInfo.supportsTimeZones()) {
//TIME_WITH_TIMEZONE=2013
rows.add(RowValue.of(rowDescriptor,
getBytes("TIME WITH TIME ZONE"), createInt(JaybirdTypeCodes.TIME_WITH_TIMEZONE),
TIME_WITH_TIMEZONE_PRECISION, getBytes("time'"), getBytes("'"), null, TYPE_NULLABLE,
CASEINSENSITIVE, TYPE_SEARCHABLE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO, SHORT_ZERO,
createInt(SQL_TIME_TZ), null, RADIX_TEN));

//TIMESTAMP_WITH_TIMEZONE=2014
rows.add(RowValue.of(rowDescriptor,
getBytes("TIMESTAMP WITH TIME ZONE"), createInt(JaybirdTypeCodes.TIMESTAMP_WITH_TIMEZONE),
TIMESTAMP_WITH_TIMEZONE_PRECISION, getBytes("timestamp'"), getBytes("'"), null, TYPE_NULLABLE,
CASEINSENSITIVE, TYPE_SEARCHABLE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO, SHORT_ZERO,
createInt(SQL_TIMESTAMP_TZ), null, RADIX_TEN));
}

return new FBResultSet(rowDescriptor, rows);
}

Expand Down
12 changes: 11 additions & 1 deletion src/main/org/firebirdsql/jdbc/field/JdbcTypeConverter.java
Expand Up @@ -117,6 +117,10 @@ public static int fromFirebirdToJdbcType(int firebirdType, int subtype, int scal
return Types.TIME;
case ISCConstants.SQL_TYPE_DATE:
return Types.DATE;
case ISCConstants.SQL_TIMESTAMP_TZ:
return JaybirdTypeCodes.TIMESTAMP_WITH_TIMEZONE;
case ISCConstants.SQL_TIME_TZ:
return JaybirdTypeCodes.TIME_WITH_TIMEZONE;
case ISCConstants.SQL_BLOB:
if (subtype < 0) {
return Types.BLOB;
Expand Down Expand Up @@ -167,7 +171,9 @@ public static int fromMetaDataToJdbcType(int metaDataType, int subtype, int scal
static final int varchar_type = 37;
// static final int cstring_type = 40;
static final int blob_type = 261;
static final short boolean_type = 23;
static final int boolean_type = 23;
static final int time_tz_type = 28;
static final int timestamp_tz_type = 29;

/**
* Converts the metadata type value to the Firebird type value (null bit not set).
Expand Down Expand Up @@ -203,6 +209,10 @@ public static int fromMetaDataToFirebirdType(int metaDataType) {
return ISCConstants.SQL_TYPE_TIME;
case timestamp_type:
return ISCConstants.SQL_TIMESTAMP;
case time_tz_type:
return ISCConstants.SQL_TIME_TZ;
case timestamp_tz_type:
return ISCConstants.SQL_TIMESTAMP_TZ;
case char_type:
return ISCConstants.SQL_TEXT;
case varchar_type:
Expand Down
31 changes: 31 additions & 0 deletions src/test/org/firebirdsql/jdbc/TestFBDatabaseMetaDataColumns.java
Expand Up @@ -93,6 +93,7 @@ public class TestFBDatabaseMetaDataColumns {
" /* boolean */ " +
" /* decfloat */ " +
" /* extended numerics */ " +
" /* time zone */ " +
")";
//@formatter:on

Expand Down Expand Up @@ -163,6 +164,10 @@ private static List<String> getCreateStatements() {
createTable = createTable.replace("/* extended numerics */",
", col_numeric25_20 NUMERIC(25, 20), col_decimal30_5 DECIMAL(30,5)");
}
if (supportInfo.supportsTimeZones()) {
createTable = createTable.replace("/* time zone */",
", col_timetz TIME WITH TIME ZONE, col_timestamptz TIMESTAMP WITH TIME ZONE");
}

statements.add(createTable);
if (supportInfo.supportsComment()) {
Expand Down Expand Up @@ -915,6 +920,32 @@ public void testDecimal30_5Column() throws Exception {

validate(TEST_TABLE, "COL_DECIMAL30_5", validationRules);
}

@Test
public void testTimeWithTimezoneColumn() throws Exception {
assumeTrue("Test requires time zone support",
supportInfoFor(con).supportsTimeZones());
Map<ColumnMetaData, Object> validationRules = getDefaultValueValidationRules();
validationRules.put(ColumnMetaData.DATA_TYPE, JaybirdTypeCodes.TIME_WITH_TIMEZONE);
validationRules.put(ColumnMetaData.TYPE_NAME, "TIME WITH TIME ZONE");
validationRules.put(ColumnMetaData.COLUMN_SIZE, 19);
validationRules.put(ColumnMetaData.ORDINAL_POSITION, 45);

validate(TEST_TABLE, "COL_TIMETZ", validationRules);
}

@Test
public void testTimestampWithTimezoneColumn() throws Exception {
assumeTrue("Test requires time zone support",
supportInfoFor(con).supportsTimeZones());
Map<ColumnMetaData, Object> validationRules = getDefaultValueValidationRules();
validationRules.put(ColumnMetaData.DATA_TYPE, JaybirdTypeCodes.TIMESTAMP_WITH_TIMEZONE);
validationRules.put(ColumnMetaData.TYPE_NAME, "TIMESTAMP WITH TIME ZONE");
validationRules.put(ColumnMetaData.COLUMN_SIZE, 30);
validationRules.put(ColumnMetaData.ORDINAL_POSITION, 46);

validate(TEST_TABLE, "COL_TIMESTAMPTZ", validationRules);
}

// TODO: Add more extensive tests of patterns

Expand Down
32 changes: 28 additions & 4 deletions src/test/org/firebirdsql/jdbc/TestFBPreparedStatementMetaData.java
Expand Up @@ -29,9 +29,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.sql.Connection;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
Expand Down Expand Up @@ -89,6 +87,7 @@ public class TestFBPreparedStatementMetaData {
" /* boolean */ " +
" /* decfloat */ " +
" /* extended numerics */ " +
" /* time zone */ " +
")";

public static final String TEST_QUERY =
Expand All @@ -100,9 +99,10 @@ public class TestFBPreparedStatementMetaData {
" /* boolean */ " +
" /* decfloat */ " +
" /* extended numerics */ " +
" /* time zone */ " +
") " +
"values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? "
+ "/* boolean-param *//* decfloat-param *//* extended-num-param*/)";
+ "/* boolean-param *//* decfloat-param *//* extended-num-param*//* time-zone-param*/)";
//@formatter:on

private static FBManager fbManager;
Expand Down Expand Up @@ -145,6 +145,12 @@ public static void setUp() throws Exception {
testQuery = testQuery.replace("/* extended numerics */", ", col_numeric25_20, col_decimal30_5")
.replace("/* extended-num-param*/", ", ?, ?");
}
if (shouldTestTimeZoneSupport()) {
createTable = createTable.replace("/* time zone */",
", col_timetz TIME WITH TIME ZONE, col_timestamptz TIMESTAMP WITH TIME ZONE");
testQuery = testQuery.replace("/* time zone */", ", col_timetz, col_timestamptz")
.replace("/* time-zone-param*/", ", ?, ?");
}
executeCreateTable(connection, createTable);

pstmt = connection.prepareStatement(testQuery);
Expand Down Expand Up @@ -208,6 +214,10 @@ public static Collection<Object[]> testData() {
testData.add(create(testData.size() + 1, "java.math.BigDecimal", parameterModeIn, NUMERIC, "NUMERIC", 34, 20, parameterNullable, true, "col_numeric25_20"));
testData.add(create(testData.size() + 1, "java.math.BigDecimal", parameterModeIn, DECIMAL, "DECIMAL", 34, 5, parameterNullable, true, "col_decimal30_5"));
}
if (shouldTestTimeZoneSupport()) {
testData.add(create(testData.size() + 1, "java.time.OffsetTime", parameterModeIn, JaybirdTypeCodes.TIME_WITH_TIMEZONE, "TIME WITH TIME ZONE", 19, 0, parameterNullable, false, "col_timetz"));
testData.add(create(testData.size() + 1, "java.time.OffsetDateTime", parameterModeIn, JaybirdTypeCodes.TIMESTAMP_WITH_TIMEZONE, "TIMESTAMP WITH TIME ZONE", 30, 0, parameterNullable, false, "col_timestamptz"));
}

return testData;
}
Expand Down Expand Up @@ -297,6 +307,20 @@ private static Object[] create(int index, String className, int mode, int type,
descriptiveName };
}

private static boolean shouldTestTimeZoneSupport() {
if (!getDefaultSupportInfo().supportsTimeZones()) {
return false;
} else {
try (Connection connection = getConnectionViaDriverManager()) {
DatabaseMetaData dbmd = connection.getMetaData();
int jdbcMajorVersion = dbmd.getJDBCMajorVersion();
return jdbcMajorVersion > 4 || (jdbcMajorVersion == 4 && dbmd.getJDBCMinorVersion() > 1);
} catch (SQLException e) {
return false;
}
}
}

/**
* Simple bean with the expected meta data information.
*/
Expand Down

0 comments on commit 47039b3

Please sign in to comment.