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;

Reply via email to