[CALCITE-3547] SqlValidatorException because Planner cannot find UDFs added to schema (Chenxiao Mao)

* Fix PlannerImpl#validate and PlannerImpl#expandView

close apache/calcite#1615
diff --git a/core/src/main/java/org/apache/calcite/prepare/PlannerImpl.java b/core/src/main/java/org/apache/calcite/prepare/PlannerImpl.java
index 9acbc42..4beee6e 100644
--- a/core/src/main/java/org/apache/calcite/prepare/PlannerImpl.java
+++ b/core/src/main/java/org/apache/calcite/prepare/PlannerImpl.java
@@ -47,6 +47,7 @@
 import org.apache.calcite.sql.SqlOperatorTable;
 import org.apache.calcite.sql.parser.SqlParseException;
 import org.apache.calcite.sql.parser.SqlParser;
+import org.apache.calcite.sql.util.ChainedSqlOperatorTable;
 import org.apache.calcite.sql.validate.SqlConformance;
 import org.apache.calcite.sql.validate.SqlValidator;
 import org.apache.calcite.sql2rel.RelDecorrelator;
@@ -211,12 +212,7 @@
 
   public SqlNode validate(SqlNode sqlNode) throws ValidationException {
     ensure(State.STATE_3_PARSED);
-    final SqlConformance conformance = conformance();
-    final CalciteCatalogReader catalogReader = createCatalogReader();
-    this.validator =
-        new CalciteSqlValidator(operatorTable, catalogReader, typeFactory,
-            conformance);
-    this.validator.setIdentifierExpansion(true);
+    this.validator = createSqlValidator(createCatalogReader());
     try {
       validatedSqlNode = validator.validate(sqlNode);
     } catch (RuntimeException e) {
@@ -294,13 +290,9 @@
       throw new RuntimeException("parse failed", e);
     }
 
-    final SqlConformance conformance = conformance();
     final CalciteCatalogReader catalogReader =
         createCatalogReader().withSchemaPath(schemaPath);
-    final SqlValidator validator =
-        new CalciteSqlValidator(operatorTable, catalogReader, typeFactory,
-            conformance);
-    validator.setIdentifierExpansion(true);
+    final SqlValidator validator = createSqlValidator(catalogReader);
 
     final RexBuilder rexBuilder = createRexBuilder();
     final RelOptCluster cluster = RelOptCluster.create(planner, rexBuilder);
@@ -334,6 +326,16 @@
         typeFactory, connectionConfig);
   }
 
+  private SqlValidator createSqlValidator(CalciteCatalogReader catalogReader) {
+    final SqlConformance conformance = conformance();
+    final SqlOperatorTable opTab =
+        ChainedSqlOperatorTable.of(operatorTable, catalogReader);
+    final SqlValidator validator =
+        new CalciteSqlValidator(opTab, catalogReader, typeFactory, conformance);
+    validator.setIdentifierExpansion(true);
+    return validator;
+  }
+
   private static SchemaPlus rootSchema(SchemaPlus schema) {
     for (;;) {
       if (schema.getParentSchema() == null) {
diff --git a/core/src/test/java/org/apache/calcite/tools/PlannerTest.java b/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
index 3d73774..dd247dd 100644
--- a/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
+++ b/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
@@ -59,6 +59,7 @@
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.schema.impl.ScalarFunctionImpl;
 import org.apache.calcite.sql.SqlAggFunction;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlDialect;
@@ -82,6 +83,7 @@
 import org.apache.calcite.test.CalciteAssert;
 import org.apache.calcite.test.RelBuilderTest;
 import org.apache.calcite.util.Optionality;
+import org.apache.calcite.util.Smalls;
 import org.apache.calcite.util.Util;
 
 import com.google.common.base.Throwables;
@@ -246,6 +248,27 @@
     }
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-3547">[CALCITE-3547]
+   * SqlValidatorException because Planner cannot find UDFs added to schema</a>. */
+  @Test public void testValidateUserDefinedFunctionInSchema() throws Exception {
+    SchemaPlus rootSchema = Frameworks.createRootSchema(true);
+    rootSchema.add("my_plus",
+        ScalarFunctionImpl.create(Smalls.MyPlusFunction.class, "eval"));
+    final FrameworkConfig config = Frameworks.newConfigBuilder()
+        .defaultSchema(
+            CalciteAssert.addSchema(rootSchema, CalciteAssert.SchemaSpec.HR))
+        .build();
+    final Planner planner = Frameworks.getPlanner(config);
+    final String sql = "select \"my_plus\"(\"deptno\", 100) as \"p\"\n"
+        + "from \"hr\".\"emps\"";
+    SqlNode parse = planner.parse(sql);
+    SqlNode validate = planner.validate(parse);
+    assertThat(Util.toLinux(validate.toString()),
+        equalTo("SELECT `my_plus`(`emps`.`deptno`, 100) AS `p`\n"
+            + "FROM `hr`.`emps` AS `emps`"));
+  }
+
   private Planner getPlanner(List<RelTraitDef> traitDefs, Program... programs) {
     return getPlanner(traitDefs, SqlParser.Config.DEFAULT, programs);
   }