This is an automated email from the ASF dual-hosted git repository.

zabetak pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/master by this push:
     new 846494a  [CALCITE-2464] Allow to set nullability for columns of 
structured types (Ruben Quesada Lopez) - Added nullability flag to struct types 
(previously they were always considered as non-nullable) - Struct types will be 
built as non-nullable by default to avoid regresssions, but this can be changed 
via RelDataTypeFactory#createTypeWithNullability - SqlCreateTable will generate 
nullable struct types, unless they are defined as NOT NULL - New unit tests
846494a is described below

commit 846494a0091b00357f3ccb67c8a4a7d9d226cd7d
Author: rubenada <rube...@gmail.com>
AuthorDate: Thu Nov 1 17:19:40 2018 +0100

    [CALCITE-2464] Allow to set nullability for columns of structured types 
(Ruben Quesada Lopez)
    - Added nullability flag to struct types (previously they were always 
considered as non-nullable)
    - Struct types will be built as non-nullable by default to avoid 
regresssions, but this
    can be changed via RelDataTypeFactory#createTypeWithNullability
    - SqlCreateTable will generate nullable struct types, unless they are 
defined as NOT NULL
    - New unit tests
---
 .../apache/calcite/jdbc/JavaTypeFactoryImpl.java   | 28 +++++---
 .../calcite/rel/type/RelDataTypeFactoryImpl.java   | 74 +++++++++++++---------
 .../org/apache/calcite/rel/type/RelRecordType.java | 25 +++++++-
 .../calcite/sql/type/SqlTypeFactoryTest.java       | 29 +++++++++
 .../java/org/apache/calcite/test/JdbcTest.java     | 15 +++++
 .../org/apache/calcite/sql/ddl/SqlCreateTable.java |  4 ++
 .../java/org/apache/calcite/test/ServerTest.java   |  8 +++
 server/src/test/resources/sql/type.iq              | 34 +++++++++-
 8 files changed, 173 insertions(+), 44 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java 
b/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java
index 08a7aa8..7b689dc 100644
--- a/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java
+++ b/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java
@@ -243,18 +243,26 @@ public class JavaTypeFactoryImpl
   /** Converts a type in Java format to a SQL-oriented type. */
   public static RelDataType toSql(final RelDataTypeFactory typeFactory,
       RelDataType type) {
+    return toSql(typeFactory, type, true);
+  }
+
+  private static RelDataType toSql(final RelDataTypeFactory typeFactory,
+      RelDataType type, boolean mustSetNullability) {
+    RelDataType sqlType = type;
     if (type instanceof RelRecordType) {
-      return typeFactory.createStructType(
-          Lists.transform(type.getFieldList(),
-              field -> toSql(typeFactory, field.getType())),
-          type.getFieldNames());
-    }
-    if (type instanceof JavaType) {
-      return typeFactory.createTypeWithNullability(
-          typeFactory.createSqlType(type.getSqlTypeName()),
-          type.isNullable());
+      // We do not need to change the nullability of the nested fields,
+      // since it can be overridden by the existing implementation of 
createTypeWithNullability
+      // when we treat the nullability of the root struct type.
+      sqlType = typeFactory.createStructType(
+              Lists.transform(type.getFieldList(),
+                field -> toSql(typeFactory, field.getType(), false)),
+              type.getFieldNames());
+    } else if (type instanceof JavaType) {
+      sqlType = typeFactory.createSqlType(type.getSqlTypeName());
     }
-    return type;
+    return mustSetNullability
+            ? typeFactory.createTypeWithNullability(sqlType, type.isNullable())
+            : sqlType;
   }
 
   public Type createSyntheticType(List<Type> types) {
diff --git 
a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java 
b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java
index d529ff7..26d4980 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java
@@ -70,7 +70,7 @@ public abstract class RelDataTypeFactoryImpl implements 
RelDataTypeFactory {
           new RelDataTypeFieldImpl(
               key.names.get(i), i, key.types.get(i)));
     }
-    return new RelRecordType(key.kind, list.build());
+    return new RelRecordType(key.kind, list.build(), key.nullable);
   }
 
   private static final Map<Class, RelDataTypeFamily> CLASS_FAMILIES =
@@ -141,8 +141,16 @@ public abstract class RelDataTypeFactoryImpl implements 
RelDataTypeFactory {
   public RelDataType createStructType(StructKind kind,
       final List<RelDataType> typeList,
       final List<String> fieldNameList) {
+    return createStructType(kind, typeList,
+        fieldNameList, false);
+  }
+
+  private RelDataType createStructType(StructKind kind,
+      final List<RelDataType> typeList,
+      final List<String> fieldNameList,
+      final boolean nullable) {
     assert typeList.size() == fieldNameList.size();
-    return canonize(kind, fieldNameList, typeList);
+    return canonize(kind, fieldNameList, typeList, nullable);
   }
 
   @SuppressWarnings("deprecation")
@@ -171,6 +179,11 @@ public abstract class RelDataTypeFactoryImpl implements 
RelDataTypeFactory {
 
   public final RelDataType createStructType(
       final List<? extends Map.Entry<String, RelDataType>> fieldList) {
+    return createStructType(fieldList, false);
+  }
+
+  private RelDataType createStructType(
+      final List<? extends Map.Entry<String, RelDataType>> fieldList, boolean 
nullable) {
     return canonize(StructKind.FULLY_QUALIFIED,
         new AbstractList<String>() {
           @Override public String get(int index) {
@@ -189,7 +202,7 @@ public abstract class RelDataTypeFactoryImpl implements 
RelDataTypeFactory {
           @Override public int size() {
             return fieldList.size();
           }
-        });
+        }, nullable);
   }
 
   public RelDataType leastRestrictive(List<RelDataType> types) {
@@ -270,12 +283,8 @@ public abstract class RelDataTypeFactoryImpl implements 
RelDataTypeFactory {
       final RelRecordType type,
       final boolean ignoreNullable,
       final boolean nullable) {
-    // REVIEW: angel 18-Aug-2005 dtbug336
-    // Shouldn't null refer to the nullability of the record type
-    // not the individual field types?
     // For flattening and outer joins, it is desirable to change
     // the nullability of the individual fields.
-
     return createStructType(type.getStructKind(),
         new AbstractList<RelDataType>() {
           @Override public RelDataType get(int index) {
@@ -292,18 +301,12 @@ public abstract class RelDataTypeFactoryImpl implements 
RelDataTypeFactory {
             return type.getFieldCount();
           }
         },
-        type.getFieldNames());
+        type.getFieldNames(), nullable);
   }
 
   // implement RelDataTypeFactory
   public RelDataType copyType(RelDataType type) {
-    if (type instanceof RelRecordType) {
-      return copyRecordType((RelRecordType) type, true, false);
-    } else {
-      return createTypeWithNullability(
-          type,
-          type.isNullable());
-    }
+    return createTypeWithNullability(type, type.isNullable());
   }
 
   // implement RelDataTypeFactory
@@ -318,15 +321,16 @@ public abstract class RelDataTypeFactoryImpl implements 
RelDataTypeFactory {
       // REVIEW: angel 18-Aug-2005 dtbug 336 workaround
       // Changed to ignore nullable parameter if nullable is false since
       // copyRecordType implementation is doubtful
-      if (nullable) {
-        // Do a deep copy, setting all fields of the record type
-        // to be nullable regardless of initial nullability
-        newType = copyRecordType((RelRecordType) type, false, true);
-      } else {
-        // Keep same type as before, ignore nullable parameter
-        // RelRecordType currently always returns a nullability of false
-        newType = copyRecordType((RelRecordType) type, true, false);
-      }
+      // - If nullable -> Do a deep copy, setting all fields of the record type
+      // to be nullable regardless of initial nullability.
+      // - If not nullable -> Do a deep copy, setting not nullable at top 
RelRecordType
+      // level only, keeping its fields' nullability as before.
+      // According to the SQL standard, nullability for struct types can be 
defined only for
+      // columns, which translates to top level structs. Nested struct 
attributes are always
+      // nullable, so in principle we could always set the nested attributes 
to be nullable.
+      // However, this might create regressions so we will not do it and we 
will keep previous
+      // behavior.
+      newType = copyRecordType((RelRecordType) type, !nullable,  nullable);
     } else {
       newType = copySimpleType(type, nullable);
     }
@@ -353,14 +357,21 @@ public abstract class RelDataTypeFactoryImpl implements 
RelDataTypeFactory {
    */
   protected RelDataType canonize(final StructKind kind,
       final List<String> names,
-      final List<RelDataType> types) {
-    final RelDataType type = CACHE.getIfPresent(new Key(kind, names, types));
+      final List<RelDataType> types,
+      final boolean nullable) {
+    final RelDataType type = CACHE.getIfPresent(new Key(kind, names, types, 
nullable));
     if (type != null) {
       return type;
     }
     final ImmutableList<String> names2 = ImmutableList.copyOf(names);
     final ImmutableList<RelDataType> types2 = ImmutableList.copyOf(types);
-    return CACHE.getUnchecked(new Key(kind, names2, types2));
+    return CACHE.getUnchecked(new Key(kind, names2, types2, nullable));
+  }
+
+  protected RelDataType canonize(final StructKind kind,
+      final List<String> names,
+      final List<RelDataType> types) {
+    return canonize(kind, names, types, false);
   }
 
   /**
@@ -658,15 +669,17 @@ public abstract class RelDataTypeFactoryImpl implements 
RelDataTypeFactory {
     private final StructKind kind;
     private final List<String> names;
     private final List<RelDataType> types;
+    private final boolean nullable;
 
-    Key(StructKind kind, List<String> names, List<RelDataType> types) {
+    Key(StructKind kind, List<String> names, List<RelDataType> types, boolean 
nullable) {
       this.kind = kind;
       this.names = names;
       this.types = types;
+      this.nullable = nullable;
     }
 
     @Override public int hashCode() {
-      return Objects.hash(kind, names, types);
+      return Objects.hash(kind, names, types, nullable);
     }
 
     @Override public boolean equals(Object obj) {
@@ -674,7 +687,8 @@ public abstract class RelDataTypeFactoryImpl implements 
RelDataTypeFactory {
           || obj instanceof Key
           && kind == ((Key) obj).kind
           && names.equals(((Key) obj).names)
-          && types.equals(((Key) obj).types);
+          && types.equals(((Key) obj).types)
+          && nullable == ((Key) obj).nullable;
     }
   }
 }
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelRecordType.java 
b/core/src/main/java/org/apache/calcite/rel/type/RelRecordType.java
index 0bb4ed0..b9b0818 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelRecordType.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelRecordType.java
@@ -29,21 +29,40 @@ import java.util.Objects;
 public class RelRecordType extends RelDataTypeImpl implements Serializable {
   /** Name resolution policy; usually {@link StructKind#FULLY_QUALIFIED}. */
   private final StructKind kind;
+  private final boolean nullable;
 
   //~ Constructors -----------------------------------------------------------
 
   /**
    * Creates a <code>RecordType</code>. This should only be called from a
    * factory method.
+   * @param kind Name resolution policy
+   * @param fields List of fields
+   * @param nullable Whether this record type allows null values
    */
-  public RelRecordType(StructKind kind, List<RelDataTypeField> fields) {
+  public RelRecordType(StructKind kind, List<RelDataTypeField> fields, boolean 
nullable) {
     super(fields);
+    this.nullable = nullable;
     this.kind = Objects.requireNonNull(kind);
     computeDigest();
   }
 
+  /**
+   * Creates a <code>RecordType</code>. This should only be called from a
+   * factory method.
+   * Shorthand for <code>RelRecordType(kind, fields, false)</code>.
+   */
+  public RelRecordType(StructKind kind, List<RelDataTypeField> fields) {
+    this(kind, fields, false);
+  }
+
+  /**
+   * Creates a <code>RecordType</code>. This should only be called from a
+   * factory method.
+   * Shorthand for <code>RelRecordType(StructKind.FULLY_QUALIFIED, fields, 
false)</code>.
+   */
   public RelRecordType(List<RelDataTypeField> fields) {
-    this(StructKind.FULLY_QUALIFIED, fields);
+    this(StructKind.FULLY_QUALIFIED, fields, false);
   }
 
   //~ Methods ----------------------------------------------------------------
@@ -53,7 +72,7 @@ public class RelRecordType extends RelDataTypeImpl implements 
Serializable {
   }
 
   @Override public boolean isNullable() {
-    return false;
+    return nullable;
   }
 
   @Override public int getPrecision() {
diff --git 
a/core/src/test/java/org/apache/calcite/sql/type/SqlTypeFactoryTest.java 
b/core/src/test/java/org/apache/calcite/sql/type/SqlTypeFactoryTest.java
index 60e3325..6af99cf 100644
--- a/core/src/test/java/org/apache/calcite/sql/type/SqlTypeFactoryTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/type/SqlTypeFactoryTest.java
@@ -17,13 +17,22 @@
 package org.apache.calcite.sql.type;
 
 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.rel.type.RelRecordType;
 
 import com.google.common.collect.Lists;
 
 import org.junit.Test;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 /**
@@ -112,6 +121,26 @@ public class SqlTypeFactoryTest {
     assertThat(SqlTypeUtil.comparePrecision(p1, p1), is(0));
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-2464";>[CALCITE-2464]
+   * Allow to set nullability for columns of structured types</a>. */
+  @Test
+  public void createStructTypeWithNullability() {
+    SqlTypeFixture f = new SqlTypeFixture();
+    RelDataTypeFactory typeFactory = f.typeFactory;
+    List<RelDataTypeField> fields = new ArrayList<>();
+    RelDataTypeField field0 = new RelDataTypeFieldImpl(
+            "i", 0, typeFactory.createSqlType(SqlTypeName.INTEGER));
+    RelDataTypeField field1 = new RelDataTypeFieldImpl(
+            "s", 1, typeFactory.createSqlType(SqlTypeName.VARCHAR));
+    fields.add(field0);
+    fields.add(field1);
+    final RelDataType recordType = new RelRecordType(fields); // nullable 
false by default
+    final RelDataType copyRecordType = 
typeFactory.createTypeWithNullability(recordType, true);
+    assertFalse(recordType.isNullable());
+    assertTrue(copyRecordType.isNullable());
+  }
+
 }
 
 // End SqlTypeFactoryTest.java
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 77674a6..01ad5d5 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -4679,6 +4679,21 @@ public class JdbcTest {
             "deptno=null; deptno=40");
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-2464";>[CALCITE-2464]
+   * Allow to set nullability for columns of structured types</a>. */
+  @Test public void testLeftJoinWhereStructIsNotNull() {
+    CalciteAssert.hr()
+        .query("select e.\"deptno\", d.\"deptno\"\n"
+            + "from \"hr\".\"emps\" as e\n"
+            + "  left join \"hr\".\"depts\" as d using (\"deptno\")"
+            + "where d.\"location\" is not null")
+        .returnsUnordered(
+            "deptno=10; deptno=10",
+            "deptno=10; deptno=10",
+            "deptno=10; deptno=10");
+  }
+
   /** Various queries against EMP and DEPT, in particular involving composite
    * join conditions in various flavors of outer join. Results are verified
    * against MySQL (except full join, which MySQL does not support). */
diff --git 
a/server/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTable.java 
b/server/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTable.java
index 3550831..a6ba383 100644
--- a/server/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTable.java
+++ b/server/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTable.java
@@ -174,6 +174,10 @@ public class SqlCreateTable extends SqlCreate
           CalciteSchema.TypeEntry typeEntry = 
pairForType.left.getType(pairForType.right, false);
           if (typeEntry != null) {
             type = typeEntry.getType().apply(typeFactory);
+            if (d.dataType.getNullable() != null
+                    && d.dataType.getNullable() != type.isNullable()) {
+              type = typeFactory.createTypeWithNullability(type, 
d.dataType.getNullable());
+            }
           }
         }
         builder.add(d.name.getSimple(), type);
diff --git a/server/src/test/java/org/apache/calcite/test/ServerTest.java 
b/server/src/test/java/org/apache/calcite/test/ServerTest.java
index e4e2026..2d8b241 100644
--- a/server/src/test/java/org/apache/calcite/test/ServerTest.java
+++ b/server/src/test/java/org/apache/calcite/test/ServerTest.java
@@ -139,6 +139,14 @@ public class ServerTest {
         assertThat(r.getInt(1), is(4));
         assertThat(r.next(), is(false));
       }
+
+      // CALCITE-2464: Allow to set nullability for columns of structured types
+      b = s.execute("create type mytype as (i int)");
+      assertThat(b, is(false));
+      b = s.execute("create table w (i int not null, j mytype)");
+      assertThat(b, is(false));
+      x = s.executeUpdate("insert into w values (1, NULL)");
+      assertThat(x, is(1));
     }
   }
 
diff --git a/server/src/test/resources/sql/type.iq 
b/server/src/test/resources/sql/type.iq
index 7103be4..19eb1ac 100644
--- a/server/src/test/resources/sql/type.iq
+++ b/server/src/test/resources/sql/type.iq
@@ -52,7 +52,7 @@ select * from t;
 
 # Create a table with complex structure type
 # This is to test struct type inference in
-# <a href="https://issues.apache.org/jira/browse/CALCITE-1222";>[CALCITE-2468]
+# <a href="https://issues.apache.org/jira/browse/CALCITE-2468";>[CALCITE-2468]
 
 create type mytype1 as (ii int not null);
 (0 rows modified)
@@ -70,8 +70,40 @@ MYINT INTEGER(10) NOT NULL
 MYSTRUCT STRUCT NOT NULL
 !type
 
+
+# Create a table with nullable complex structure type
+# This is to test nullability for columns of structured types
+# <a href="https://issues.apache.org/jira/browse/CALCITE-2464";>[CALCITE-2464]
+
+# Create a complex table
+create table w (i int not null, j mytype1);
+(0 rows modified)
+
+!update
+
+select i AS myInt, j AS myNullableStruct from w;
+MYINT INTEGER(10) NOT NULL
+MYNULLABLESTRUCT STRUCT
+!type
+
+insert into w values (1, NULL);
+(1 row modified)
+
+!update
+
+select * from w;
++---+---+
+| I | J |
++---+---+
+| 1 |   |
++---+---+
+(1 row)
+
+!ok
+
 drop table t;
 drop table v;
+drop table w;
 (0 rows modified)
 
 !update

Reply via email to