Repository: calcite
Updated Branches:
  refs/heads/master 0cbd2a182 -> bdaa33f9c


[CALCITE-1899] When reading JSON model, give error if mandatory JSON attributes 
are missing


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/b516216b
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/b516216b
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/b516216b

Branch: refs/heads/master
Commit: b516216b6a51fb17b9cebb47e5b84c31207bab8f
Parents: ee0afb6
Author: Julian Hyde <[email protected]>
Authored: Sat Jul 22 20:34:25 2017 -0700
Committer: Julian Hyde <[email protected]>
Committed: Mon Jul 24 21:39:43 2017 -0700

----------------------------------------------------------------------
 .../org/apache/calcite/model/JsonColumn.java    |  4 ++
 .../org/apache/calcite/model/ModelHandler.java  | 44 ++++++++++++++++++--
 .../java/org/apache/calcite/test/ModelTest.java | 31 ++++++++++++++
 3 files changed, 75 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/b516216b/core/src/main/java/org/apache/calcite/model/JsonColumn.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/model/JsonColumn.java 
b/core/src/main/java/org/apache/calcite/model/JsonColumn.java
index c57fd29..b337ef3 100644
--- a/core/src/main/java/org/apache/calcite/model/JsonColumn.java
+++ b/core/src/main/java/org/apache/calcite/model/JsonColumn.java
@@ -29,6 +29,10 @@ public class JsonColumn {
    * <p>Required, and must be unique within the table.
    */
   public String name;
+
+  public void accept(ModelHandler handler) {
+    handler.visit(this);
+  }
 }
 
 // End JsonColumn.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/b516216b/core/src/main/java/org/apache/calcite/model/ModelHandler.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/model/ModelHandler.java 
b/core/src/main/java/org/apache/calcite/model/ModelHandler.java
index adfdd07..ee6373e 100644
--- a/core/src/main/java/org/apache/calcite/model/ModelHandler.java
+++ b/core/src/main/java/org/apache/calcite/model/ModelHandler.java
@@ -48,6 +48,7 @@ import com.google.common.collect.ImmutableMap;
 
 import java.io.File;
 import java.io.IOException;
+import java.lang.reflect.Field;
 import java.sql.SQLException;
 import java.util.ArrayDeque;
 import java.util.Collections;
@@ -135,18 +136,36 @@ public class ModelHandler {
         + "'initAdd', 'merge' and 'result' methods.");
   }
 
-  public void visit(JsonRoot root) {
+  private void checkRequiredAttributes(Object json, String... attributeNames) {
+    for (String attributeName : attributeNames) {
+      try {
+        final Class<?> c = json.getClass();
+        final Field f = c.getField(attributeName);
+        final Object o = f.get(json);
+        if (o == null) {
+          throw new RuntimeException("Field '" + attributeName
+              + "' is required in " + c.getSimpleName());
+        }
+      } catch (NoSuchFieldException | IllegalAccessException e) {
+        throw new RuntimeException("while accessing field " + attributeName,
+            e);
+      }
+    }
+  }
+
+  public void visit(JsonRoot jsonRoot) {
+    checkRequiredAttributes(jsonRoot, "version");
     final Pair<String, SchemaPlus> pair =
         Pair.of(null, connection.getRootSchema());
     schemaStack.push(pair);
-    for (JsonSchema schema : root.schemas) {
+    for (JsonSchema schema : jsonRoot.schemas) {
       schema.accept(this);
     }
     final Pair<String, SchemaPlus> p = schemaStack.pop();
     assert p == pair;
-    if (root.defaultSchema != null) {
+    if (jsonRoot.defaultSchema != null) {
       try {
-        connection.setSchema(root.defaultSchema);
+        connection.setSchema(jsonRoot.defaultSchema);
       } catch (SQLException e) {
         throw new RuntimeException(e);
       }
@@ -154,6 +173,7 @@ public class ModelHandler {
   }
 
   public void visit(JsonMapSchema jsonSchema) {
+    checkRequiredAttributes(jsonSchema, "name");
     final SchemaPlus parentSchema = currentMutableSchema("schema");
     final SchemaPlus schema =
         parentSchema.add(jsonSchema.name, new AbstractSchema());
@@ -208,6 +228,7 @@ public class ModelHandler {
   public void visit(JsonCustomSchema jsonSchema) {
     try {
       final SchemaPlus parentSchema = currentMutableSchema("sub-schema");
+      checkRequiredAttributes(jsonSchema, "name", "factory");
       final SchemaFactory schemaFactory =
           AvaticaUtils.instantiatePlugin(SchemaFactory.class,
               jsonSchema.factory);
@@ -259,6 +280,7 @@ public class ModelHandler {
   }
 
   public void visit(JsonJdbcSchema jsonSchema) {
+    checkRequiredAttributes(jsonSchema, "name");
     final SchemaPlus parentSchema = currentMutableSchema("jdbc schema");
     final DataSource dataSource =
         JdbcSchema.dataSource(jsonSchema.jdbcUrl,
@@ -274,6 +296,7 @@ public class ModelHandler {
 
   public void visit(JsonMaterialization jsonMaterialization) {
     try {
+      checkRequiredAttributes(jsonMaterialization, "sql");
       final SchemaPlus schema = currentSchema();
       if (!schema.isMutable()) {
         throw new RuntimeException(
@@ -307,6 +330,7 @@ public class ModelHandler {
 
   public void visit(JsonLattice jsonLattice) {
     try {
+      checkRequiredAttributes(jsonLattice, "name", "sql");
       final SchemaPlus schema = currentSchema();
       if (!schema.isMutable()) {
         throw new RuntimeException("Cannot define lattice; parent schema '"
@@ -347,6 +371,7 @@ public class ModelHandler {
 
   public void visit(JsonCustomTable jsonTable) {
     try {
+      checkRequiredAttributes(jsonTable, "name", "factory");
       final SchemaPlus schema = currentMutableSchema("table");
       final TableFactory tableFactory =
           AvaticaUtils.instantiatePlugin(TableFactory.class,
@@ -354,14 +379,22 @@ public class ModelHandler {
       final Table table =
           tableFactory.create(schema, jsonTable.name,
               operandMap(null, jsonTable.operand), null);
+      for (JsonColumn column : jsonTable.columns) {
+        column.accept(this);
+      }
       schema.add(jsonTable.name, table);
     } catch (Exception e) {
       throw new RuntimeException("Error instantiating " + jsonTable, e);
     }
   }
 
+  public void visit(JsonColumn jsonColumn) {
+    checkRequiredAttributes(jsonColumn, "name");
+  }
+
   public void visit(JsonView jsonView) {
     try {
+      checkRequiredAttributes(jsonView, "name");
       final SchemaPlus schema = currentMutableSchema("view");
       final List<String> path = Util.first(jsonView.path, currentSchemaPath());
       final List<String> viewPath = 
ImmutableList.<String>builder().addAll(path)
@@ -396,6 +429,8 @@ public class ModelHandler {
   }
 
   public void visit(JsonFunction jsonFunction) {
+    // "name" is not required - a class can have several functions
+    checkRequiredAttributes(jsonFunction, "className");
     try {
       final SchemaPlus schema = currentMutableSchema("function");
       final List<String> path =
@@ -411,6 +446,7 @@ public class ModelHandler {
   }
 
   public void visit(JsonMeasure jsonMeasure) {
+    checkRequiredAttributes(jsonMeasure, "agg");
     assert latticeBuilder != null;
     final Lattice.Measure measure =
         latticeBuilder.resolveMeasure(jsonMeasure.agg, jsonMeasure.args);

http://git-wip-us.apache.org/repos/asf/calcite/blob/b516216b/core/src/test/java/org/apache/calcite/test/ModelTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/ModelTest.java 
b/core/src/test/java/org/apache/calcite/test/ModelTest.java
index 989ddf4..35e78fb 100644
--- a/core/src/test/java/org/apache/calcite/test/ModelTest.java
+++ b/core/src/test/java/org/apache/calcite/test/ModelTest.java
@@ -198,6 +198,37 @@ public class ModelTest {
             + "is not a SemiMutableSchema");
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-1899";>[CALCITE-1899]
+   * When reading model, give error if mandatory JSON attributes are
+   * missing</a>.
+   *
+   * <p>Schema without name should give useful error, not
+   * NullPointerException. */
+  @Test public void testSchemaWithoutName() throws Exception {
+    final String model = "{\n"
+        + "  version: '1.0',\n"
+        + "  defaultSchema: 'adhoc',\n"
+        + "  schemas: [ {\n"
+        + "  } ]\n"
+        + "}";
+    CalciteAssert.model(model)
+        .connectThrows("Field 'name' is required in JsonMapSchema");
+  }
+
+  @Test public void testCustomSchemaWithoutFactory() throws Exception {
+    final String model = "{\n"
+        + "  version: '1.0',\n"
+        + "  defaultSchema: 'adhoc',\n"
+        + "  schemas: [ {\n"
+        + "    type: 'custom',\n"
+        + "    name: 'my_custom_schema'\n"
+        + "  } ]\n"
+        + "}";
+    CalciteAssert.model(model)
+        .connectThrows("Field 'factory' is required in JsonCustomSchema");
+  }
+
   /** Tests a model containing a lattice and some views. */
   @Test public void testReadLattice() throws IOException {
     final ObjectMapper mapper = mapper();

Reply via email to