Skip to content

Commit 19052a6

Browse files
committed
Fix failure reading FLOAT type in Query table function in Oracle
1 parent bcebf07 commit 19052a6

File tree

2 files changed

+84
-1
lines changed

2 files changed

+84
-1
lines changed

plugin/trino-oracle/src/main/java/io/trino/plugin/oracle/OracleClient.java

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import io.trino.plugin.jdbc.BaseJdbcClient;
2727
import io.trino.plugin.jdbc.BaseJdbcConfig;
2828
import io.trino.plugin.jdbc.BooleanWriteFunction;
29+
import io.trino.plugin.jdbc.CaseSensitivity;
2930
import io.trino.plugin.jdbc.ColumnMapping;
3031
import io.trino.plugin.jdbc.ConnectionFactory;
3132
import io.trino.plugin.jdbc.DoubleWriteFunction;
@@ -81,6 +82,7 @@
8182
import java.sql.DatabaseMetaData;
8283
import java.sql.PreparedStatement;
8384
import java.sql.ResultSet;
85+
import java.sql.ResultSetMetaData;
8486
import java.sql.SQLException;
8587
import java.sql.Types;
8688
import java.time.Instant;
@@ -108,6 +110,8 @@
108110
import static com.google.common.base.Verify.verify;
109111
import static io.airlift.slice.Slices.utf8Slice;
110112
import static io.airlift.slice.Slices.wrappedBuffer;
113+
import static io.trino.plugin.jdbc.CaseSensitivity.CASE_INSENSITIVE;
114+
import static io.trino.plugin.jdbc.CaseSensitivity.CASE_SENSITIVE;
111115
import static io.trino.plugin.jdbc.JdbcErrorCode.JDBC_ERROR;
112116
import static io.trino.plugin.jdbc.PredicatePushdownController.DISABLE_PUSHDOWN;
113117
import static io.trino.plugin.jdbc.PredicatePushdownController.FULL_PUSHDOWN;
@@ -162,6 +166,7 @@
162166
import static java.lang.Math.toIntExact;
163167
import static java.lang.String.format;
164168
import static java.lang.String.join;
169+
import static java.sql.Types.FLOAT;
165170
import static java.util.Locale.ENGLISH;
166171
import static java.util.concurrent.TimeUnit.DAYS;
167172

@@ -393,6 +398,67 @@ public Optional<String> getTableComment(ResultSet resultSet)
393398
return Optional.ofNullable(emptyToNull(resultSet.getString("REMARKS")));
394399
}
395400

401+
@Override
402+
protected List<JdbcColumnHandle> getColumns(ConnectorSession session, Connection connection, ResultSetMetaData metadata)
403+
throws SQLException
404+
{
405+
ImmutableList.Builder<JdbcColumnHandle> columns = ImmutableList.builder();
406+
for (int column = 1; column <= metadata.getColumnCount(); column++) {
407+
JdbcTypeHandle jdbcTypeHandle = getJdbcTypeHandle(metadata, column);
408+
409+
// Use getColumnLabel method because query pass-through table function may contain column aliases
410+
String name = metadata.getColumnLabel(column);
411+
Type type = toColumnMapping(session, connection, jdbcTypeHandle)
412+
.orElseThrow(() -> new UnsupportedOperationException(format("Unsupported type: %s of column: %s", jdbcTypeHandle, name)))
413+
.getType();
414+
columns.add(new JdbcColumnHandle(name, jdbcTypeHandle, type));
415+
}
416+
return columns.build();
417+
}
418+
419+
private static JdbcTypeHandle getJdbcTypeHandle(ResultSetMetaData metadata, int column)
420+
throws SQLException
421+
{
422+
int columnType = metadata.getColumnType(column);
423+
int columnSize = metadata.getPrecision(column);
424+
int scale = metadata.getScale(column);
425+
CaseSensitivity caseSensitive = metadata.isCaseSensitive(column) ? CASE_SENSITIVE : CASE_INSENSITIVE;
426+
if (columnType == Types.NUMERIC) {
427+
String columnClassName = metadata.getColumnClassName(column);
428+
int precision = columnSize + max(-scale, 0);
429+
// Oracle NUMBER are expected "java.math.BigDecimal", when the class name is "java.lang.Double" it means
430+
// it's actual a FLOAT type in Oracle side
431+
if ("java.lang.Double".equals(columnClassName)) {
432+
// we just handle the precision here is not able to handle by `toColumnMapping` method
433+
if (!isAllowedNumber(precision, columnSize)) {
434+
return new JdbcTypeHandle(FLOAT, Optional.of("FLOAT"), Optional.of(columnSize), Optional.of(scale), Optional.empty(), Optional.of(caseSensitive));
435+
}
436+
}
437+
else {
438+
verify("java.math.BigDecimal".equals(columnClassName), "Unexpected class name %s for Oracle NUMBER type", columnClassName);
439+
}
440+
}
441+
442+
return new JdbcTypeHandle(
443+
columnType,
444+
Optional.ofNullable(metadata.getColumnTypeName(column)),
445+
Optional.of(columnSize),
446+
Optional.of(scale),
447+
Optional.empty(), // TODO support arrays
448+
Optional.of(caseSensitive));
449+
}
450+
451+
private static boolean isAllowedNumber(int precision, int columnSize)
452+
{
453+
if (precision > Decimals.MAX_PRECISION) {
454+
return false;
455+
}
456+
if (columnSize <= 0) {
457+
return false;
458+
}
459+
return true;
460+
}
461+
396462
// Iterates over filtered schemas to fetch column details per schema, preventing unnecessary columns for Oracle's internal schemas
397463
@Override
398464
public Iterator<RelationColumnsMetadata> getAllTableColumns(ConnectorSession session, Optional<String> schema)
@@ -515,7 +581,7 @@ else if (numberDefaultScale.isPresent() && precision == PRECISION_OF_UNSPECIFIED
515581
precision = Decimals.MAX_PRECISION;
516582
scale = numberDefaultScale.get();
517583
}
518-
else if (precision > Decimals.MAX_PRECISION || actualPrecision <= 0) {
584+
else if (!isAllowedNumber(precision, actualPrecision)) {
519585
break;
520586
}
521587
DecimalType decimalType = createDecimalType(precision, scale);

plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/BaseOracleConnectorTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,23 @@ protected TestTable createTableWithUnsupportedColumn()
120120
"(one NUMBER(19), two NUMBER, three VARCHAR2(10 CHAR))");
121121
}
122122

123+
@Test
124+
void testReadingFloatWithQueryTableFunction()
125+
{
126+
String tableName = "test_float_" + randomNameSuffix();
127+
onRemoteDatabase().execute("CREATE TABLE " + tableName + " (x int, y FLOAT)");
128+
129+
assertUpdate("INSERT INTO " + tableName + " VALUES (1, 0.123), (2, 456.789), (3, NULL)", 3);
130+
131+
// test both query with and without through query table function, make sure the type and values are the same
132+
// for the oracle FLOAT type
133+
String expectedValues = "VALUES CAST(0.123 AS DOUBLE), CAST(456.789 AS DOUBLE), CAST(NULL as DOUBLE)";
134+
assertThat(query("SELECT y FROM " + tableName))
135+
.matches(expectedValues);
136+
assertThat(query("SELECT y FROM TABLE(system.query('SELECT * FROM " + tableName + "'))"))
137+
.matches(expectedValues);
138+
}
139+
123140
@Test
124141
@Override
125142
public void testShowColumns()

0 commit comments

Comments
 (0)