This is an automated email from the ASF dual-hosted git repository.
jhyde pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new 967bb5acc5 [CALCITE-5676] In JDBC DatabaseMetaData.getColumns, set
DATA_TYPE and TYPE_NAME metadata values for MEASURE types
967bb5acc5 is described below
commit 967bb5acc5448bc8d6ee9b9f5fa3c5f0d71405c2
Author: TJ Banghart <[email protected]>
AuthorDate: Wed May 24 07:57:40 2023 -0700
[CALCITE-5676] In JDBC DatabaseMetaData.getColumns, set DATA_TYPE and
TYPE_NAME metadata values for MEASURE types
Close apache/calcite#3179
---
.../org/apache/calcite/jdbc/CalciteMetaImpl.java | 11 ++++-
.../apache/calcite/prepare/CalcitePrepareImpl.java | 14 +++++-
.../org/apache/calcite/rel/type/RelDataType.java | 9 ++++
.../apache/calcite/sql/type/MeasureSqlType.java | 4 ++
.../calcite/jdbc/CalciteRemoteDriverTest.java | 43 ++++++++++++++++--
.../apache/calcite/test/JdbcFrontLinqBackTest.java | 53 ++++++++++++++++------
.../java/org/apache/calcite/test/JdbcTest.java | 2 +-
7 files changed, 115 insertions(+), 21 deletions(-)
diff --git a/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaImpl.java
b/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaImpl.java
index 460b9e2029..cfc606ece8 100644
--- a/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaImpl.java
+++ b/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaImpl.java
@@ -78,6 +78,7 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.regex.Pattern;
import static java.util.Objects.requireNonNull;
@@ -458,12 +459,20 @@ public class CalciteMetaImpl extends MetaImpl {
instanceof RelDataTypeFactoryImpl.JavaType)
? field.getType().getPrecision()
: -1;
+ // MEASURE is a special case. We want to surface the type returned
+ // after aggregation rather than its default java.sql.Type,
+ // OTHER(1111).
+ final int jdbcOrdinal =
+ Optional.ofNullable(field.getType().getMeasureElementType())
+ .map(RelDataType::getSqlTypeName)
+ .map(SqlTypeName::getJdbcOrdinal)
+ .orElse(field.getType().getSqlTypeName().getJdbcOrdinal());
return new MetaColumn(
table.tableCat,
table.tableSchem,
table.tableName,
field.getName(),
- field.getType().getSqlTypeName().getJdbcOrdinal(),
+ jdbcOrdinal,
field.getType().getFullTypeString(),
precision,
field.getType().getSqlTypeName().allowsScale()
diff --git
a/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
b/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
index 2323029f19..bb0447d1a9 100644
--- a/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
+++ b/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
@@ -831,14 +831,23 @@ public class CalcitePrepareImpl implements CalcitePrepare
{
}
}
- private static @Nullable String origin(@Nullable List<String> origins, int
offsetFromEnd) {
+ private static @Nullable String origin(@Nullable List<String> origins,
+ int offsetFromEnd) {
return origins == null || offsetFromEnd >= origins.size()
? null
: origins.get(origins.size() - 1 - offsetFromEnd);
}
private static int getTypeOrdinal(RelDataType type) {
- return type.getSqlTypeName().getJdbcOrdinal();
+ switch (type.getSqlTypeName()) {
+ case MEASURE:
+ // getMeasureElementType() for MEASURE types will never be null
+ final RelDataType measureElementType =
+ requireNonNull(type.getMeasureElementType(), "measureElementType");
+ return measureElementType.getSqlTypeName().getJdbcOrdinal();
+ default:
+ return type.getSqlTypeName().getJdbcOrdinal();
+ }
}
private static String getClassName(@SuppressWarnings("unused") RelDataType
type) {
@@ -867,6 +876,7 @@ public class CalcitePrepareImpl implements CalcitePrepare {
case MULTISET:
case MAP:
case ROW:
+ case MEASURE:
return type.toString(); // e.g. "INTEGER ARRAY"
case INTERVAL_YEAR_MONTH:
return "INTERVAL_YEAR_TO_MONTH";
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
b/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
index 437a2a0f85..7d4d61fa1c 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
@@ -139,6 +139,15 @@ public interface RelDataType {
*/
@Nullable RelDataType getValueType();
+ /**
+ * Gets the element type if this type is a measure, otherwise null.
+ *
+ * @return canonical type descriptor for the value used in the measure
+ */
+ default @Nullable RelDataType getMeasureElementType() {
+ return null;
+ }
+
/**
* Gets this type's character set, or null if this type cannot carry a
* character set or has no character set defined.
diff --git a/core/src/main/java/org/apache/calcite/sql/type/MeasureSqlType.java
b/core/src/main/java/org/apache/calcite/sql/type/MeasureSqlType.java
index e6e4fb567c..f4e5f94c99 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/MeasureSqlType.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/MeasureSqlType.java
@@ -33,6 +33,10 @@ public class MeasureSqlType extends ApplySqlType {
computeDigest();
}
+ @Override public RelDataType getMeasureElementType() {
+ return types.get(0);
+ }
+
/** Creates a MeasureSqlType. */
static MeasureSqlType create(RelDataType elementType) {
return new MeasureSqlType(elementType, elementType.isNullable());
diff --git
a/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
b/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
index 6325ee73c9..8415fcedc0 100644
--- a/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
+++ b/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
@@ -246,6 +246,20 @@ class CalciteRemoteDriverTest {
assertThat(connection.isClosed(), is(true));
}
+ @Test void testMeasureColumnsLocal() throws Exception {
+ final Connection connection = makeConnectionWithMeasures();
+ assertThat(connection.isClosed(), is(false));
+ final ResultSet resultSet =
+ connection.getMetaData().getColumns(null, "foo", null, "salary");
+ assertThat(resultSet.getMetaData().getColumnCount(), is(24));
+ final int typeNameIdx = resultSet.findColumn("TYPE_NAME");
+ final int dataTypeIdx = resultSet.findColumn("DATA_TYPE");
+ assertThat(resultSet.next(), is(true));
+ assertThat(resultSet.getString(typeNameIdx),
+ is("MEASURE<FLOAT NOT NULL> NOT NULL"));
+ assertThat(resultSet.getInt(dataTypeIdx), is(6));
+ }
+
@Test void testRemoteCatalogs() {
CalciteAssert.hr()
.with(CalciteRemoteDriverTest::getRemoteConnection)
@@ -537,13 +551,23 @@ class CalciteRemoteDriverTest {
}
}
- public static Connection makeConnection() throws Exception {
+ public static Connection makeConnection(boolean withMeasures)
+ throws Exception {
List<Employee> employees = new ArrayList<Employee>();
for (int i = 1; i <= 101; i++) {
employees.add(new Employee(i, 0, "first", 0f, null));
}
- Connection conn = JdbcFrontLinqBackTest.makeConnection(employees);
- return conn;
+ return JdbcFrontLinqBackTest.makeConnection(employees, withMeasures);
+ }
+
+ /** Creates a connection without measures. */
+ public static Connection makeConnection() throws Exception {
+ return makeConnection(false);
+ }
+
+ /** Creates a connection with measures. */
+ public static Connection makeConnectionWithMeasures() throws Exception {
+ return makeConnection(true);
}
@Test void testLocalStatementFetch() throws Exception {
@@ -560,6 +584,19 @@ class CalciteRemoteDriverTest {
assertThat(count, is(101));
}
+ @Test void testLocalStatementResultSetMeasureMetadata() throws Exception {
+ Connection conn = makeConnectionWithMeasures();
+ String sql = "select * from \"foo\".\"bar\"";
+ Statement statement = conn.createStatement();
+ boolean status = statement.execute(sql);
+ assertThat(status, is(true));
+ ResultSet resultSet = statement.getResultSet();
+ String typeName = resultSet.getMetaData().getColumnTypeName(4);
+ Integer ordinal = resultSet.getMetaData().getColumnType(4);
+ assertThat(typeName, is("MEASURE<FLOAT>"));
+ assertThat(ordinal, is(6));
+ }
+
/** Test that returns all result sets in one go. */
@Test void testLocalPreparedStatementFetch() throws Exception {
Connection conn = makeConnection();
diff --git
a/core/src/test/java/org/apache/calcite/test/JdbcFrontLinqBackTest.java
b/core/src/test/java/org/apache/calcite/test/JdbcFrontLinqBackTest.java
index ad95e5f06e..bf7b793de2 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcFrontLinqBackTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcFrontLinqBackTest.java
@@ -18,6 +18,7 @@ package org.apache.calcite.test;
import org.apache.calcite.adapter.java.JavaTypeFactory;
import org.apache.calcite.jdbc.CalciteConnection;
+import org.apache.calcite.jdbc.JavaRecordType;
import org.apache.calcite.linq4j.Enumerator;
import org.apache.calcite.linq4j.Linq4j;
import org.apache.calcite.linq4j.QueryProvider;
@@ -25,16 +26,22 @@ import org.apache.calcite.linq4j.Queryable;
import org.apache.calcite.linq4j.tree.Expression;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rel.type.RelDataTypeFieldImpl;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.schema.Schemas;
import org.apache.calcite.schema.impl.AbstractSchema;
import org.apache.calcite.schema.impl.AbstractTableQueryable;
+import org.apache.calcite.sql.type.MeasureSqlType;
+import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.test.schemata.hr.Employee;
import org.apache.calcite.util.TestUtil;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.DriverManager;
@@ -300,7 +307,7 @@ public class JdbcFrontLinqBackTest {
* @return a connection post-processor
*/
private static CalciteAssert.ConnectionPostProcessor makePostProcessor(
- final List<Employee> initialData) {
+ List<Employee> initialData, boolean withMeasures) {
return connection -> {
CalciteConnection calciteConnection =
connection.unwrap(CalciteConnection.class);
@@ -308,7 +315,7 @@ public class JdbcFrontLinqBackTest {
SchemaPlus mapSchema = rootSchema.add("foo", new AbstractSchema());
final String tableName = "bar";
final AbstractModifiableTable table =
- mutable(tableName, initialData);
+ mutable(tableName, initialData, withMeasures);
mapSchema.add(tableName, table);
return calciteConnection;
};
@@ -319,11 +326,11 @@ public class JdbcFrontLinqBackTest {
*
* @param initialData record to be presented in table
*/
- public static Connection makeConnection(
- final List<Employee> initialData) throws Exception {
+ public static Connection makeConnection(final List<Employee> initialData,
+ final boolean withMeasures) throws Exception {
Properties info = new Properties();
Connection connection = DriverManager.getConnection("jdbc:calcite:", info);
- connection = makePostProcessor(initialData).apply(connection);
+ connection = makePostProcessor(initialData,
withMeasures).apply(connection);
return connection;
}
@@ -332,7 +339,7 @@ public class JdbcFrontLinqBackTest {
* {@link Employee} schema.
*/
public static Connection makeConnection() throws Exception {
- return makeConnection(new ArrayList<Employee>());
+ return makeConnection(new ArrayList<>(), false);
}
private CalciteAssert.AssertThat mutable(
@@ -340,16 +347,34 @@ public class JdbcFrontLinqBackTest {
employees.add(new Employee(0, 0, "first", 0f, null));
return that()
.with(CalciteAssert.Config.REGULAR)
- .with(makePostProcessor(employees));
+ .with(makePostProcessor(employees, false));
}
+ /** Creates an AbstractModifiableTable based on {@link Employee} class.
+ *
+ * <p>If {@code withMeasures} is true, treats {@link Employee#salary} field
+ * as a {@link MeasureSqlType} of type {@link SqlTypeName#FLOAT}. */
static AbstractModifiableTable mutable(String tableName,
- final List<Employee> employees) {
+ List<Employee> employees, boolean withMeasures) {
return new AbstractModifiableTable(tableName) {
- public RelDataType getRowType(
- RelDataTypeFactory typeFactory) {
- return ((JavaTypeFactory) typeFactory)
- .createType(Employee.class);
+ public RelDataType getRowType(RelDataTypeFactory typeFactory) {
+ final JavaTypeFactory javaTypeFactory = (JavaTypeFactory) typeFactory;
+ final List<RelDataTypeField> list = new ArrayList<>();
+ for (Field field : Employee.class.getFields()) {
+ if (!Modifier.isStatic(field.getModifiers())) {
+ // FIXME: watch out for recursion
+ final Type fieldType = field.getType();
+ final RelDataType relType =
+ withMeasures && field.getName().equals("salary")
+ ? javaTypeFactory.createMeasureType(
+ javaTypeFactory.createSqlType(SqlTypeName.FLOAT))
+ : javaTypeFactory.createType(fieldType);
+ list.add(
+ new RelDataTypeFieldImpl(field.getName(), list.size(),
+ relType));
+ }
+ }
+ return new JavaRecordType(list, Employee.class);
}
public <T> Queryable<T> asQueryable(QueryProvider queryProvider,
@@ -397,7 +422,7 @@ public class JdbcFrontLinqBackTest {
/** Local Statement insert. */
@Test void testInsert3() throws Exception {
- Connection connection = makeConnection(new ArrayList<Employee>());
+ Connection connection = makeConnection();
String sql = "insert into \"foo\".\"bar\" values (1, 1, 'second', 2, 2)";
Statement statement = connection.createStatement();
@@ -411,7 +436,7 @@ public class JdbcFrontLinqBackTest {
/** Local PreparedStatement insert WITHOUT bind variables. */
@Test void testPreparedStatementInsert() throws Exception {
- Connection connection = makeConnection(new ArrayList<Employee>());
+ Connection connection = makeConnection();
assertFalse(connection.isClosed());
String sql = "insert into \"foo\".\"bar\" values (1, 1, 'second', 2, 2)";
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index edb248cd38..ad06270d49 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -8232,7 +8232,7 @@ public class JdbcTest {
if (employees == null) {
employees = Collections.emptyList();
}
- return JdbcFrontLinqBackTest.mutable(name, employees);
+ return JdbcFrontLinqBackTest.mutable(name, employees, false);
case "DEPARTMENTS":
clazz = Department.class;
array = new HrSchema().depts;