You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2015/05/13 21:06:44 UTC
[13/13] incubator-calcite git commit: [CALCITE-505] Support
modifiable view
[CALCITE-505] Support modifiable view
Project: http://git-wip-us.apache.org/repos/asf/incubator-calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-calcite/commit/bc45a2c0
Tree: http://git-wip-us.apache.org/repos/asf/incubator-calcite/tree/bc45a2c0
Diff: http://git-wip-us.apache.org/repos/asf/incubator-calcite/diff/bc45a2c0
Branch: refs/heads/master
Commit: bc45a2c047349826e22641e82468a2d4dad948e3
Parents: aee32bc
Author: Julian Hyde <jh...@apache.org>
Authored: Thu Mar 19 09:56:09 2015 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Tue May 12 14:18:10 2015 -0700
----------------------------------------------------------------------
.../adapter/enumerable/RexToLixTranslator.java | 3 +
.../org/apache/calcite/jdbc/CalcitePrepare.java | 61 +++--
.../java/org/apache/calcite/model/JsonView.java | 14 ++
.../org/apache/calcite/model/ModelHandler.java | 3 +-
.../org/apache/calcite/plan/RelOptUtil.java | 48 +++-
.../calcite/prepare/CalcitePrepareImpl.java | 149 ++++++++++++-
.../apache/calcite/prepare/RelOptTableImpl.java | 146 +++++++++++-
.../java/org/apache/calcite/rex/RexCopier.java | 18 +-
.../apache/calcite/runtime/CalciteResource.java | 9 +
.../apache/calcite/schema/ModifiableTable.java | 2 +
.../apache/calcite/schema/ModifiableView.java | 69 ++++++
.../java/org/apache/calcite/schema/Path.java | 38 ++++
.../java/org/apache/calcite/schema/Schemas.java | 94 +++++++-
.../schema/impl/MaterializedViewTable.java | 2 +-
.../apache/calcite/schema/impl/ViewTable.java | 100 +++++++--
.../calcite/sql2rel/SqlToRelConverter.java | 98 ++++++--
.../calcite/runtime/CalciteResource.properties | 3 +
.../apache/calcite/sql/test/SqlAdvisorTest.java | 1 +
.../org/apache/calcite/test/CalciteAssert.java | 6 +-
.../calcite/test/JdbcFrontLinqBackTest.java | 93 ++++----
.../java/org/apache/calcite/test/JdbcTest.java | 223 ++++++++++++++++++-
.../apache/calcite/test/MockCatalogReader.java | 223 +++++++++++++++----
.../calcite/test/ReflectiveSchemaTest.java | 10 +-
.../calcite/test/SqlToRelConverterTest.java | 16 ++
.../apache/calcite/test/SqlValidatorTest.java | 6 +
.../calcite/test/SqlToRelConverterTest.xml | 40 ++++
doc/model.md | 26 ++-
.../calcite/linq4j/tree/ConstantExpression.java | 27 ++-
.../apache/calcite/linq4j/tree/Expressions.java | 6 +-
.../apache/calcite/linq4j/test/Linq4jTest.java | 33 +++
30 files changed, 1371 insertions(+), 196 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
index 9e7502d..eed3d9d 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
@@ -586,6 +586,9 @@ public class RexToLixTranslator {
final Object value2;
switch (literal.getType().getSqlTypeName()) {
case DECIMAL:
+ if (javaClass == float.class) {
+ return Expressions.constant(value, javaClass);
+ }
assert javaClass == BigDecimal.class;
return Expressions.new_(BigDecimal.class,
Expressions.constant(value.toString()));
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/core/src/main/java/org/apache/calcite/jdbc/CalcitePrepare.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/jdbc/CalcitePrepare.java b/core/src/main/java/org/apache/calcite/jdbc/CalcitePrepare.java
index acbdfe9..c792ed2 100644
--- a/core/src/main/java/org/apache/calcite/jdbc/CalcitePrepare.java
+++ b/core/src/main/java/org/apache/calcite/jdbc/CalcitePrepare.java
@@ -33,14 +33,19 @@ import org.apache.calcite.prepare.CalcitePrepareImpl;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rex.RexNode;
import org.apache.calcite.runtime.ArrayBindable;
import org.apache.calcite.runtime.Bindable;
+import org.apache.calcite.schema.Table;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.validate.SqlValidator;
+import org.apache.calcite.util.ImmutableIntList;
import org.apache.calcite.util.Stacks;
import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.google.common.collect.ImmutableList;
+
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
@@ -61,7 +66,7 @@ public interface CalcitePrepare {
ThreadLocal<ArrayList<Context>> THREAD_CONTEXT_STACK =
new ThreadLocal<ArrayList<Context>>() {
@Override protected ArrayList<Context> initialValue() {
- return new ArrayList<Context>();
+ return new ArrayList<>();
}
};
@@ -69,6 +74,16 @@ public interface CalcitePrepare {
ConvertResult convert(Context context, String sql);
+ /** Analyzes a view.
+ *
+ * @param context Context
+ * @param sql View SQL
+ * @param fail Whether to fail (and throw a descriptive error message) if the
+ * view is not modifiable
+ * @return Result of analyzing the view
+ */
+ AnalyzeViewResult analyzeView(Context context, String sql, boolean fail);
+
<T> CalciteSignature<T> prepareSql(
Context context,
String sql,
@@ -97,7 +112,7 @@ public interface CalcitePrepare {
}
/** Callback to register Spark as the main engine. */
- public interface SparkHandler {
+ interface SparkHandler {
RelNode flattenTypes(RelOptPlanner planner, RelNode rootRel,
boolean restructure);
@@ -118,9 +133,11 @@ public interface CalcitePrepare {
/** Namespace that allows us to define non-abstract methods inside an
* interface. */
- public static class Dummy {
+ class Dummy {
private static SparkHandler sparkHandler;
+ private Dummy() {}
+
/** Returns a spark handler. Returns a trivial handler, for which
* {@link SparkHandler#enabled()} returns {@code false}, if {@code enable}
* is {@code false} or if Spark is not on the class path. Never returns
@@ -140,13 +157,10 @@ public interface CalcitePrepare {
return (CalcitePrepare.SparkHandler) method.invoke(null);
} catch (ClassNotFoundException e) {
return new TrivialSparkHandler();
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- } catch (ClassCastException e) {
- throw new RuntimeException(e);
- } catch (NoSuchMethodException e) {
- throw new RuntimeException(e);
- } catch (InvocationTargetException e) {
+ } catch (IllegalAccessException
+ | ClassCastException
+ | InvocationTargetException
+ | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
@@ -189,7 +203,7 @@ public interface CalcitePrepare {
}
/** The result of parsing and validating a SQL query. */
- public static class ParseResult {
+ class ParseResult {
public final CalcitePrepareImpl prepare;
public final String sql; // for debug
public final SqlNode sqlNode;
@@ -210,7 +224,7 @@ public interface CalcitePrepare {
/** The result of parsing and validating a SQL query and converting it to
* relational algebra. */
- public static class ConvertResult extends ParseResult {
+ class ConvertResult extends ParseResult {
public final RelNode relNode;
public ConvertResult(CalcitePrepareImpl prepare, SqlValidator validator,
@@ -220,10 +234,31 @@ public interface CalcitePrepare {
}
}
+ /** The result of analyzing a view. */
+ class AnalyzeViewResult extends ConvertResult {
+ /** Not null if and only if the view is modifiable. */
+ public final Table table;
+ public final ImmutableList<String> tablePath;
+ public final RexNode constraint;
+ public final ImmutableIntList columnMapping;
+
+ public AnalyzeViewResult(CalcitePrepareImpl prepare,
+ SqlValidator validator, String sql, SqlNode sqlNode,
+ RelDataType rowType, RelNode relNode, Table table,
+ ImmutableList<String> tablePath, RexNode constraint,
+ ImmutableIntList columnMapping) {
+ super(prepare, validator, sql, sqlNode, rowType, relNode);
+ this.table = table;
+ this.tablePath = tablePath;
+ this.constraint = constraint;
+ this.columnMapping = columnMapping;
+ }
+ }
+
/** The result of preparing a query. It gives the Avatica driver framework
* the information it needs to create a prepared statement, or to execute a
* statement directly, without an explicit prepare step. */
- public static class CalciteSignature<T> extends Meta.Signature {
+ class CalciteSignature<T> extends Meta.Signature {
@JsonIgnore public final RelDataType rowType;
private final int maxRowCount;
private final Bindable<T> bindable;
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/core/src/main/java/org/apache/calcite/model/JsonView.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/model/JsonView.java b/core/src/main/java/org/apache/calcite/model/JsonView.java
index f482765..48e1cf3 100644
--- a/core/src/main/java/org/apache/calcite/model/JsonView.java
+++ b/core/src/main/java/org/apache/calcite/model/JsonView.java
@@ -31,6 +31,20 @@ public class JsonView extends JsonTable {
* to current schema. */
public List<String> path;
+ /** Whether this view should allow INSERT requests.
+ *
+ * <p>The values have the following meanings:
+ * <ul>
+ * <li>If true, Calcite throws an error when validating the schema if the
+ * view is not modifiable.
+ * <li>If null, Calcite deduces whether the view is modifiable.
+ * <li>If false, Calcite will not allow inserts.
+ * </ul>
+ *
+ * <p>The default value is {@code null}.
+ */
+ public Boolean modifiable;
+
public void accept(ModelHandler handler) {
handler.visit(this);
}
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/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 01ed89e..dfdb901 100644
--- a/core/src/main/java/org/apache/calcite/model/ModelHandler.java
+++ b/core/src/main/java/org/apache/calcite/model/ModelHandler.java
@@ -331,7 +331,8 @@ public class ModelHandler {
final SchemaPlus schema = currentMutableSchema("view");
final List<String> path = Util.first(jsonView.path, currentSchemaPath());
schema.add(jsonView.name,
- ViewTable.viewMacro(schema, jsonView.getSql(), path));
+ ViewTable.viewMacro(schema, jsonView.getSql(), path,
+ jsonView.modifiable));
} catch (Exception e) {
throw new RuntimeException("Error instantiating " + jsonView, e);
}
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
index 3deb4e4..6090dd2 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
@@ -92,6 +92,7 @@ import java.util.BitSet;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -636,8 +637,8 @@ public abstract class RelOptUtil {
final RelNode rel,
RelDataType castRowType,
boolean rename) {
- return createCastRel(rel, castRowType, rename,
- RelFactories.DEFAULT_PROJECT_FACTORY);
+ return createCastRel(
+ rel, castRowType, rename, RelFactories.DEFAULT_PROJECT_FACTORY);
}
/**
@@ -2033,6 +2034,45 @@ public abstract class RelOptUtil {
return left;
}
+ /** Decomposes the WHERE clause of a view into predicates that constraint
+ * a column to a particular value.
+ *
+ * <p>This method is key to the validation of a modifiable view. Columns that
+ * are constrained to a single value can be omitted from the
+ * SELECT clause of a modifiable view.
+ *
+ * @param projectMap Mapping from column ordinal to the expression that
+ * populate that column, to be populated by this method
+ * @param filters List of remaining filters, to be populated by this method
+ * @param constraint Constraint to be analyzed
+ */
+ public static void inferViewPredicates(Map<Integer, RexNode> projectMap,
+ List<RexNode> filters, RexNode constraint) {
+ for (RexNode node : conjunctions(constraint)) {
+ switch (node.getKind()) {
+ case EQUALS:
+ final List<RexNode> operands = ((RexCall) node).getOperands();
+ RexNode o0 = operands.get(0);
+ RexNode o1 = operands.get(1);
+ if (o0 instanceof RexLiteral) {
+ o0 = operands.get(1);
+ o1 = operands.get(0);
+ }
+ if (o0.getKind() == SqlKind.CAST) {
+ o0 = ((RexCall) o0).getOperands().get(0);
+ }
+ if (o0 instanceof RexInputRef && o1 instanceof RexLiteral) {
+ final int index = ((RexInputRef) o0).getIndex();
+ if (projectMap.get(index) == null) {
+ projectMap.put(index, o1);
+ continue;
+ }
+ }
+ }
+ filters.add(node);
+ }
+ }
+
/**
* Adjusts key values in a list by some fixed amount.
*
@@ -2623,8 +2663,8 @@ public abstract class RelOptUtil {
*/
public static RelNode createProject(final RelNode child,
final List<Integer> posList) {
- return createProject(RelFactories.DEFAULT_PROJECT_FACTORY,
- child, posList);
+ return createProject(
+ RelFactories.DEFAULT_PROJECT_FACTORY, child, posList);
}
/**
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
----------------------------------------------------------------------
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 9d00a87..9ebfe8a 100644
--- a/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
+++ b/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
@@ -64,6 +64,9 @@ import org.apache.calcite.plan.hep.HepProgramBuilder;
import org.apache.calcite.plan.volcano.VolcanoPlanner;
import org.apache.calcite.rel.RelCollationTraitDef;
import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Filter;
+import org.apache.calcite.rel.core.Project;
+import org.apache.calcite.rel.core.TableScan;
import org.apache.calcite.rel.rules.AggregateExpandDistinctAggregatesRule;
import org.apache.calcite.rel.rules.AggregateReduceFunctionsRule;
import org.apache.calcite.rel.rules.AggregateStarTableRule;
@@ -87,11 +90,13 @@ import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypeFactoryImpl;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.runtime.Bindable;
import org.apache.calcite.runtime.Hook;
import org.apache.calcite.runtime.Typed;
import org.apache.calcite.schema.Schemas;
+import org.apache.calcite.schema.Table;
import org.apache.calcite.server.CalciteServerStatement;
import org.apache.calcite.sql.SqlBinaryOperator;
import org.apache.calcite.sql.SqlExplainLevel;
@@ -109,6 +114,7 @@ import org.apache.calcite.sql.validate.SqlValidatorImpl;
import org.apache.calcite.sql2rel.SqlToRelConverter;
import org.apache.calcite.sql2rel.StandardConvertletTable;
import org.apache.calcite.tools.Frameworks;
+import org.apache.calcite.util.ImmutableIntList;
import org.apache.calcite.util.Util;
import com.google.common.collect.ImmutableList;
@@ -121,10 +127,13 @@ import java.math.BigDecimal;
import java.sql.DatabaseMetaData;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import static org.apache.calcite.util.Static.RESOURCE;
+
/**
* Shit just got real.
*
@@ -223,15 +232,21 @@ public class CalcitePrepareImpl implements CalcitePrepare {
public ParseResult parse(
Context context, String sql) {
- return parse_(context, sql, false);
+ return parse_(context, sql, false, false, false);
}
public ConvertResult convert(Context context, String sql) {
- return (ConvertResult) parse_(context, sql, true);
+ return (ConvertResult) parse_(context, sql, true, false, false);
+ }
+
+ public AnalyzeViewResult analyzeView(Context context, String sql, boolean fail) {
+ return (AnalyzeViewResult) parse_(context, sql, true, true, fail);
}
- /** Shared implementation for {@link #parse} and {@link #convert}. */
- private ParseResult parse_(Context context, String sql, boolean convert) {
+ /** Shared implementation for {@link #parse}, {@link #convert} and
+ * {@link #analyzeView}. */
+ private ParseResult parse_(Context context, String sql, boolean convert,
+ boolean analyze, boolean fail) {
final JavaTypeFactory typeFactory = context.getTypeFactory();
CalciteCatalogReader catalogReader =
new CalciteCatalogReader(
@@ -250,24 +265,144 @@ public class CalcitePrepareImpl implements CalcitePrepare {
new CalciteSqlValidator(
SqlStdOperatorTable.instance(), catalogReader, typeFactory);
SqlNode sqlNode1 = validator.validate(sqlNode);
- if (!convert) {
- return new ParseResult(this, validator, sql, sqlNode1,
- validator.getValidatedNodeType(sqlNode1));
+ if (convert) {
+ return convert_(
+ context, sql, analyze, fail, catalogReader, validator, sqlNode1);
}
+ return new ParseResult(this, validator, sql, sqlNode1,
+ validator.getValidatedNodeType(sqlNode1));
+ }
+
+ private ParseResult convert_(Context context, String sql, boolean analyze,
+ boolean fail, CalciteCatalogReader catalogReader, SqlValidator validator,
+ SqlNode sqlNode1) {
+ final JavaTypeFactory typeFactory = context.getTypeFactory();
final Convention resultConvention =
ENABLE_BINDABLE ? BindableConvention.INSTANCE
: EnumerableConvention.INSTANCE;
final HepPlanner planner = new HepPlanner(new HepProgramBuilder().build());
+ planner.addRelTraitDef(ConventionTraitDef.INSTANCE);
final CalcitePreparingStmt preparingStmt =
new CalcitePreparingStmt(this, context, catalogReader, typeFactory,
context.getRootSchema(), null, planner, resultConvention);
final SqlToRelConverter converter =
preparingStmt.getSqlToRelConverter(validator, catalogReader);
+ if (analyze) {
+ converter.enableTableAccessConversion(false);
+ }
final RelNode relNode = converter.convertQuery(sqlNode1, false, true);
+ if (analyze) {
+ return analyze_(validator, sql, sqlNode1, relNode, fail);
+ }
return new ConvertResult(this, validator, sql, sqlNode1,
validator.getValidatedNodeType(sqlNode1), relNode);
}
+ private AnalyzeViewResult analyze_(SqlValidator validator, String sql,
+ SqlNode sqlNode, RelNode rel, boolean fail) {
+ final RexBuilder rexBuilder = rel.getCluster().getRexBuilder();
+ final RelNode viewRel = rel;
+ Project project;
+ if (rel instanceof Project) {
+ project = (Project) rel;
+ rel = project.getInput();
+ } else {
+ project = null;
+ }
+ Filter filter;
+ if (rel instanceof Filter) {
+ filter = (Filter) rel;
+ rel = filter.getInput();
+ } else {
+ filter = null;
+ }
+ TableScan scan;
+ if (rel instanceof TableScan) {
+ scan = (TableScan) rel;
+ } else {
+ scan = null;
+ }
+ if (scan == null) {
+ if (fail) {
+ throw validator.newValidationError(sqlNode,
+ RESOURCE.modifiableViewMustBeBasedOnSingleTable());
+ }
+ return new AnalyzeViewResult(this, validator, sql, sqlNode,
+ validator.getValidatedNodeType(sqlNode), rel, null, null, null,
+ null);
+ }
+ final RelOptTable targetRelTable = scan.getTable();
+ final RelDataType targetRowType = targetRelTable.getRowType();
+ final Table table = targetRelTable.unwrap(Table.class);
+ final List<String> tablePath = targetRelTable.getQualifiedName();
+ assert table != null;
+ List<Integer> columnMapping;
+ final Map<Integer, RexNode> projectMap = new HashMap<>();
+ if (project == null) {
+ columnMapping = ImmutableIntList.range(0, targetRowType.getFieldCount());
+ } else {
+ columnMapping = new ArrayList<>();
+ for (Ord<RexNode> node : Ord.zip(project.getProjects())) {
+ if (node.e instanceof RexInputRef) {
+ RexInputRef rexInputRef = (RexInputRef) node.e;
+ int index = rexInputRef.getIndex();
+ if (projectMap.get(index) != null) {
+ if (fail) {
+ throw validator.newValidationError(sqlNode,
+ RESOURCE.moreThanOneMappedColumn(
+ targetRowType.getFieldList().get(index).getName(),
+ Util.last(tablePath)));
+ }
+ return new AnalyzeViewResult(this, validator, sql, sqlNode,
+ validator.getValidatedNodeType(sqlNode), rel, null, null, null,
+ null);
+ }
+ projectMap.put(index, rexBuilder.makeInputRef(viewRel, node.i));
+ columnMapping.add(index);
+ } else {
+ columnMapping.add(-1);
+ }
+ }
+ }
+ final RexNode constraint;
+ if (filter != null) {
+ constraint = filter.getCondition();
+ } else {
+ constraint = rexBuilder.makeLiteral(true);
+ }
+ final List<RexNode> filters = new ArrayList<>();
+ RelOptUtil.inferViewPredicates(projectMap, filters, constraint);
+
+ // Check that all columns that are not projected have a constant value
+ for (RelDataTypeField field : targetRowType.getFieldList()) {
+ final int x = columnMapping.indexOf(field.getIndex());
+ if (x >= 0) {
+ assert Util.skip(columnMapping, x + 1).indexOf(field.getIndex()) < 0
+ : "column projected more than once; should have checked above";
+ continue; // target column is projected
+ }
+ if (projectMap.get(field.getIndex()) != null) {
+ continue; // constant expression
+ }
+ if (field.getType().isNullable()) {
+ continue; // don't need expression for nullable columns; NULL suffices
+ }
+ if (fail) {
+ throw validator.newValidationError(sqlNode,
+ RESOURCE.noValueSuppliedForViewColumn(field.getName(),
+ Util.last(tablePath)));
+ }
+ return new AnalyzeViewResult(this, validator, sql, sqlNode,
+ validator.getValidatedNodeType(sqlNode), rel, null, null, null,
+ null);
+ }
+
+ return new AnalyzeViewResult(this, validator, sql, sqlNode,
+ validator.getValidatedNodeType(sqlNode), rel, table,
+ ImmutableList.copyOf(tablePath),
+ constraint, ImmutableIntList.copyOf(columnMapping));
+ }
+
/** Factory method for default SQL parser. */
protected SqlParser createParser(String sql) {
return createParser(sql, createParserConfig());
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java b/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java
index 8925ede..9919cec 100644
--- a/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java
+++ b/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java
@@ -19,6 +19,7 @@ package org.apache.calcite.prepare;
import org.apache.calcite.adapter.enumerable.EnumerableTableScan;
import org.apache.calcite.jdbc.CalciteSchema;
import org.apache.calcite.linq4j.tree.Expression;
+import org.apache.calcite.materialize.Lattice;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptSchema;
import org.apache.calcite.plan.RelOptTable;
@@ -32,9 +33,13 @@ import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.schema.ExtensibleTable;
import org.apache.calcite.schema.FilterableTable;
+import org.apache.calcite.schema.ModifiableTable;
+import org.apache.calcite.schema.Path;
import org.apache.calcite.schema.ProjectableFilterableTable;
import org.apache.calcite.schema.QueryableTable;
import org.apache.calcite.schema.ScannableTable;
+import org.apache.calcite.schema.Schema;
+import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.schema.Schemas;
import org.apache.calcite.schema.StreamableTable;
import org.apache.calcite.schema.Table;
@@ -43,6 +48,7 @@ import org.apache.calcite.sql.SqlAccessType;
import org.apache.calcite.sql.validate.SqlModality;
import org.apache.calcite.sql.validate.SqlMonotonicity;
import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Util;
import com.google.common.base.Function;
@@ -50,7 +56,9 @@ import com.google.common.base.Functions;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
+import java.util.Collection;
import java.util.List;
+import java.util.Set;
/**
* Implementation of {@link org.apache.calcite.plan.RelOptTable}.
@@ -99,6 +107,15 @@ public class RelOptTableImpl implements Prepare.PreparingTable {
}
public static RelOptTableImpl create(RelOptSchema schema, RelDataType rowType,
+ Table table, Path path) {
+ final SchemaPlus schemaPlus = MySchemaPlus.create(path);
+ Function<Class, Expression> expressionFunction =
+ getClassExpressionFunction(schemaPlus, Util.last(path).left, table);
+ return new RelOptTableImpl(schema, rowType, Pair.left(path), table,
+ expressionFunction, table.getStatistic().getRowCount());
+ }
+
+ public static RelOptTableImpl create(RelOptSchema schema, RelDataType rowType,
final CalciteSchema.TableEntry tableEntry, Double rowCount) {
final Table table = tableEntry.getTable();
Function<Class, Expression> expressionFunction =
@@ -108,13 +125,18 @@ public class RelOptTableImpl implements Prepare.PreparingTable {
}
private static Function<Class, Expression> getClassExpressionFunction(
- final CalciteSchema.TableEntry tableEntry, final Table table) {
+ CalciteSchema.TableEntry tableEntry, Table table) {
+ return getClassExpressionFunction(tableEntry.schema.plus(), tableEntry.name,
+ table);
+ }
+
+ private static Function<Class, Expression> getClassExpressionFunction(
+ final SchemaPlus schema, final String tableName, final Table table) {
if (table instanceof QueryableTable) {
final QueryableTable queryableTable = (QueryableTable) table;
return new Function<Class, Expression>() {
public Expression apply(Class clazz) {
- return queryableTable.getExpression(tableEntry.schema.plus(),
- tableEntry.name, clazz);
+ return queryableTable.getExpression(schema, tableName, clazz);
}
};
} else if (table instanceof ScannableTable
@@ -122,14 +144,12 @@ public class RelOptTableImpl implements Prepare.PreparingTable {
|| table instanceof ProjectableFilterableTable) {
return new Function<Class, Expression>() {
public Expression apply(Class clazz) {
- return Schemas.tableExpression(tableEntry.schema.plus(),
- Object[].class,
- tableEntry.name,
+ return Schemas.tableExpression(schema, Object[].class, tableName,
table.getClass());
}
};
} else if (table instanceof StreamableTable) {
- return getClassExpressionFunction(tableEntry,
+ return getClassExpressionFunction(schema, tableName,
((StreamableTable) table).stream());
} else {
return new Function<Class, Expression>() {
@@ -145,7 +165,8 @@ public class RelOptTableImpl implements Prepare.PreparingTable {
RelDataType rowType,
Table table) {
assert table instanceof TranslatableTable
- || table instanceof ScannableTable;
+ || table instanceof ScannableTable
+ || table instanceof ModifiableTable;
return new RelOptTableImpl(schema, rowType, ImmutableList.<String>of(),
table, null, null);
}
@@ -299,6 +320,115 @@ public class RelOptTableImpl implements Prepare.PreparingTable {
public SqlAccessType getAllowedAccess() {
return SqlAccessType.ALL;
}
+
+ /** Im0plementation of {@link SchemaPlus} that wraps a regular schema and knows
+ * its name and parent.
+ *
+ * <p>It is read-only, and functionality is limited in other ways, it but
+ * allows table expressions to be genenerated. */
+ private static class MySchemaPlus implements SchemaPlus {
+ private final SchemaPlus parent;
+ private final String name;
+ private final Schema schema;
+
+ public MySchemaPlus(SchemaPlus parent, String name, Schema schema) {
+ this.parent = parent;
+ this.name = name;
+ this.schema = schema;
+ }
+
+ public static MySchemaPlus create(Path path) {
+ final Pair<String, Schema> pair = Util.last(path);
+ final SchemaPlus parent;
+ if (path.size() == 1) {
+ parent = null;
+ } else {
+ parent = create(path.parent());
+ }
+ return new MySchemaPlus(parent, pair.left, pair.right);
+ }
+
+ @Override public SchemaPlus getParentSchema() {
+ return parent;
+ }
+
+ @Override public String getName() {
+ return name;
+ }
+
+ @Override public SchemaPlus getSubSchema(String name) {
+ final Schema subSchema = schema.getSubSchema(name);
+ return subSchema == null ? null : new MySchemaPlus(this, name, subSchema);
+ }
+
+ @Override public SchemaPlus add(String name, Schema schema) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override public void add(String name, Table table) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override public void add(String name,
+ org.apache.calcite.schema.Function function) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override public void add(String name, Lattice lattice) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override public boolean isMutable() {
+ return schema.isMutable();
+ }
+
+ @Override public <T> T unwrap(Class<T> clazz) {
+ return null;
+ }
+
+ @Override public void setPath(ImmutableList<ImmutableList<String>> path) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override public void setCacheEnabled(boolean cache) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override public boolean isCacheEnabled() {
+ return false;
+ }
+
+ @Override public Table getTable(String name) {
+ return schema.getTable(name);
+ }
+
+ @Override public Set<String> getTableNames() {
+ return schema.getTableNames();
+ }
+
+ @Override public Collection<org.apache.calcite.schema.Function>
+ getFunctions(String name) {
+ return schema.getFunctions(name);
+ }
+
+ @Override public Set<String> getFunctionNames() {
+ return schema.getFunctionNames();
+ }
+
+ @Override public Set<String> getSubSchemaNames() {
+ return schema.getSubSchemaNames();
+ }
+
+ @Override public Expression getExpression(SchemaPlus parentSchema,
+ String name) {
+ return schema.getExpression(parentSchema, name);
+ }
+
+ @Override public boolean contentsHaveChangedSince(long lastCheck,
+ long now) {
+ return schema.contentsHaveChangedSince(lastCheck, now);
+ }
+ }
}
// End RelOptTableImpl.java
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/core/src/main/java/org/apache/calcite/rex/RexCopier.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rex/RexCopier.java b/core/src/main/java/org/apache/calcite/rex/RexCopier.java
index 708a59a..3563cdd 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexCopier.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexCopier.java
@@ -16,6 +16,8 @@
*/
package org.apache.calcite.rex;
+import org.apache.calcite.rel.type.RelDataType;
+
/**
* Shuttle which creates a deep copy of a Rex expression.
*
@@ -45,6 +47,10 @@ class RexCopier extends RexShuttle {
//~ Methods ----------------------------------------------------------------
+ private RelDataType copy(RelDataType type) {
+ return builder.getTypeFactory().copyType(type);
+ }
+
public RexNode visitOver(RexOver over) {
throw new UnsupportedOperationException();
}
@@ -55,8 +61,7 @@ class RexCopier extends RexShuttle {
public RexNode visitCall(final RexCall call) {
final boolean[] update = null;
- return builder.makeCall(
- builder.getTypeFactory().copyType(call.getType()),
+ return builder.makeCall(copy(call.getType()),
call.getOperator(),
visitList(call.getOperands(), update));
}
@@ -66,13 +71,12 @@ class RexCopier extends RexShuttle {
}
public RexNode visitFieldAccess(RexFieldAccess fieldAccess) {
- return builder.makeFieldAccess(
- fieldAccess.getReferenceExpr().accept(this),
+ return builder.makeFieldAccess(fieldAccess.getReferenceExpr().accept(this),
fieldAccess.getField().getIndex());
}
public RexNode visitInputRef(RexInputRef inputRef) {
- throw new UnsupportedOperationException();
+ return builder.makeInputRef(copy(inputRef.getType()), inputRef.getIndex());
}
public RexNode visitLocalRef(RexLocalRef localRef) {
@@ -80,9 +84,7 @@ class RexCopier extends RexShuttle {
}
public RexNode visitLiteral(RexLiteral literal) {
- return new RexLiteral(
- literal.getValue(),
- builder.getTypeFactory().copyType(literal.getType()),
+ return new RexLiteral(literal.getValue(), copy(literal.getType()),
literal.getTypeName());
}
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
index 1e8badf..ee3cb5a 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
@@ -573,6 +573,15 @@ public interface CalciteResource {
@BaseMessage("Cannot stream VALUES")
ExInst<SqlValidatorException> cannotStreamValues();
+
+ @BaseMessage("Modifiable view must be based on a single table")
+ ExInst<SqlValidatorException> modifiableViewMustBeBasedOnSingleTable();
+
+ @BaseMessage("View is not modifiable. More than one expression maps to column ''{0}'' of base table ''{1}''")
+ ExInst<SqlValidatorException> moreThanOneMappedColumn(String columnName, String tableName);
+
+ @BaseMessage("View is not modifiable. No value is supplied for NOT NULL column ''{0}'' of base table ''{1}''")
+ ExInst<SqlValidatorException> noValueSuppliedForViewColumn(String columnName, String tableName);
}
// End CalciteResource.java
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/core/src/main/java/org/apache/calcite/schema/ModifiableTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/schema/ModifiableTable.java b/core/src/main/java/org/apache/calcite/schema/ModifiableTable.java
index 85b2a87..ce0f363 100644
--- a/core/src/main/java/org/apache/calcite/schema/ModifiableTable.java
+++ b/core/src/main/java/org/apache/calcite/schema/ModifiableTable.java
@@ -30,6 +30,8 @@ import java.util.List;
*
* <p>NOTE: The current API is inefficient and experimental. It will change
* without notice.</p>
+ *
+ * @see ModifiableView
*/
public interface ModifiableTable extends QueryableTable {
/** Returns the modifiable collection.
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/core/src/main/java/org/apache/calcite/schema/ModifiableView.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/schema/ModifiableView.java b/core/src/main/java/org/apache/calcite/schema/ModifiableView.java
new file mode 100644
index 0000000..c1a2d81
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/schema/ModifiableView.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.schema;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.util.ImmutableIntList;
+
+/**
+ * A modifiable view onto {@link ModifiableTable}.
+ *
+ * <p>It describes how its columns map onto the underlying table's columns,
+ * and any constraints that incoming rows must satisfy.
+ *
+ * <p>For example, given
+ *
+ * <blockquote><pre>
+ * CREATE TABLE emps (empno INTEGER, gender VARCHAR(1), deptno INTEGER);
+ * CREATE VIEW female_emps AS
+ * SELECT empno, deptno FROM emps WHERE gender = 'F';
+ * </pre></blockquote>
+ *
+ * constraint is {@code $1 = 'F'}
+ * and column mapping is {@code [0, 2]}.
+ *
+ * <p>NOTE: The current API is inefficient and experimental. It will change
+ * without notice.</p>
+ */
+public interface ModifiableView extends Table {
+ /** Returns a constraint that each candidate row must satisfy.
+ *
+ * <p>Never null; if there is no constraint, returns "true".
+ *
+ * @param rexBuilder Rex builder
+ * @param tableRowType Row type of the table that this view maps onto
+ */
+ RexNode getConstraint(RexBuilder rexBuilder, RelDataType tableRowType);
+
+ /** Returns the column mapping onto another table.
+ *
+ * {@code mapping[i]} contains the column of the underlying table that the
+ * {@code i}th column of the view comes from, or -1 if it is based on an
+ * expression.
+ */
+ ImmutableIntList getColumnMapping();
+
+ /** Returns the underlying table. */
+ Table getTable();
+
+ /** Returns the full path of the underlying table. */
+ Path getTablePath();
+}
+
+// End ModifiableView.java
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/core/src/main/java/org/apache/calcite/schema/Path.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/schema/Path.java b/core/src/main/java/org/apache/calcite/schema/Path.java
new file mode 100644
index 0000000..7c363a0
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/schema/Path.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.schema;
+
+import org.apache.calcite.util.Pair;
+
+import java.util.List;
+import java.util.RandomAccess;
+
+/**
+ * Path from a root schema to a particular object (schema, table, function).
+ *
+ * <p>Examples:
+ * <ul>
+ * <li>The root schema has a single element [(root, "")].
+ * <li>A direct child "foo" of the root schema has a two elements
+ * [(root, ""), (child, "foo")].
+ * </ul>
+ */
+public interface Path extends List<Pair<String, Schema>>, RandomAccess {
+ Path parent();
+}
+
+// End Path.java
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/core/src/main/java/org/apache/calcite/schema/Schemas.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/schema/Schemas.java b/core/src/main/java/org/apache/calcite/schema/Schemas.java
index 63d0e4e..b634233 100644
--- a/core/src/main/java/org/apache/calcite/schema/Schemas.java
+++ b/core/src/main/java/org/apache/calcite/schema/Schemas.java
@@ -39,6 +39,7 @@ import org.apache.calcite.rel.type.RelProtoDataType;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.sql.type.SqlTypeUtil;
import org.apache.calcite.util.BuiltInMethod;
+import org.apache.calcite.util.Pair;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
@@ -47,6 +48,7 @@ import com.google.common.collect.Lists;
import java.lang.reflect.Type;
import java.sql.Connection;
+import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -92,8 +94,7 @@ public final class Schemas {
String name,
Collection<CalciteSchema.FunctionEntry> functionEntries,
List<RelDataType> argumentTypes) {
- final List<CalciteSchema.FunctionEntry> matches =
- new ArrayList<CalciteSchema.FunctionEntry>();
+ final List<CalciteSchema.FunctionEntry> matches = new ArrayList<>();
for (CalciteSchema.FunctionEntry entry : functionEntries) {
if (matches(typeFactory, entry.getFunction(), argumentTypes)) {
matches.add(entry);
@@ -285,9 +286,10 @@ public final class Schemas {
final CalciteConnection connection, final CalciteSchema schema,
final List<String> schemaPath, final String sql) {
final CalcitePrepare prepare = CalcitePrepare.DEFAULT_FACTORY.apply();
+ final ImmutableMap<CalciteConnectionProperty, String> propValues =
+ ImmutableMap.of();
final CalcitePrepare.Context context =
- makeContext(connection, schema, schemaPath,
- ImmutableMap.<CalciteConnectionProperty, String>of());
+ makeContext(connection, schema, schemaPath, propValues);
CalcitePrepare.Dummy.push(context);
try {
return prepare.parse(context, sql);
@@ -302,9 +304,10 @@ public final class Schemas {
final CalciteConnection connection, final CalciteSchema schema,
final List<String> schemaPath, final String sql) {
final CalcitePrepare prepare = CalcitePrepare.DEFAULT_FACTORY.apply();
+ final ImmutableMap<CalciteConnectionProperty, String> propValues =
+ ImmutableMap.of();
final CalcitePrepare.Context context =
- makeContext(connection, schema, schemaPath,
- ImmutableMap.<CalciteConnectionProperty, String>of());
+ makeContext(connection, schema, schemaPath, propValues);
CalcitePrepare.Dummy.push(context);
try {
return prepare.convert(context, sql);
@@ -313,6 +316,23 @@ public final class Schemas {
}
}
+ /** Analyzes a view. For use within Calcite only. */
+ public static CalcitePrepare.AnalyzeViewResult analyzeView(
+ final CalciteConnection connection, final CalciteSchema schema,
+ final List<String> schemaPath, final String sql, boolean fail) {
+ final CalcitePrepare prepare = CalcitePrepare.DEFAULT_FACTORY.apply();
+ final ImmutableMap<CalciteConnectionProperty, String> propValues =
+ ImmutableMap.of();
+ final CalcitePrepare.Context context =
+ makeContext(connection, schema, schemaPath, propValues);
+ CalcitePrepare.Dummy.push(context);
+ try {
+ return prepare.analyzeView(context, sql, fail);
+ } finally {
+ CalcitePrepare.Dummy.pop(context);
+ }
+ }
+
/** Prepares a SQL query for execution. For use within Calcite only. */
public static CalcitePrepare.CalciteSignature<Object> prepare(
final CalciteConnection connection, final CalciteSchema schema,
@@ -470,6 +490,30 @@ public final class Schemas {
return t;
}
+ /** Creates a path with a given list of names starting from a given root
+ * schema. */
+ public static Path path(CalciteSchema rootSchema, Iterable<String> names) {
+ final ImmutableList.Builder<Pair<String, Schema>> builder =
+ ImmutableList.builder();
+ Schema schema = rootSchema.schema;
+ final Iterator<String> iterator = names.iterator();
+ if (!iterator.hasNext()) {
+ return PathImpl.EMPTY;
+ }
+ for (;;) {
+ final String name = iterator.next();
+ builder.add(Pair.of(name, schema));
+ if (!iterator.hasNext()) {
+ return path(builder.build());
+ }
+ schema = schema.getSubSchema(name);
+ }
+ }
+
+ public static PathImpl path(ImmutableList<Pair<String, Schema>> build) {
+ return new PathImpl(build);
+ }
+
/** Dummy data context that has no variables. */
private static class DummyDataContext implements DataContext {
private final CalciteConnection connection;
@@ -497,6 +541,44 @@ public final class Schemas {
return map.get(name);
}
}
+
+ /** Implementation of {@link Path}. */
+ private static class PathImpl
+ extends AbstractList<Pair<String, Schema>> implements Path {
+ private final ImmutableList<Pair<String, Schema>> pairs;
+
+ private static final PathImpl EMPTY =
+ new PathImpl(ImmutableList.<Pair<String, Schema>>of());
+
+ PathImpl(ImmutableList<Pair<String, Schema>> pairs) {
+ this.pairs = pairs;
+ }
+
+ @Override public boolean equals(Object o) {
+ return this == o
+ || o instanceof PathImpl
+ && pairs.equals(((PathImpl) o).pairs);
+ }
+
+ @Override public int hashCode() {
+ return pairs.hashCode();
+ }
+
+ public Pair<String, Schema> get(int index) {
+ return pairs.get(index);
+ }
+
+ public int size() {
+ return pairs.size();
+ }
+
+ @Override public Path parent() {
+ if (pairs.isEmpty()) {
+ throw new IllegalArgumentException("at root");
+ }
+ return new PathImpl(pairs.subList(0, pairs.size() - 1));
+ }
+ }
}
// End Schemas.java
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/core/src/main/java/org/apache/calcite/schema/impl/MaterializedViewTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/schema/impl/MaterializedViewTable.java b/core/src/main/java/org/apache/calcite/schema/impl/MaterializedViewTable.java
index 5810f76..27e873a 100644
--- a/core/src/main/java/org/apache/calcite/schema/impl/MaterializedViewTable.java
+++ b/core/src/main/java/org/apache/calcite/schema/impl/MaterializedViewTable.java
@@ -103,7 +103,7 @@ public class MaterializedViewTable extends ViewTable {
private MaterializedViewTableMacro(CalciteSchema schema, String viewSql,
List<String> viewSchemaPath, String suggestedTableName) {
- super(schema, viewSql, viewSchemaPath);
+ super(schema, viewSql, viewSchemaPath, Boolean.TRUE);
this.key = Preconditions.checkNotNull(
MaterializationService.instance().defineMaterialization(
schema, null, viewSql, schemaPath, suggestedTableName, true));
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/core/src/main/java/org/apache/calcite/schema/impl/ViewTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/schema/impl/ViewTable.java b/core/src/main/java/org/apache/calcite/schema/impl/ViewTable.java
index c978a3a..b3a8ff7 100644
--- a/core/src/main/java/org/apache/calcite/schema/impl/ViewTable.java
+++ b/core/src/main/java/org/apache/calcite/schema/impl/ViewTable.java
@@ -29,12 +29,18 @@ import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypeImpl;
import org.apache.calcite.rel.type.RelProtoDataType;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexNode;
import org.apache.calcite.schema.FunctionParameter;
+import org.apache.calcite.schema.ModifiableView;
+import org.apache.calcite.schema.Path;
import org.apache.calcite.schema.Schema;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.schema.Schemas;
+import org.apache.calcite.schema.Table;
import org.apache.calcite.schema.TableMacro;
import org.apache.calcite.schema.TranslatableTable;
+import org.apache.calcite.util.ImmutableIntList;
import org.apache.calcite.util.Util;
import com.google.common.collect.ImmutableList;
@@ -64,9 +70,33 @@ public class ViewTable
}
/** Table macro that returns a view. */
+ @Deprecated // to be removed before 2.0
public static ViewTableMacro viewMacro(SchemaPlus schema,
final String viewSql, final List<String> schemaPath) {
- return new ViewTableMacro(CalciteSchema.from(schema), viewSql, schemaPath);
+ return viewMacro(schema, viewSql, schemaPath, Boolean.TRUE);
+ }
+
+ /** Table macro that returns a view.
+ *
+ * @param schema Schema the view will belong to
+ * @param viewSql SQL query
+ * @param schemaPath Path of schema
+ * @param modifiable Whether view is modifiable, or null to deduce it
+ */
+ public static ViewTableMacro viewMacro(SchemaPlus schema, String viewSql,
+ List<String> schemaPath, Boolean modifiable) {
+ return new ViewTableMacro(CalciteSchema.from(schema), viewSql, schemaPath,
+ modifiable);
+ }
+
+ /** Returns the view's SQL definition. */
+ public String getViewSql() {
+ return viewSql;
+ }
+
+ /** Returns the the schema path of the view. */
+ public List<String> getSchemaPath() {
+ return schemaPath;
}
@Override public Schema.TableType getJdbcTableType() {
@@ -80,8 +110,7 @@ public class ViewTable
public <T> Queryable<T> asQueryable(QueryProvider queryProvider,
SchemaPlus schema, String tableName) {
return queryProvider.createQuery(
- getExpression(schema, tableName, Queryable.class),
- elementType);
+ getExpression(schema, tableName, Queryable.class), elementType);
}
public RelNode toRel(
@@ -111,14 +140,16 @@ public class ViewTable
static class ViewTableMacro implements TableMacro {
protected final String viewSql;
protected final CalciteSchema schema;
+ private final Boolean modifiable;
/** Typically null. If specified, overrides the path of the schema as the
* context for validating {@code viewSql}. */
protected final List<String> schemaPath;
- ViewTableMacro(CalciteSchema schema, String viewSql,
- List<String> schemaPath) {
+ ViewTableMacro(CalciteSchema schema, String viewSql, List<String> schemaPath,
+ Boolean modifiable) {
this.viewSql = viewSql;
this.schema = schema;
+ this.modifiable = modifiable;
this.schemaPath =
schemaPath == null ? null : ImmutableList.copyOf(schemaPath);
}
@@ -128,25 +159,60 @@ public class ViewTable
}
public TranslatableTable apply(List<Object> arguments) {
- CalcitePrepare.ParseResult parsed =
- Schemas.parse(MaterializedViewTable.MATERIALIZATION_CONNECTION,
- schema, schemaPath, viewSql);
+ CalcitePrepare.AnalyzeViewResult parsed =
+ Schemas.analyzeView(MaterializedViewTable.MATERIALIZATION_CONNECTION,
+ schema, schemaPath, viewSql, modifiable != null && modifiable);
final List<String> schemaPath1 =
schemaPath != null ? schemaPath : schema.path(null);
final JavaTypeFactory typeFactory = (JavaTypeFactory) parsed.typeFactory;
- return new ViewTable(typeFactory.getJavaClass(parsed.rowType),
- RelDataTypeImpl.proto(parsed.rowType), viewSql, schemaPath1);
+ final Type elementType = typeFactory.getJavaClass(parsed.rowType);
+ if ((modifiable == null || modifiable) && parsed.table != null) {
+ return new ModifiableViewTable(elementType,
+ RelDataTypeImpl.proto(parsed.rowType), viewSql, schemaPath1,
+ parsed.table, Schemas.path(schema.root(), parsed.tablePath),
+ parsed.constraint, parsed.columnMapping);
+ } else {
+ return new ViewTable(elementType,
+ RelDataTypeImpl.proto(parsed.rowType), viewSql, schemaPath1);
+ }
}
}
- /** Returns the view's SQL definition. */
- public String getViewSql() {
- return viewSql;
- }
+ /** Extension to {@link ViewTable} that is modifiable. */
+ static class ModifiableViewTable extends ViewTable
+ implements ModifiableView {
+ private final Table table;
+ private final Path tablePath;
+ private final RexNode constraint;
+ private final ImmutableIntList columnMapping;
+
+ public ModifiableViewTable(Type elementType, RelProtoDataType rowType,
+ String viewSql, List<String> schemaPath, Table table,
+ Path tablePath, RexNode constraint,
+ ImmutableIntList columnMapping) {
+ super(elementType, rowType, viewSql, schemaPath);
+ this.table = table;
+ this.tablePath = tablePath;
+ this.constraint = constraint;
+ this.columnMapping = columnMapping;
+ }
- /** Returns the the schema path of the view. */
- public List<String> getSchemaPath() {
- return schemaPath;
+ public RexNode getConstraint(RexBuilder rexBuilder,
+ RelDataType tableRowType) {
+ return rexBuilder.copy(constraint);
+ }
+
+ public ImmutableIntList getColumnMapping() {
+ return columnMapping;
+ }
+
+ public Table getTable() {
+ return table;
+ }
+
+ public Path getTablePath() {
+ return tablePath;
+ }
}
}
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
index 5dad7f4..a190159 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -73,6 +73,8 @@ import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.rex.RexVisitorImpl;
import org.apache.calcite.rex.RexWindowBound;
import org.apache.calcite.schema.ModifiableTable;
+import org.apache.calcite.schema.ModifiableView;
+import org.apache.calcite.schema.Table;
import org.apache.calcite.schema.TranslatableTable;
import org.apache.calcite.sql.JoinConditionType;
import org.apache.calcite.sql.JoinType;
@@ -561,11 +563,7 @@ public class SqlToRelConverter {
validatedRowType = uniquifyFields(validatedRowType);
return RelOptUtil.equal(
- "validated row type",
- validatedRowType,
- "converted row type",
- convertedRowType,
- false);
+ "validated row type", validatedRowType, "converted row type", convertedRowType, false);
}
protected RelDataType uniquifyFields(RelDataType rowType) {
@@ -2024,7 +2022,8 @@ public class SqlToRelConverter {
Set<RelColumnMapping> columnMappings =
getColumnMappings(operator);
LogicalTableFunctionScan callRel =
- LogicalTableFunctionScan.create(cluster,
+ LogicalTableFunctionScan.create(
+ cluster,
inputs,
rexCall,
elementType,
@@ -2767,8 +2766,8 @@ public class SqlToRelConverter {
protected RelNode createAggregate(Blackboard bb, boolean indicator,
ImmutableBitSet groupSet, ImmutableList<ImmutableBitSet> groupSets,
List<AggregateCall> aggCalls) {
- return LogicalAggregate.create(bb.root, indicator, groupSet, groupSets,
- aggCalls);
+ return LogicalAggregate.create(
+ bb.root, indicator, groupSet, groupSets, aggCalls);
}
public RexDynamicParam convertDynamicParam(
@@ -2999,27 +2998,88 @@ public class SqlToRelConverter {
assert targetRowType != null;
RelNode sourceRel =
convertQueryRecursive(
- call.getSource(),
- false,
- targetRowType);
+ call.getSource(), false, targetRowType);
RelNode massagedRel = convertColumnList(call, sourceRel);
+ return createModify(targetTable, massagedRel);
+ }
+
+ /** Creates a relational expression to modify a table or modifiable view. */
+ private RelNode createModify(RelOptTable targetTable, RelNode source) {
final ModifiableTable modifiableTable =
targetTable.unwrap(ModifiableTable.class);
if (modifiableTable != null) {
- return modifiableTable.toModificationRel(
- cluster,
- targetTable,
- catalogReader,
- massagedRel,
- LogicalTableModify.Operation.INSERT,
- null,
+ return modifiableTable.toModificationRel(cluster, targetTable,
+ catalogReader, source, LogicalTableModify.Operation.INSERT, null,
false);
}
- return LogicalTableModify.create(targetTable, catalogReader, massagedRel,
+ final ModifiableView modifiableView =
+ targetTable.unwrap(ModifiableView.class);
+ if (modifiableView != null) {
+ final Table delegateTable = modifiableView.getTable();
+ final RelDataType delegateRowType = delegateTable.getRowType(typeFactory);
+ final RelOptTable delegateRelOptTable =
+ RelOptTableImpl.create(null, delegateRowType, delegateTable,
+ modifiableView.getTablePath());
+ final RelNode newSource =
+ createSource(targetTable, source, modifiableView, delegateRowType);
+ return createModify(delegateRelOptTable, newSource);
+ }
+ return LogicalTableModify.create(targetTable, catalogReader, source,
LogicalTableModify.Operation.INSERT, null, false);
}
+ /** Wraps a relational expression in the projects and filters implied by
+ * a {@link ModifiableView}.
+ *
+ * <p>The input relational expression is suitable for inserting into the view,
+ * and the returned relational expression is suitable for inserting into its
+ * delegate table.
+ *
+ * <p>In principle, the delegate table of a view might be another modifiable
+ * view, and if so, the process can be repeated. */
+ private RelNode createSource(RelOptTable targetTable, RelNode source,
+ ModifiableView modifiableView, RelDataType delegateRowType) {
+ final ImmutableIntList mapping = modifiableView.getColumnMapping();
+ assert mapping.size() == targetTable.getRowType().getFieldCount();
+
+ // For columns represented in the mapping, the expression is just a field
+ // reference.
+ final Map<Integer, RexNode> projectMap = new HashMap<>();
+ final List<RexNode> filters = new ArrayList<>();
+ for (int i = 0; i < mapping.size(); i++) {
+ int target = mapping.get(i);
+ if (target >= 0) {
+ projectMap.put(target, RexInputRef.of(i, source.getRowType()));
+ }
+ }
+
+ // For columns that are not in the mapping, and have a constraint of the
+ // form "column = value", the expression is the literal "value".
+ //
+ // If a column has multiple constraints, the extra ones will become a
+ // filter.
+ final RexNode constraint =
+ modifiableView.getConstraint(rexBuilder, delegateRowType);
+ RelOptUtil.inferViewPredicates(projectMap, filters, constraint);
+ final List<Pair<RexNode, String>> projects = new ArrayList<>();
+ for (RelDataTypeField field : delegateRowType.getFieldList()) {
+ RexNode node = projectMap.get(field.getIndex());
+ if (node == null) {
+ node = rexBuilder.makeNullLiteral(field.getType().getSqlTypeName());
+ }
+ projects.add(
+ Pair.of(rexBuilder.ensureType(field.getType(), node, false),
+ field.getName()));
+ }
+
+ source = RelOptUtil.createProject(source, projects, true);
+ if (filters.size() > 0) {
+ source = RelOptUtil.createFilter(source, filters);
+ }
+ return source;
+ }
+
private RelOptTable.ToRelContext createToRelContext() {
return new RelOptTable.ToRelContext() {
public RelOptCluster getCluster() {
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
----------------------------------------------------------------------
diff --git a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
index e2321bb..e170b67 100644
--- a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
+++ b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
@@ -187,4 +187,7 @@ StreamMustGroupByMonotonic=Streaming aggregation requires at least one monotonic
StreamMustOrderByMonotonic=Streaming ORDER BY must start with monotonic expression
StreamSetOpInconsistentInputs=Set operator cannot combine streaming and non-streaming inputs
CannotStreamValues=Cannot stream VALUES
+ModifiableViewMustBeBasedOnSingleTable=Modifiable view must be based on a single table
+MoreThanOneMappedColumn=View is not modifiable. More than one expression maps to column ''{0}'' of base table ''{1}''
+NoValueSuppliedForViewColumn=View is not modifiable. No value is supplied for NOT NULL column ''{0}'' of base table ''{1}''
# End CalciteResource.properties
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java b/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
index 3694094..1a71ec4 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
@@ -66,6 +66,7 @@ public class SqlAdvisorTest extends SqlValidatorTestCase {
protected static final List<String> SALES_TABLES =
Arrays.asList(
"TABLE(CATALOG.SALES.EMP)",
+ "TABLE(CATALOG.SALES.EMP_20)",
"TABLE(CATALOG.SALES.EMP_ADDRESS)",
"TABLE(CATALOG.SALES.DEPT)",
"TABLE(CATALOG.SALES.BONUS)",
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
index cc4e1f2..8160c71 100644
--- a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
+++ b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
@@ -698,7 +698,7 @@ public class CalciteAssert {
+ " ('Grace', 60, 'F'),\n"
+ " ('Wilma', cast(null as integer), 'F'))\n"
+ " as t(ename, deptno, gender)",
- ImmutableList.<String>of()));
+ ImmutableList.<String>of(), null));
post.add("DEPT",
ViewTable.viewMacro(post,
"select * from (values\n"
@@ -706,7 +706,7 @@ public class CalciteAssert {
+ " (20, 'Marketing'),\n"
+ " (30, 'Engineering'),\n"
+ " (40, 'Empty')) as t(deptno, dname)",
- ImmutableList.<String>of()));
+ ImmutableList.<String>of(), null));
post.add("EMPS",
ViewTable.viewMacro(post,
"select * from (values\n"
@@ -716,7 +716,7 @@ public class CalciteAssert {
+ " (120, 'Wilma', 20, 'F', CAST(NULL AS VARCHAR(20)), 1, 5, UNKNOWN, TRUE, DATE '2005-09-07'),\n"
+ " (130, 'Alice', 40, 'F', 'Vancouver', 2, CAST(NULL AS INT), FALSE, TRUE, DATE '2007-01-01'))\n"
+ " as t(empno, name, deptno, gender, city, empid, age, slacker, manager, joinedat)",
- ImmutableList.<String>of()));
+ ImmutableList.<String>of(), null));
return post;
default:
throw new AssertionError("unknown schema " + schema);
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/core/src/test/java/org/apache/calcite/test/JdbcFrontLinqBackTest.java
----------------------------------------------------------------------
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 a6d23e0..e4131c3 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcFrontLinqBackTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcFrontLinqBackTest.java
@@ -237,54 +237,59 @@ public class JdbcFrontLinqBackTest {
employees.add(new JdbcTest.Employee(0, 0, "first", 0f, null));
return that()
.with(CalciteAssert.Config.REGULAR)
- .with(new CalciteAssert.ConnectionPostProcessor() {
- public Connection apply(final Connection connection)
- throws SQLException {
- CalciteConnection calciteConnection =
- connection.unwrap(CalciteConnection.class);
- SchemaPlus rootSchema =
- calciteConnection.getRootSchema();
- SchemaPlus mapSchema =
- rootSchema.add("foo", new AbstractSchema());
- final String tableName = "bar";
- final JdbcTest.AbstractModifiableTable table =
- new JdbcTest.AbstractModifiableTable(tableName) {
- public RelDataType getRowType(
- RelDataTypeFactory typeFactory) {
- return ((JavaTypeFactory) typeFactory)
- .createType(JdbcTest.Employee.class);
- }
+ .with(
+ new CalciteAssert.ConnectionPostProcessor() {
+ public Connection apply(final Connection connection)
+ throws SQLException {
+ CalciteConnection calciteConnection =
+ connection.unwrap(CalciteConnection.class);
+ SchemaPlus rootSchema =
+ calciteConnection.getRootSchema();
+ SchemaPlus mapSchema =
+ rootSchema.add("foo", new AbstractSchema());
+ final String tableName = "bar";
+ final JdbcTest.AbstractModifiableTable table =
+ mutable(tableName, employees);
+ mapSchema.add(tableName, table);
+ return calciteConnection;
+ }
+ });
+ }
- public <T> Queryable<T> asQueryable(
- QueryProvider queryProvider, SchemaPlus schema,
- String tableName) {
- return new AbstractTableQueryable<T>(queryProvider,
- schema, this, tableName) {
- public Enumerator<T> enumerator() {
- //noinspection unchecked
- return (Enumerator<T>) Linq4j.enumerator(employees);
- }
- };
- }
+ static JdbcTest.AbstractModifiableTable mutable(String tableName,
+ final List<JdbcTest.Employee> employees) {
+ return new JdbcTest.AbstractModifiableTable(tableName) {
+ public RelDataType getRowType(
+ RelDataTypeFactory typeFactory) {
+ return ((JavaTypeFactory) typeFactory)
+ .createType(JdbcTest.Employee.class);
+ }
- public Type getElementType() {
- return JdbcTest.Employee.class;
- }
+ public <T> Queryable<T> asQueryable(QueryProvider queryProvider,
+ SchemaPlus schema, String tableName) {
+ return new AbstractTableQueryable<T>(queryProvider, schema, this,
+ tableName) {
+ public Enumerator<T> enumerator() {
+ //noinspection unchecked
+ return (Enumerator<T>) Linq4j.enumerator(employees);
+ }
+ };
+ }
- public Expression getExpression(SchemaPlus schema,
- String tableName, Class clazz) {
- return Schemas.tableExpression(schema, getElementType(),
- tableName, clazz);
- }
+ public Type getElementType() {
+ return JdbcTest.Employee.class;
+ }
- public Collection getModifiableCollection() {
- return employees;
- }
- };
- mapSchema.add(tableName, table);
- return calciteConnection;
- }
- });
+ public Expression getExpression(SchemaPlus schema, String tableName,
+ Class clazz) {
+ return Schemas.tableExpression(schema, getElementType(), tableName,
+ clazz);
+ }
+
+ public Collection getModifiableCollection() {
+ return employees;
+ }
+ };
}
@Test public void testInsert2() {
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/bc45a2c0/core/src/test/java/org/apache/calcite/test/JdbcTest.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 27eb0e5..8f73fbd 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -58,6 +58,7 @@ import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.runtime.Hook;
import org.apache.calcite.runtime.SqlFunctions;
import org.apache.calcite.schema.ModifiableTable;
+import org.apache.calcite.schema.ModifiableView;
import org.apache.calcite.schema.QueryableTable;
import org.apache.calcite.schema.Schema;
import org.apache.calcite.schema.SchemaFactory;
@@ -79,11 +80,13 @@ import org.apache.calcite.sql.advise.SqlAdvisorGetHintsFunction;
import org.apache.calcite.sql.parser.SqlParserUtil;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.util.Bug;
+import org.apache.calcite.util.JsonBuilder;
import org.apache.calcite.util.Litmus;
import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Util;
import com.google.common.base.Function;
+import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -130,6 +133,8 @@ import java.util.TimeZone;
import java.util.regex.Pattern;
import javax.sql.DataSource;
+import static org.apache.calcite.util.Static.RESOURCE;
+
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
@@ -137,6 +142,7 @@ import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.CoreMatchers.startsWith;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -250,6 +256,165 @@ public class JdbcTest {
return FOODMART_QUERIES;
}
+ /** Tests a modifiable view. */
+ @Test public void testModelWithModifiableView() throws Exception {
+ final List<Employee> employees = new ArrayList<>();
+ employees.add(new Employee(135, 10, "Simon", 56.7f, null));
+ try {
+ EmpDeptTableFactory.THREAD_COLLECTION.set(employees);
+ final CalciteAssert.AssertThat with = modelWithView(
+ "select \"name\", \"empid\" as e, \"salary\" "
+ + "from \"MUTABLE_EMPLOYEES\" where \"deptno\" = 10",
+ null);
+ with.query("select \"name\" from \"adhoc\".V order by \"name\"")
+ .returns("name=Simon\n");
+ with.doWithConnection(
+ new Function<CalciteConnection, Object>() {
+ @Override public Object apply(CalciteConnection input) {
+ try {
+ final Statement statement = input.createStatement();
+ ResultSet resultSet =
+ statement.executeQuery("explain plan for\n"
+ + "insert into \"adhoc\".V\n"
+ + "values ('Fred', 56, 123.4)");
+ assertThat(resultSet.next(), is(true));
+ assertThat(resultSet.getString(1),
+ is(
+ "EnumerableTableModify(table=[[adhoc, MUTABLE_EMPLOYEES]], operation=[INSERT], updateColumnList=[[]], flattened=[false])\n"
+ + " EnumerableCalc(expr#0..2=[{inputs}], expr#3=[CAST($t1):JavaType(int) NOT NULL], expr#4=[10], expr#5=[CAST($t0):JavaType(class java.lang.String)], expr#6=[CAST($t2):JavaType(float) NOT NULL], expr#7=[null], empid=[$t3], deptno=[$t4], name=[$t5], salary=[$t6], commission=[$t7])\n"
+ + " EnumerableValues(tuples=[[{ 'Fred', 56, 123.4 }]])\n"));
+
+ // With named columns
+ resultSet =
+ statement.executeQuery("explain plan for\n"
+ + "insert into \"adhoc\".V (\"name\", e, \"salary\")\n"
+ + "values ('Fred', 56, 123.4)");
+ assertThat(resultSet.next(), is(true));
+
+ // With named columns, in different order
+ resultSet =
+ statement.executeQuery("explain plan for\n"
+ + "insert into \"adhoc\".V (e, \"salary\", \"name\")\n"
+ + "values (56, 123.4, 'Fred')");
+ assertThat(resultSet.next(), is(true));
+
+ // Mis-named column
+ try {
+ final PreparedStatement s =
+ input.prepareStatement("explain plan for\n"
+ + "insert into \"adhoc\".V (empno, \"salary\", \"name\")\n"
+ + "values (56, 123.4, 'Fred')");
+ fail("expected error, got " + s);
+ } catch (SQLException e) {
+ assertThat(e.getMessage(),
+ startsWith("Error while preparing statement"));
+ }
+
+ // Fail to provide mandatory column
+ try {
+ final PreparedStatement s =
+ input.prepareStatement("explain plan for\n"
+ + "insert into \"adhoc\".V (e, name)\n"
+ + "values (56, 'Fred')");
+ fail("expected error, got " + s);
+ } catch (SQLException e) {
+ assertThat(e.getMessage(),
+ startsWith("Error while preparing statement"));
+ }
+
+ statement.close();
+ return null;
+ } catch (SQLException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+ });
+ } finally {
+ EmpDeptTableFactory.THREAD_COLLECTION.remove();
+ }
+ }
+
+ /** Tests a few cases where modifiable views are invalid. */
+ @Test public void testModelWithInvalidModifiableView() throws Exception {
+ final List<Employee> employees = new ArrayList<>();
+ employees.add(new Employee(135, 10, "Simon", 56.7f, null));
+ try {
+ EmpDeptTableFactory.THREAD_COLLECTION.set(employees);
+
+ Util.discard(RESOURCE.noValueSuppliedForViewColumn(null, null));
+ modelWithView("select \"name\", \"empid\" as e, \"salary\" "
+ + "from \"MUTABLE_EMPLOYEES\" where \"commission\" = 10",
+ true)
+ .query("select \"name\" from \"adhoc\".V order by \"name\"")
+ .throws_(
+ "View is not modifiable. No value is supplied for NOT NULL "
+ + "column 'deptno' of base table 'MUTABLE_EMPLOYEES'");
+
+ // no error if we do not claim that the view is modifiable
+ modelWithView(
+ "select \"name\", \"empid\" as e, \"salary\" "
+ + "from \"MUTABLE_EMPLOYEES\" where \"commission\" = 10", null)
+ .query("select \"name\" from \"adhoc\".V order by \"name\"")
+ .runs();
+
+ modelWithView("select \"name\", \"empid\" as e, \"salary\" "
+ + "from \"MUTABLE_EMPLOYEES\" where \"deptno\" IN (10, 20)",
+ true)
+ .query("select \"name\" from \"adhoc\".V order by \"name\"")
+ .throws_(
+ "View is not modifiable. No value is supplied for NOT NULL "
+ + "column 'deptno' of base table 'MUTABLE_EMPLOYEES'");
+
+ // Deduce "deptno = 10" from the constraint, and add a further
+ // condition "deptno < 20 OR commission > 1000".
+ modelWithView("select \"name\", \"empid\" as e, \"salary\" "
+ + "from \"MUTABLE_EMPLOYEES\"\n"
+ + "where \"deptno\" = 10 AND (\"deptno\" < 20 OR \"commission\" > 1000)",
+ true)
+ .query("insert into \"adhoc\".v values ('n',1,2)")
+ .explainContains(""
+ + "EnumerableTableModify(table=[[adhoc, MUTABLE_EMPLOYEES]], operation=[INSERT], updateColumnList=[[]], flattened=[false])\n"
+ + " EnumerableCalc(expr#0..2=[{inputs}], expr#3=[CAST($t1):JavaType(int) NOT NULL], expr#4=[10], expr#5=[CAST($t0):JavaType(class java.lang.String)], expr#6=[CAST($t2):JavaType(float) NOT NULL], expr#7=[null], expr#8=[20], expr#9=[<($t4, $t8)], expr#10=[1000], expr#11=[>($t7, $t10)], expr#12=[OR($t9, $t11)], empid=[$t3], deptno=[$t4], name=[$t5], salary=[$t6], commission=[$t7], $condition=[$t12])\n"
+ + " EnumerableValues(tuples=[[{ 'n', 1, 2 }]])");
+
+ modelWithView(
+ "select \"name\", \"empid\" as e, \"salary\" "
+ + "from \"MUTABLE_EMPLOYEES\"\n"
+ + "where \"commission\" = 100 AND \"deptno\" = 20",
+ true)
+ .query("select \"name\" from \"adhoc\".V order by \"name\"")
+ .runs();
+
+ modelWithView(
+ "select \"name\", \"empid\" as e, \"salary\", \"empid\" + 3 as e3, 1 as one\n"
+ + "from \"MUTABLE_EMPLOYEES\"\n"
+ + "where \"commission\" = 100 AND \"deptno\" = 20",
+ true)
+ .query("select \"name\" from \"adhoc\".V order by \"name\"")
+ .runs();
+
+ Util.discard(RESOURCE.moreThanOneMappedColumn(null, null));
+ modelWithView(
+ "select \"name\", \"empid\" as e, \"salary\", \"name\" as n2 "
+ + "from \"MUTABLE_EMPLOYEES\" where \"deptno\" IN (10, 20)",
+ true)
+ .query("select \"name\" from \"adhoc\".V order by \"name\"")
+ .throws_(
+ "View is not modifiable. More than one expression maps to "
+ + "column 'name' of base table 'MUTABLE_EMPLOYEES'");
+
+ // no error if we do not claim that the view is modifiable
+ modelWithView(
+ "select \"name\", \"empid\" as e, \"salary\", \"name\" as n2 "
+ + "from \"MUTABLE_EMPLOYEES\" where \"deptno\" IN (10, 20)",
+ null)
+ .query("select \"name\" from \"adhoc\".V order by \"name\"")
+ .runs();
+ } finally {
+ EmpDeptTableFactory.THREAD_COLLECTION.remove();
+ }
+ }
+
/**
* Tests a table function with literal arguments.
*/
@@ -3264,10 +3429,10 @@ public class JdbcTest {
+ " EnumerableCalc(expr#0..4=[{inputs}], expr#5=[+($t3, $t0)], proj#0..1=[{exprs}], salary=[$t3], $3=[$t5])\n"
+ " EnumerableTableScan(table=[[hr, emps]])\n")
.returnsUnordered(
- "deptno=10; empid=100; S=10100.0; FIVE=5; M=10000.0; C=1"
- , "deptno=10; empid=110; S=21710.0; FIVE=5; M=10000.0; C=2"
- , "deptno=10; empid=150; S=18760.0; FIVE=5; M=7000.0; C=2"
- , "deptno=20; empid=200; S=8200.0; FIVE=5; M=8000.0; C=1")
+ "deptno=10; empid=100; S=10100.0; FIVE=5; M=10000.0; C=1",
+ "deptno=10; empid=110; S=21710.0; FIVE=5; M=10000.0; C=2",
+ "deptno=10; empid=150; S=18760.0; FIVE=5; M=7000.0; C=2",
+ "deptno=20; empid=200; S=8200.0; FIVE=5; M=8000.0; C=1")
.planContains(CalcitePrepareImpl.DEBUG
? "_list.add(new Object[] {\n"
+ " row[0],\n" // box-unbox is optimized
@@ -4822,7 +4987,8 @@ public class JdbcTest {
"Cannot define view; parent schema 'adhoc' is not mutable");
}
- private CalciteAssert.AssertThat modelWithView(String view) {
+ private CalciteAssert.AssertThat modelWithView(String view,
+ Boolean modifiable) {
final Class<EmpDeptTableFactory> clazz = EmpDeptTableFactory.class;
return CalciteAssert.model("{\n"
+ " version: '1.0',\n"
@@ -4837,9 +5003,16 @@ public class JdbcTest {
+ " operand: {'foo': true, 'bar': 345}\n"
+ " },\n"
+ " {\n"
+ + " name: 'MUTABLE_EMPLOYEES',\n"
+ + " type: 'custom',\n"
+ + " factory: '" + clazz.getName() + "',\n"
+ + " operand: {'foo': false}\n"
+ + " },\n"
+ + " {\n"
+ " name: 'V',\n"
+ " type: 'view',\n"
- + " sql: '" + view + "'\n"
+ + (modifiable == null ? "" : " modifiable: " + modifiable + ",\n")
+ + " sql: " + new JsonBuilder().toJsonString(view) + "\n"
+ " }\n"
+ " ]\n"
+ " }\n"
@@ -4850,7 +5023,8 @@ public class JdbcTest {
/** Tests a JDBC connection that provides a model that contains a view. */
@Test public void testModelView() throws Exception {
final CalciteAssert.AssertThat with =
- modelWithView("select * from \"EMPLOYEES\" where \"deptno\" = 10");
+ modelWithView("select * from \"EMPLOYEES\" where \"deptno\" = 10",
+ null);
with.query("select * from \"adhoc\".V order by \"name\" desc")
.returns(""
@@ -4868,6 +5042,7 @@ public class JdbcTest {
// all table types
assertEquals(
"TABLE_CAT=null; TABLE_SCHEM=adhoc; TABLE_NAME=EMPLOYEES; TABLE_TYPE=TABLE; REMARKS=null; TYPE_CAT=null; TYPE_SCHEM=null; TYPE_NAME=null; SELF_REFERENCING_COL_NAME=null; REF_GENERATION=null\n"
+ + "TABLE_CAT=null; TABLE_SCHEM=adhoc; TABLE_NAME=MUTABLE_EMPLOYEES; TABLE_TYPE=TABLE; REMARKS=null; TYPE_CAT=null; TYPE_SCHEM=null; TYPE_NAME=null; SELF_REFERENCING_COL_NAME=null; REF_GENERATION=null\n"
+ "TABLE_CAT=null; TABLE_SCHEM=adhoc; TABLE_NAME=V; TABLE_TYPE=VIEW; REMARKS=null; TYPE_CAT=null; TYPE_SCHEM=null; TYPE_NAME=null; SELF_REFERENCING_COL_NAME=null; REF_GENERATION=null\n",
CalciteAssert.toString(
metaData.getTables(null, "adhoc", null, null)));
@@ -4931,7 +5106,7 @@ public class JdbcTest {
@Test public void testOrderByView() throws Exception {
final CalciteAssert.AssertThat with =
modelWithView("select * from \"EMPLOYEES\" where \"deptno\" = 10 "
- + "order by \"empid\" limit 2");
+ + "order by \"empid\" limit 2", null);
with
.query("select \"name\" from \"adhoc\".V order by \"name\"")
.returns("name=Bill\n"
@@ -6050,7 +6225,9 @@ public class JdbcTest {
assertThat(a2CalciteSchema.getTable("table1", false), notNullValue());
assertThat(a2CalciteSchema.getTable("taBle1", true), nullValue());
assertThat(a2CalciteSchema.getTable("taBle1", false), notNullValue());
- final TableMacro function = ViewTable.viewMacro(a2Schema, "values 1", null);
+ final TableMacro function =
+ ViewTable.viewMacro(a2Schema, "values 1", null, null);
+ Util.discard(function);
connection.close();
}
@@ -6186,7 +6363,7 @@ public class JdbcTest {
assertThat(rs.next(), is(true));
assertThat((Integer) rs.getObject("ID"), equalTo(2));
- assertThat((Double) rs.getObject("VALS"), nullValue());
+ assertThat(rs.getObject("VALS"), nullValue());
assertThat(rs.next(), is(true));
assertThat(rs.getObject("ID"), nullValue());
@@ -6420,8 +6597,19 @@ public class JdbcTest {
}
}
+ /** Abstract base class for implementations of {@link ModifiableView}. */
+ public abstract static class AbstractModifiableView
+ extends AbstractTable implements ModifiableView {
+ protected AbstractModifiableView() {
+ super();
+ }
+ }
+
/** Factory for EMP and DEPT tables. */
public static class EmpDeptTableFactory implements TableFactory<Table> {
+ public static final ThreadLocal<List<Employee>> THREAD_COLLECTION =
+ new ThreadLocal<>();
+
public Table create(
SchemaPlus schema,
String name,
@@ -6429,12 +6617,23 @@ public class JdbcTest {
RelDataType rowType) {
final Class clazz;
final Object[] array;
- if (name.equals("EMPLOYEES")) {
+ switch (name) {
+ case "EMPLOYEES":
clazz = Employee.class;
array = new HrSchema().emps;
- } else {
+ break;
+ case "MUTABLE_EMPLOYEES":
+ List<Employee> employees = THREAD_COLLECTION.get();
+ if (employees == null) {
+ employees = Collections.emptyList();
+ }
+ return JdbcFrontLinqBackTest.mutable(name, employees);
+ case "DEPARTMENTS":
clazz = Department.class;
array = new HrSchema().depts;
+ break;
+ default:
+ throw new AssertionError(name);
}
return new AbstractQueryableTable(clazz) {
public RelDataType getRowType(RelDataTypeFactory typeFactory) {