[CALCITE-1666] Support for modifiable views with extended columns (Kevin Liew)
[CALCITE-504] Support EXTEND clause (to declare extra columns) in CREATE VIEW Close apache/calcite#386 Project: http://git-wip-us.apache.org/repos/asf/calcite/repo Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/e0a1f7d3 Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/e0a1f7d3 Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/e0a1f7d3 Branch: refs/heads/master Commit: e0a1f7d3069f0eefeb8bdd411a250cd1fb0532f3 Parents: 598cf64 Author: kliewkliew <[email protected]> Authored: Wed Mar 1 23:43:18 2017 -0800 Committer: Julian Hyde <[email protected]> Committed: Tue Mar 14 21:42:13 2017 -0700 ---------------------------------------------------------------------- .../org/apache/calcite/jdbc/CalcitePrepare.java | 6 +- .../calcite/prepare/CalciteCatalogReader.java | 7 + .../calcite/prepare/CalcitePrepareImpl.java | 25 +- .../org/apache/calcite/prepare/Prepare.java | 26 ++ .../apache/calcite/prepare/RelOptTableImpl.java | 17 +- .../calcite/rel/type/RelDataTypeField.java | 23 ++ .../apache/calcite/runtime/CalciteResource.java | 6 + .../apache/calcite/schema/ExtensibleTable.java | 5 + .../java/org/apache/calcite/schema/Schemas.java | 5 +- .../schema/impl/ModifiableViewTable.java | 149 ++++++++++ .../apache/calcite/schema/impl/ViewTable.java | 95 ------- .../calcite/schema/impl/ViewTableMacro.java | 108 ++++++++ .../DelegatingSqlValidatorCatalogReader.java | 4 + .../apache/calcite/sql/validate/EmptyScope.java | 9 +- .../sql/validate/IdentifierNamespace.java | 19 +- .../sql/validate/SqlValidatorCatalogReader.java | 3 +- .../calcite/sql/validate/SqlValidatorImpl.java | 192 ++++++++++++- .../calcite/sql/validate/SqlValidatorUtil.java | 98 +++++++ .../calcite/sql/validate/TableNamespace.java | 14 + .../NullInitializerExpressionFactory.java | 2 +- .../calcite/sql2rel/SqlToRelConverter.java | 68 +++-- .../calcite/runtime/CalciteResource.properties | 2 + .../apache/calcite/sql/test/SqlAdvisorTest.java | 2 + .../java/org/apache/calcite/test/JdbcTest.java | 16 +- .../apache/calcite/test/MockCatalogReader.java | 272 +++++++++++++++++-- .../calcite/test/SqlToRelConverterTest.java | 69 +++++ .../apache/calcite/test/SqlValidatorTest.java | 92 +++++++ .../calcite/test/SqlToRelConverterTest.xml | 154 +++++++++++ 28 files changed, 1291 insertions(+), 197 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/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 31b6b70..6d2a8ac 100644 --- a/core/src/main/java/org/apache/calcite/jdbc/CalcitePrepare.java +++ b/core/src/main/java/org/apache/calcite/jdbc/CalcitePrepare.java @@ -46,6 +46,7 @@ import org.apache.calcite.util.ImmutableIntList; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import java.lang.reflect.InvocationTargetException; @@ -270,17 +271,20 @@ public interface CalcitePrepare { public final ImmutableList<String> tablePath; public final RexNode constraint; public final ImmutableIntList columnMapping; + public final boolean modifiable; public AnalyzeViewResult(CalcitePrepareImpl prepare, SqlValidator validator, String sql, SqlNode sqlNode, RelDataType rowType, RelRoot root, Table table, ImmutableList<String> tablePath, RexNode constraint, - ImmutableIntList columnMapping) { + ImmutableIntList columnMapping, boolean modifiable) { super(prepare, validator, sql, sqlNode, rowType, root); this.table = table; this.tablePath = tablePath; this.constraint = constraint; this.columnMapping = columnMapping; + this.modifiable = modifiable; + Preconditions.checkArgument(modifiable == (table != null)); } } http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java b/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java index b03f44b..ca68904 100644 --- a/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java +++ b/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java @@ -404,6 +404,13 @@ public class CalciteCatalogReader implements Prepare.CatalogReader { public SqlNameMatcher nameMatcher() { return nameMatcher; } + + @Override public <C> C unwrap(Class<C> aClass) { + if (aClass.isInstance(this)) { + return aClass.cast(this); + } + return null; + } } // End CalciteCatalogReader.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/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 7f43801..9858b48 100644 --- a/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java +++ b/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java @@ -358,7 +358,7 @@ public class CalcitePrepareImpl implements CalcitePrepare { } return new AnalyzeViewResult(this, validator, sql, sqlNode, validator.getValidatedNodeType(sqlNode), root, null, null, null, - null); + null, false); } final RelOptTable targetRelTable = scan.getTable(); final RelDataType targetRowType = targetRelTable.getRowType(); @@ -384,7 +384,7 @@ public class CalcitePrepareImpl implements CalcitePrepare { } return new AnalyzeViewResult(this, validator, sql, sqlNode, validator.getValidatedNodeType(sqlNode), root, null, null, null, - null); + null, false); } projectMap.put(index, rexBuilder.makeInputRef(viewRel, node.i)); columnMapping.add(index); @@ -400,7 +400,20 @@ public class CalcitePrepareImpl implements CalcitePrepare { constraint = rexBuilder.makeLiteral(true); } final List<RexNode> filters = new ArrayList<>(); + // If we put a constraint in projectMap above, then filters will not be empty despite + // being a modifiable view. + final List<RexNode> filters2 = new ArrayList<>(); + boolean retry = false; RelOptUtil.inferViewPredicates(projectMap, filters, constraint); + if (fail && !filters.isEmpty()) { + final Map<Integer, RexNode> projectMap2 = new HashMap<>(); + RelOptUtil.inferViewPredicates(projectMap2, filters2, constraint); + if (!filters2.isEmpty()) { + throw validator.newValidationError(sqlNode, + RESOURCE.modifiableViewMustHaveOnlyEqualityPredicates()); + } + retry = true; + } // Check that all columns that are not projected have a constant value for (RelDataTypeField field : targetRowType.getFieldList()) { @@ -423,13 +436,15 @@ public class CalcitePrepareImpl implements CalcitePrepare { } return new AnalyzeViewResult(this, validator, sql, sqlNode, validator.getValidatedNodeType(sqlNode), root, null, null, null, - null); + null, false); } + final boolean modifiable = filters.isEmpty() || retry && filters2.isEmpty(); return new AnalyzeViewResult(this, validator, sql, sqlNode, - validator.getValidatedNodeType(sqlNode), root, table, + validator.getValidatedNodeType(sqlNode), root, modifiable ? table : null, ImmutableList.copyOf(tablePath), - constraint, ImmutableIntList.copyOf(columnMapping)); + constraint, ImmutableIntList.copyOf(columnMapping), + modifiable); } @Override public void executeDdl(Context context, SqlNode node) { http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/prepare/Prepare.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/prepare/Prepare.java b/core/src/main/java/org/apache/calcite/prepare/Prepare.java index 26a6bff..81102b3 100644 --- a/core/src/main/java/org/apache/calcite/prepare/Prepare.java +++ b/core/src/main/java/org/apache/calcite/prepare/Prepare.java @@ -35,12 +35,15 @@ import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.RelRoot; import org.apache.calcite.rel.logical.LogicalTableModify; import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeField; import org.apache.calcite.rex.RexExecutorImpl; import org.apache.calcite.runtime.Bindable; import org.apache.calcite.runtime.Hook; import org.apache.calcite.runtime.Typed; +import org.apache.calcite.schema.ExtensibleTable; import org.apache.calcite.schema.Table; import org.apache.calcite.schema.Wrapper; +import org.apache.calcite.schema.impl.ModifiableViewTable; import org.apache.calcite.schema.impl.StarTable; import org.apache.calcite.sql.SqlExplain; import org.apache.calcite.sql.SqlExplainFormat; @@ -418,6 +421,29 @@ public abstract class Prepare { } return !rowType.getFieldList().get(ordinal).getType().isNullable(); } + + public RelOptTable extend(List<RelDataTypeField> extendedFields) { + final Table table = unwrap(Table.class); + if (table instanceof ExtensibleTable) { + return extend((ExtensibleTable) table, extendedFields); + } else if (table instanceof ModifiableViewTable) { + final Table underlying = ((Wrapper) table).unwrap(Table.class); + if (underlying instanceof ExtensibleTable) { + return extend((ExtensibleTable) underlying, extendedFields); + } + } + throw new RuntimeException("Cannot extend " + table); + } + + private RelOptTable extend(ExtensibleTable table, + List<RelDataTypeField> extendedFields) { + final Table extendedTable = table.extend(extendedFields); + return extend(extendedTable); + } + + /** Implementation-specific code to instantiate a new {@link RelOptTable} + * based on a {@link Table} that has been extended. */ + protected abstract RelOptTable extend(Table extendedTable); } /** http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/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 cddee2b..ccc25d8 100644 --- a/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java +++ b/core/src/main/java/org/apache/calcite/prepare/RelOptTableImpl.java @@ -30,10 +30,8 @@ import org.apache.calcite.rel.RelFieldCollation; import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.logical.LogicalTableScan; import org.apache.calcite.rel.type.RelDataType; -import org.apache.calcite.rel.type.RelDataTypeField; import org.apache.calcite.rel.type.RelRecordType; import org.apache.calcite.runtime.Hook; -import org.apache.calcite.schema.ExtensibleTable; import org.apache.calcite.schema.FilterableTable; import org.apache.calcite.schema.ModifiableTable; import org.apache.calcite.schema.Path; @@ -200,16 +198,11 @@ public class RelOptTableImpl extends Prepare.AbstractPreparingTable { return expressionFunction.apply(clazz); } - public RelOptTable extend(List<RelDataTypeField> extendedFields) { - if (table instanceof ExtensibleTable) { - final Table extendedTable = - ((ExtensibleTable) table).extend(extendedFields); - final RelDataType extendedRowType = - extendedTable.getRowType(schema.getTypeFactory()); - return new RelOptTableImpl(schema, extendedRowType, names, extendedTable, - expressionFunction, rowCount); - } - throw new RuntimeException("Cannot extend " + table); // TODO: user error + @Override protected RelOptTable extend(Table extendedTable) { + final RelDataType extendedRowType = + extendedTable.getRowType(getRelOptSchema().getTypeFactory()); + return new RelOptTableImpl(getRelOptSchema(), extendedRowType, getQualifiedName(), + extendedTable, expressionFunction, getRowCount()); } @Override public boolean equals(Object obj) { http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeField.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeField.java b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeField.java index 293de27..36acc1b 100644 --- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeField.java +++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeField.java @@ -16,6 +16,8 @@ */ package org.apache.calcite.rel.type; +import com.google.common.base.Function; + import java.util.Map; /** @@ -28,6 +30,27 @@ import java.util.Map; * and {@link #getValue()} must be equivalent to {@link #getType()}. */ public interface RelDataTypeField extends Map.Entry<String, RelDataType> { + + /** + * Function to transform a set of {@link RelDataTypeField} to + * a set of {@link Integer} of the field keys. + */ + class ToFieldIndex implements Function<RelDataTypeField, Integer> { + @Override public Integer apply(RelDataTypeField o) { + return o.getIndex(); + } + } + + /** + * Function to transform a set of {@link RelDataTypeField} to + * a set of {@link String} of the field names. + */ + class ToFieldName implements Function<RelDataTypeField, String> { + @Override public String apply(RelDataTypeField o) { + return o.getName(); + } + } + //~ Methods ---------------------------------------------------------------- /** http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/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 7bc99eb..40774d0 100644 --- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java +++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java @@ -636,12 +636,18 @@ public interface CalciteResource { @BaseMessage("Modifiable view must be based on a single table") ExInst<SqlValidatorException> modifiableViewMustBeBasedOnSingleTable(); + @BaseMessage("Modifiable view must be predicated only on equality expressions") + ExInst<SqlValidatorException> modifiableViewMustHaveOnlyEqualityPredicates(); + @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); + @BaseMessage("Modifiable view constraint is not satisfied for column ''{0}'' of base table ''{1}''") + ExInst<SqlValidatorException> viewConstraintNotSatisfied(String columnName, String tableName); + @BaseMessage("Not a record type. The ''*'' operator requires a record") ExInst<SqlValidatorException> starRequiresRecordType(); http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/schema/ExtensibleTable.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/schema/ExtensibleTable.java b/core/src/main/java/org/apache/calcite/schema/ExtensibleTable.java index 7469cd7..1127adb 100644 --- a/core/src/main/java/org/apache/calcite/schema/ExtensibleTable.java +++ b/core/src/main/java/org/apache/calcite/schema/ExtensibleTable.java @@ -40,6 +40,11 @@ public interface ExtensibleTable extends Table { /** Returns a table that has the row type of this table plus the given * fields. */ Table extend(List<RelDataTypeField> fields); + + /** Returns the starting offset of the first extended column, which may differ + * from the field count when the table stores metadata columns that are not + * counted in the row-type field count. */ + int getExtendedColumnOffset(); } // End ExtensibleTable.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/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 6c8ab1d..86275c4 100644 --- a/core/src/main/java/org/apache/calcite/schema/Schemas.java +++ b/core/src/main/java/org/apache/calcite/schema/Schemas.java @@ -504,11 +504,14 @@ public final class Schemas { public static Path path(CalciteSchema rootSchema, Iterable<String> names) { final ImmutableList.Builder<Pair<String, Schema>> builder = ImmutableList.builder(); - Schema schema = rootSchema.schema; + Schema schema = rootSchema.plus(); final Iterator<String> iterator = names.iterator(); if (!iterator.hasNext()) { return PathImpl.EMPTY; } + if (!rootSchema.name.isEmpty()) { + assert rootSchema.name.equals(iterator.next()); + } for (;;) { final String name = iterator.next(); builder.add(Pair.of(name, schema)); http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/schema/impl/ModifiableViewTable.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/schema/impl/ModifiableViewTable.java b/core/src/main/java/org/apache/calcite/schema/impl/ModifiableViewTable.java new file mode 100644 index 0000000..c006e68 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/schema/impl/ModifiableViewTable.java @@ -0,0 +1,149 @@ +/* + * 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.impl; + +import org.apache.calcite.plan.RelOptTable; +import org.apache.calcite.plan.RelOptUtil; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeFactory; +import org.apache.calcite.rel.type.RelProtoDataType; +import org.apache.calcite.rex.RexBuilder; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.schema.ModifiableView; +import org.apache.calcite.schema.Path; +import org.apache.calcite.schema.Table; +import org.apache.calcite.schema.Wrapper; +import org.apache.calcite.sql.SqlFunction; +import org.apache.calcite.sql2rel.InitializerExpressionFactory; +import org.apache.calcite.sql2rel.NullInitializerExpressionFactory; +import org.apache.calcite.util.ImmutableIntList; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** Extension to {@link ViewTable} that is modifiable. */ +public class ModifiableViewTable extends ViewTable + implements ModifiableView, Wrapper { + private final Table table; + private final Path tablePath; + private final RexNode constraint; + private final ImmutableIntList columnMapping; + private final InitializerExpressionFactory initializerExpressionFactory; + + /** Creates a ModifiableViewTable. */ + public ModifiableViewTable(Type elementType, RelProtoDataType rowType, + String viewSql, List<String> schemaPath, List<String> viewPath, + Table table, Path tablePath, RexNode constraint, + ImmutableIntList columnMapping, RelDataTypeFactory typeFactory) { + super(elementType, rowType, viewSql, schemaPath, viewPath); + this.table = table; + this.tablePath = tablePath; + this.constraint = constraint; + this.columnMapping = columnMapping; + this.initializerExpressionFactory = + new ModifiableViewTableInitializerExpressionFactory(typeFactory); + } + + 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; + } + + @Override public <C> C unwrap(Class<C> aClass) { + if (aClass.isInstance(initializerExpressionFactory)) { + return aClass.cast(initializerExpressionFactory); + } else if (aClass.isInstance(table)) { + return aClass.cast(table); + } + return null; + } + + /** + * Initializes columns based on the view constraint. + */ + private class ModifiableViewTableInitializerExpressionFactory + extends NullInitializerExpressionFactory { + private final ImmutableMap<Integer, RexNode> projectMap; + + private ModifiableViewTableInitializerExpressionFactory(RelDataTypeFactory typeFactory) { + super(typeFactory); + final Map<Integer, RexNode> projectMap = Maps.newHashMap(); + final List<RexNode> filters = new ArrayList<>(); + RelOptUtil.inferViewPredicates(projectMap, filters, constraint); + assert filters.isEmpty(); + this.projectMap = ImmutableMap.copyOf(projectMap); + } + + @Override public boolean isGeneratedAlways(RelOptTable table, int iColumn) { + assert table.unwrap(ModifiableViewTable.class) != null; + return false; + } + + @Override public RexNode newColumnDefaultValue(RelOptTable table, int iColumn) { + final ModifiableViewTable viewTable = table.unwrap(ModifiableViewTable.class); + final RelDataType viewType = + viewTable.getRowType(rexBuilder.getTypeFactory()); + final RelDataType iType = viewType.getFieldList().get(iColumn).getType(); + + // Use the view constraint to generate the default value if the column is constrained. + final int mappedOrdinal = viewTable.columnMapping.get(iColumn); + final RexNode viewConstraint = projectMap.get(mappedOrdinal); + if (viewConstraint != null) { + return rexBuilder.ensureType(iType, viewConstraint, true); + } + + // Otherwise use the default value of the underlying table. + final Table schemaTable = viewTable.unwrap(Table.class); + if (schemaTable instanceof Wrapper) { + final InitializerExpressionFactory initializerExpressionFactory = + ((Wrapper) schemaTable).unwrap(InitializerExpressionFactory.class); + if (initializerExpressionFactory != null) { + final RexNode tableConstraint = + initializerExpressionFactory.newColumnDefaultValue(table, iColumn); + return rexBuilder.ensureType(iType, tableConstraint, true); + } + } + + // Otherwise Sql type of NULL. + return super.newColumnDefaultValue(table, iColumn); + } + + @Override public RexNode newAttributeInitializer(RelDataType type, + SqlFunction constructor, int iAttribute, List<RexNode> constructorArgs) { + throw new UnsupportedOperationException("Not implemented - unknown requirements"); + } + } +} + +// End ModifiableViewTable.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/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 507db2f..65a5b9f 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 @@ -17,8 +17,6 @@ package org.apache.calcite.schema.impl; import org.apache.calcite.adapter.java.AbstractQueryableTable; -import org.apache.calcite.adapter.java.JavaTypeFactory; -import org.apache.calcite.jdbc.CalcitePrepare; import org.apache.calcite.jdbc.CalciteSchema; import org.apache.calcite.linq4j.QueryProvider; import org.apache.calcite.linq4j.Queryable; @@ -28,25 +26,14 @@ import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.RelRoot; 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 com.google.common.collect.ImmutableList; import java.lang.reflect.Type; -import java.util.Collections; import java.util.List; /** @@ -138,88 +125,6 @@ public class ViewTable + queryString, e); } } - - /** Table function that implements a view. It returns the operator - * tree of the view's SQL query. */ - 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; - protected final List<String> viewPath; - - ViewTableMacro(CalciteSchema schema, String viewSql, List<String> schemaPath, - List<String> viewPath, Boolean modifiable) { - this.viewSql = viewSql; - this.schema = schema; - this.viewPath = viewPath == null ? null : ImmutableList.copyOf(viewPath); - this.modifiable = modifiable; - this.schemaPath = - schemaPath == null ? null : ImmutableList.copyOf(schemaPath); - } - - public List<FunctionParameter> getParameters() { - return Collections.emptyList(); - } - - public TranslatableTable apply(List<Object> arguments) { - 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; - final Type elementType = typeFactory.getJavaClass(parsed.rowType); - if ((modifiable == null || modifiable) && parsed.table != null) { - return new ModifiableViewTable(elementType, - RelDataTypeImpl.proto(parsed.rowType), viewSql, schemaPath1, viewPath, - parsed.table, Schemas.path(schema.root(), parsed.tablePath), - parsed.constraint, parsed.columnMapping); - } else { - return new ViewTable(elementType, - RelDataTypeImpl.proto(parsed.rowType), viewSql, schemaPath1, viewPath); - } - } - } - - /** 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, List<String> viewPath, - Table table, Path tablePath, RexNode constraint, - ImmutableIntList columnMapping) { - super(elementType, rowType, viewSql, schemaPath, viewPath); - this.table = table; - this.tablePath = tablePath; - this.constraint = constraint; - this.columnMapping = columnMapping; - } - - 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; - } - } } // End ViewTable.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/schema/impl/ViewTableMacro.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/schema/impl/ViewTableMacro.java b/core/src/main/java/org/apache/calcite/schema/impl/ViewTableMacro.java new file mode 100644 index 0000000..e7e044a --- /dev/null +++ b/core/src/main/java/org/apache/calcite/schema/impl/ViewTableMacro.java @@ -0,0 +1,108 @@ +/* + * 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.impl; + +import org.apache.calcite.adapter.java.JavaTypeFactory; +import org.apache.calcite.jdbc.CalcitePrepare; +import org.apache.calcite.jdbc.CalciteSchema; +import org.apache.calcite.rel.type.RelDataTypeImpl; +import org.apache.calcite.schema.FunctionParameter; +import org.apache.calcite.schema.Schemas; +import org.apache.calcite.schema.TableMacro; +import org.apache.calcite.schema.TranslatableTable; + +import com.google.common.collect.ImmutableList; + +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.List; + +/** Table function that implements a view. It returns the operator + * tree of the view's SQL query. */ +public 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; + protected final List<String> viewPath; + + /** + * Creates a ViewTableMacro. + * + * @param schema Root schema + * @param viewSql SQL defining the view + * @param schemaPath Schema path relative to the root schema + * @param viewPath View path relative to the schema path + * @param modifiable Request that a view is modifiable (dependent on analysis + * of {@code viewSql}) + */ + public ViewTableMacro(CalciteSchema schema, String viewSql, + List<String> schemaPath, List<String> viewPath, Boolean modifiable) { + this.viewSql = viewSql; + this.schema = schema; + this.viewPath = viewPath == null ? null : ImmutableList.copyOf(viewPath); + this.modifiable = modifiable; + this.schemaPath = + schemaPath == null ? null : ImmutableList.copyOf(schemaPath); + } + + public List<FunctionParameter> getParameters() { + return Collections.emptyList(); + } + + public TranslatableTable apply(List<Object> arguments) { + CalcitePrepare.AnalyzeViewResult parsed = + Schemas.analyzeView(MaterializedViewTable.MATERIALIZATION_CONNECTION, + schema, schemaPath, viewSql, modifiable != null && modifiable); + final List<String> schemaPath1 = + schemaPath != null ? schemaPath : schema.path(null); + if ((modifiable == null || modifiable) + && parsed.modifiable + && parsed.table != null) { + return modifiableViewTable(parsed, viewSql, schemaPath1, viewPath, schema); + } else { + return viewTable(parsed, viewSql, schemaPath1, viewPath); + } + } + + /** Allows a sub-class to return an extension of {@link ModifiableViewTable} + * by overriding this method. */ + protected ModifiableViewTable modifiableViewTable(CalcitePrepare.AnalyzeViewResult parsed, + String viewSql, List<String> schemaPath, List<String> viewPath, + CalciteSchema schema) { + final JavaTypeFactory typeFactory = (JavaTypeFactory) parsed.typeFactory; + final Type elementType = typeFactory.getJavaClass(parsed.rowType); + return new ModifiableViewTable(elementType, + RelDataTypeImpl.proto(parsed.rowType), viewSql, schemaPath, viewPath, + parsed.table, Schemas.path(schema.root(), parsed.tablePath), + parsed.constraint, parsed.columnMapping, parsed.typeFactory); + } + + /** Allows a sub-class to return an extension of {@link ViewTable} by + * overriding this method. */ + protected ViewTable viewTable(CalcitePrepare.AnalyzeViewResult parsed, + String viewSql, List<String> schemaPath, List<String> viewPath) { + final JavaTypeFactory typeFactory = (JavaTypeFactory) parsed.typeFactory; + final Type elementType = typeFactory.getJavaClass(parsed.rowType); + return new ViewTable(elementType, + RelDataTypeImpl.proto(parsed.rowType), viewSql, schemaPath, viewPath); + } +} + +// End ViewTableMacro.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/sql/validate/DelegatingSqlValidatorCatalogReader.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/validate/DelegatingSqlValidatorCatalogReader.java b/core/src/main/java/org/apache/calcite/sql/validate/DelegatingSqlValidatorCatalogReader.java index 244afb5..6a1ad8a 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/DelegatingSqlValidatorCatalogReader.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/DelegatingSqlValidatorCatalogReader.java @@ -55,6 +55,10 @@ public abstract class DelegatingSqlValidatorCatalogReader public List<List<String>> getSchemaPaths() { return catalogReader.getSchemaPaths(); } + + @Override public <C> C unwrap(Class<C> aClass) { + return catalogReader.unwrap(aClass); + } } // End DelegatingSqlValidatorCatalogReader.java http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/sql/validate/EmptyScope.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/validate/EmptyScope.java b/core/src/main/java/org/apache/calcite/sql/validate/EmptyScope.java index 0dc9131..bafa309 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/EmptyScope.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/EmptyScope.java @@ -17,6 +17,7 @@ package org.apache.calcite.sql.validate; import org.apache.calcite.jdbc.CalciteSchema; +import org.apache.calcite.plan.RelOptSchema; import org.apache.calcite.prepare.Prepare; import org.apache.calcite.prepare.RelOptTableImpl; import org.apache.calcite.rel.type.RelDataType; @@ -154,9 +155,11 @@ class EmptyScope implements SqlValidatorScope { table2 = ((Wrapper) table).unwrap(Prepare.PreparingTable.class); } if (table2 == null) { - table2 = RelOptTableImpl.create(null, - table.getRowType(validator.typeFactory), schema.add(name2, table), - null); + final RelOptSchema relOptSchema = + validator.catalogReader.unwrap(RelOptSchema.class); + final RelDataType rowType = table.getRowType(validator.typeFactory); + table2 = RelOptTableImpl.create(relOptSchema, rowType, + schema.add(name2, table), null); } namespace = new TableNamespace(validator, table2); resolved.found(namespace, false, this, path, remainingNames); http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/sql/validate/IdentifierNamespace.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/validate/IdentifierNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/IdentifierNamespace.java index f5ffde3..63247e7 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/IdentifierNamespace.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/IdentifierNamespace.java @@ -18,9 +18,7 @@ package org.apache.calcite.sql.validate; import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeField; -import org.apache.calcite.rel.type.RelDataTypeFieldImpl; import org.apache.calcite.sql.SqlCall; -import org.apache.calcite.sql.SqlDataTypeSpec; import org.apache.calcite.sql.SqlIdentifier; import org.apache.calcite.sql.SqlNode; import org.apache.calcite.sql.SqlNodeList; @@ -29,11 +27,9 @@ import org.apache.calcite.util.Pair; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; import java.util.ArrayList; import java.util.Collections; -import java.util.Iterator; import java.util.List; import javax.annotation.Nullable; @@ -199,20 +195,13 @@ public class IdentifierNamespace extends AbstractNamespace { RelDataType rowType = resolvedNamespace.getRowType(); if (extendList != null) { - final List<RelDataTypeField> fields = Lists.newArrayList(); - final Iterator<SqlNode> extendIterator = extendList.iterator(); - while (extendIterator.hasNext()) { - SqlIdentifier id = (SqlIdentifier) extendIterator.next(); - SqlDataTypeSpec type = (SqlDataTypeSpec) extendIterator.next(); - fields.add( - new RelDataTypeFieldImpl(id.getSimple(), fields.size(), - type.deriveType(validator))); - } - if (!(resolvedNamespace instanceof TableNamespace)) { throw new RuntimeException("cannot convert"); } - resolvedNamespace = ((TableNamespace) resolvedNamespace).extend(fields); + final List<RelDataTypeField> extendedFields = + SqlValidatorUtil.getExtendedColumns(validator, getTable(), extendList); + resolvedNamespace = + ((TableNamespace) resolvedNamespace).extend(extendedFields); rowType = resolvedNamespace.getRowType(); } http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorCatalogReader.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorCatalogReader.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorCatalogReader.java index a71f8f4..be204e2 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorCatalogReader.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorCatalogReader.java @@ -19,6 +19,7 @@ package org.apache.calcite.sql.validate; import org.apache.calcite.jdbc.CalciteSchema; import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeField; +import org.apache.calcite.schema.Wrapper; import org.apache.calcite.sql.SqlIdentifier; import java.util.List; @@ -32,7 +33,7 @@ import java.util.List; * implement the repository. It is also possible to construct mock * implementations of this interface for testing purposes. */ -public interface SqlValidatorCatalogReader { +public interface SqlValidatorCatalogReader extends Wrapper { //~ Methods ---------------------------------------------------------------- /** http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java index f9045a2..ad2d4c1 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java @@ -19,17 +19,25 @@ package org.apache.calcite.sql.validate; import org.apache.calcite.config.NullCollation; import org.apache.calcite.linq4j.Ord; import org.apache.calcite.plan.RelOptTable; +import org.apache.calcite.plan.RelOptUtil; import org.apache.calcite.rel.type.DynamicRecordType; import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeFactory; import org.apache.calcite.rel.type.RelDataTypeField; import org.apache.calcite.rel.type.RelDataTypeSystem; import org.apache.calcite.rel.type.RelRecordType; +import org.apache.calcite.rex.RexBuilder; +import org.apache.calcite.rex.RexLiteral; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.rex.RexSqlStandardConvertletTable; +import org.apache.calcite.rex.RexToSqlNodeConverter; +import org.apache.calcite.rex.RexToSqlNodeConverterImpl; import org.apache.calcite.runtime.CalciteContextException; import org.apache.calcite.runtime.CalciteException; import org.apache.calcite.runtime.Feature; import org.apache.calcite.runtime.Resources; import org.apache.calcite.schema.Table; +import org.apache.calcite.schema.impl.ModifiableViewTable; import org.apache.calcite.sql.JoinConditionType; import org.apache.calcite.sql.JoinType; import org.apache.calcite.sql.SqlAccessEnum; @@ -80,6 +88,7 @@ import org.apache.calcite.sql.util.SqlShuttle; import org.apache.calcite.sql.util.SqlVisitor; import org.apache.calcite.util.BitString; import org.apache.calcite.util.Bug; +import org.apache.calcite.util.ImmutableBitSet; import org.apache.calcite.util.ImmutableNullableList; import org.apache.calcite.util.Litmus; import org.apache.calcite.util.Pair; @@ -94,6 +103,7 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.slf4j.Logger; @@ -983,21 +993,40 @@ public class SqlValidatorImpl implements SqlValidatorWithHints { SqlValidatorScope scope) { if (node instanceof SqlIdentifier && scope instanceof DelegatingScope) { final SqlIdentifier id = (SqlIdentifier) node; - final SqlValidatorScope parentScope = - ((DelegatingScope) scope).getParent(); - if (id.isSimple()) { - final SqlNameMatcher nameMatcher = catalogReader.nameMatcher(); - final SqlValidatorScope.ResolvedImpl resolved = - new SqlValidatorScope.ResolvedImpl(); - parentScope.resolve(id.names, nameMatcher, false, resolved); - if (resolved.count() == 1) { - return resolved.only().namespace; + final DelegatingScope idScope = (DelegatingScope) ((DelegatingScope) scope).getParent(); + return getNamespace(id, idScope); + } else if (node instanceof SqlCall) { + // Handle extended identifiers. + final SqlCall sqlCall = (SqlCall) node; + final SqlKind sqlKind = sqlCall.getOperator().getKind(); + if (sqlKind.equals(SqlKind.EXTEND)) { + final SqlIdentifier id = (SqlIdentifier) sqlCall.getOperandList().get(0); + final DelegatingScope idScope = (DelegatingScope) scope; + return getNamespace(id, idScope); + } else { + final SqlNode nested = sqlCall.getOperandList().get(0); + if (sqlKind.equals(SqlKind.AS) + && nested.getKind().equals(SqlKind.EXTEND)) { + return getNamespace(nested, scope); } } } return getNamespace(node); } + private SqlValidatorNamespace getNamespace(SqlIdentifier id, DelegatingScope scope) { + if (id.isSimple()) { + final SqlNameMatcher nameMatcher = catalogReader.nameMatcher(); + final SqlValidatorScope.ResolvedImpl resolved = + new SqlValidatorScope.ResolvedImpl(); + scope.resolve(id.names, nameMatcher, false, resolved); + if (resolved.count() == 1) { + return resolved.only().namespace; + } + } + return getNamespace(id); + } + public SqlValidatorNamespace getNamespace(SqlNode node) { switch (node.getKind()) { case AS: @@ -2053,6 +2082,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints { tableScope = new TableScope(parentScope, node); } tableScope.addChild(newNs, alias, forceNullable); + if (extendList != null && extendList.size() != 0) { + return enclosingNode; + } return newNode; case LATERAL: @@ -3832,9 +3864,149 @@ public class SqlValidatorImpl implements SqlValidatorWithHints { checkTypeAssignment(logicalSourceRowType, logicalTargetRowType, insert); + checkConstraint(table, source, logicalTargetRowType); + validateAccess(insert.getTargetTable(), table, SqlAccessEnum.INSERT); } + /** + * Validates insert values against the constraint of a modifiable view. + * + * @param validatorTable Table that may wrap a ModifiableViewTable + * @param source The values being inserted + * @param targetRowType The target type for the view + */ + private void checkConstraint( + SqlValidatorTable validatorTable, + SqlNode source, + RelDataType targetRowType) { + final ModifiableViewTable modifiableViewTable = + validatorTable.unwrap(ModifiableViewTable.class); + if (modifiableViewTable != null && source instanceof SqlCall) { + final Table table = modifiableViewTable.unwrap(Table.class); + final RelDataType tableRowType = table.getRowType(typeFactory); + final List<RelDataTypeField> tableFields = tableRowType.getFieldList(); + + // Get the mapping from column indexes of the underlying table + // to the target columns and view constraints. + final Map<Integer, RelDataTypeField> tableIndexToTargetField = + SqlValidatorUtil.getIndexToFieldMap(tableFields, targetRowType); + final Map<Integer, RexNode> projectMap = + getConstraintForModifiableView(modifiableViewTable, targetRowType); + + // Determine columns (indexed to the underlying table) that need + // to be validated against the view constraint. + final ImmutableBitSet targetColumns = + ImmutableBitSet.of(tableIndexToTargetField.keySet()); + final ImmutableBitSet constrainedColumns = + ImmutableBitSet.of(projectMap.keySet()); + final ImmutableBitSet constrainedTargetColumns = + targetColumns.intersect(constrainedColumns); + + // Validate insert values against the view constraint. + final List<SqlNode> values = ((SqlCall) source).getOperandList(); + for (final int colIndex : constrainedTargetColumns.asList()) { + final String colName = tableFields.get(colIndex).getName(); + final RelDataTypeField targetField = tableIndexToTargetField.get(colIndex); + for (SqlNode row : values) { + final SqlCall call = (SqlCall) row; + final SqlNode sourceValue = call.operand(targetField.getIndex()); + checkConstraint(validatorTable, colName, sourceValue, projectMap.get(colIndex)); + } + } + } + } + + /** + * Returns a mapping of the column ordinal in the underlying table to a column + * constraint of the modifiable view. + * + * @param modifiableViewTable The modifiable view which has a constraint + * @param targetRowType The target type + */ + private Map<Integer, RexNode> getConstraintForModifiableView( + ModifiableViewTable modifiableViewTable, RelDataType targetRowType) { + final RexBuilder rexBuilder = new RexBuilder(typeFactory); + final RexNode constraint = + modifiableViewTable.getConstraint(rexBuilder, targetRowType); + final Map<Integer, RexNode> projectMap = Maps.newHashMap(); + final List<RexNode> filters = new ArrayList<>(); + RelOptUtil.inferViewPredicates(projectMap, filters, constraint); + assert filters.isEmpty(); + return projectMap; + } + + /** + * Validates updates against the constraint of a modifiable view. + * + * @param validatorTable A {@link SqlValidatorTable} that may wrap a + * ModifiableViewTable + * @param update The UPDATE parse tree node + * @param targetRowType The target type + */ + private void checkConstraint( + SqlValidatorTable validatorTable, + SqlUpdate update, + RelDataType targetRowType) { + final ModifiableViewTable modifiableViewTable = + validatorTable.unwrap(ModifiableViewTable.class); + if (modifiableViewTable != null) { + final Table table = modifiableViewTable.unwrap(Table.class); + final RelDataType tableRowType = table.getRowType(typeFactory); + + final Map<Integer, RexNode> projectMap = + getConstraintForModifiableView(modifiableViewTable, targetRowType); + final Map<String, Integer> nameToIndex = + SqlValidatorUtil.mapNameToIndex(tableRowType.getFieldList()); + + // Validate update values against the view constraint. + for (final Pair<SqlNode, SqlNode> column : Pair.zip(update.getTargetColumnList().getList(), + update.getSourceExpressionList().getList())) { + final String columnName = ((SqlIdentifier) column.left).getSimple(); + final Integer columnIndex = nameToIndex.get(columnName); + if (projectMap.containsKey(columnIndex)) { + final RexNode columnConstraint = projectMap.get(columnIndex); + checkConstraint(validatorTable, columnName, column.right, columnConstraint); + } + } + } + } + + /** + * Ensures that a source value does not violate the constraint of the target column. + * + * @param table The SqlValidatorTable which wraps a ModifiableViewTable. + * @param columnName The target column name. + * @param sourceValue The insert value being validated. + * @param targetConstraint The constraint applied to sourceValue for validation. + */ + private void checkConstraint( + SqlValidatorTable table, String columnName, + SqlNode sourceValue, RexNode targetConstraint) { + if (!(sourceValue instanceof SqlLiteral)) { + // We cannot guarantee that the value satisfies the constraint. + throw newValidationError(sourceValue, + RESOURCE.viewConstraintNotSatisfied( + columnName, Util.last(table.getQualifiedName()))); + } + final SqlLiteral insertValue = (SqlLiteral) sourceValue; + final RexLiteral columnConstraint = (RexLiteral) targetConstraint; + + final RexSqlStandardConvertletTable convertletTable = + new RexSqlStandardConvertletTable(); + final RexToSqlNodeConverter sqlNodeToRexConverter = + new RexToSqlNodeConverterImpl(convertletTable); + final SqlLiteral constraintValue = + (SqlLiteral) sqlNodeToRexConverter.convertLiteral(columnConstraint); + + if (!insertValue.equals(constraintValue)) { + // The value does not satisfy the constraint. + throw newValidationError(sourceValue, + RESOURCE.viewConstraintNotSatisfied( + columnName, Util.last(table.getQualifiedName()))); + } + } + private void checkFieldCount( SqlNode node, SqlValidatorTable table, @@ -4009,6 +4181,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints { RelDataType sourceRowType = getNamespace(select).getRowType(); checkTypeAssignment(sourceRowType, targetRowType, call); + checkConstraint(table, call, targetRowType); + validateAccess(call.getTargetTable(), table, SqlAccessEnum.UPDATE); } http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java index c643a83..a393d84 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java @@ -25,7 +25,9 @@ import org.apache.calcite.rel.core.JoinRelType; import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeFactory; import org.apache.calcite.rel.type.RelDataTypeField; +import org.apache.calcite.rel.type.RelDataTypeFieldImpl; import org.apache.calcite.schema.CustomColumnResolvingTable; +import org.apache.calcite.schema.ExtensibleTable; import org.apache.calcite.schema.Table; import org.apache.calcite.sql.SqlCall; import org.apache.calcite.sql.SqlDataTypeSpec; @@ -47,6 +49,7 @@ import org.apache.calcite.util.Util; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -109,6 +112,101 @@ public class SqlValidatorUtil { return table; } + /** + * Gets a list of extended columns with field indices to the underlying table. + */ + public static List<RelDataTypeField> getExtendedColumns( + SqlValidator validator, SqlValidatorTable table, SqlNodeList extendedColumns) { + final ImmutableList.Builder<RelDataTypeField> extendedFields = + ImmutableList.builder(); + final ExtensibleTable extTable = table.unwrap(ExtensibleTable.class); + int extendedFieldOffset = + extTable == null + ? table.getRowType().getFieldCount() + : extTable.getExtendedColumnOffset(); + for (final Pair<SqlIdentifier, SqlDataTypeSpec> pair : pairs(extendedColumns)) { + final SqlIdentifier identifier = pair.left; + final SqlDataTypeSpec type = pair.right; + extendedFields.add( + new RelDataTypeFieldImpl(identifier.getSimple(), + extendedFieldOffset++, + type.deriveType(validator))); + } + return extendedFields.build(); + } + + /** Converts a list of extended columns + * (of the form [name0, type0, name1, type1, ...]) + * into a list of (name, type) pairs. */ + private static List<Pair<SqlIdentifier, SqlDataTypeSpec>> pairs( + SqlNodeList extendedColumns) { + final List list = extendedColumns.getList(); + //noinspection unchecked + return Pair.zip(Util.quotientList(list, 2, 0), + Util.quotientList(list, 2, 1)); + } + + /** + * Gets a map of indexes from the source to fields in the target for the + * intersecting set of source and target fields. + * + * @param sourceFields The source of column names that determine indexes + * @param targetFields The target fields to be indexed + */ + public static ImmutableMap<Integer, RelDataTypeField> getIndexToFieldMap( + List<RelDataTypeField> sourceFields, + RelDataType targetFields) { + final ImmutableMap.Builder<Integer, RelDataTypeField> output = + ImmutableMap.builder(); + for (final RelDataTypeField source : sourceFields) { + final RelDataTypeField target = targetFields.getField(source.getName(), true, false); + if (target != null) { + output.put(source.getIndex(), target); + } + } + return output.build(); + } + + /** + * Gets the bit-set to the column ordinals in the source for columns that intersect in the target. + * @param sourceRowType The source upon which to ordinate the bit set. + * @param targetRowType The target to overlay on the source to create the bit set. + */ + public static ImmutableBitSet getOrdinalBitSet( + RelDataType sourceRowType, RelDataType targetRowType) { + Map<Integer, RelDataTypeField> indexToField = + getIndexToFieldMap(sourceRowType.getFieldList(), targetRowType); + return getOrdinalBitSet(sourceRowType, indexToField); + } + + /** + * Gets the bit-set to the column ordinals in the source for columns that + * intersect in the target. + * + * @param sourceRowType The source upon which to ordinate the bit set. + * @param indexToField The map of ordinals to target fields. + */ + public static ImmutableBitSet getOrdinalBitSet( + RelDataType sourceRowType, + Map<Integer, RelDataTypeField> indexToField) { + ImmutableBitSet source = ImmutableBitSet.of( + Lists.transform( + sourceRowType.getFieldList(), + new RelDataTypeField.ToFieldIndex())); + ImmutableBitSet target = + ImmutableBitSet.of(indexToField.keySet()); + return source.intersect(target); + } + + /** Returns a map from field names to indexes. */ + static Map<String, Integer> mapNameToIndex(List<RelDataTypeField> fields) { + ImmutableMap.Builder<String, Integer> output = ImmutableMap.builder(); + for (RelDataTypeField field : fields) { + output.put(field.getName(), field.getIndex()); + } + return output.build(); + } + @Deprecated // to be removed before 2.0 public static RelDataTypeField lookupField(boolean caseSensitive, final RelDataType rowType, String columnName) { http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/sql/validate/TableNamespace.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/validate/TableNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/TableNamespace.java index d354c5d..a8c6476 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/TableNamespace.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/TableNamespace.java @@ -16,9 +16,12 @@ */ package org.apache.calcite.sql.validate; +import org.apache.calcite.plan.RelOptTable; import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeFactory; import org.apache.calcite.rel.type.RelDataTypeField; +import org.apache.calcite.schema.ExtensibleTable; +import org.apache.calcite.schema.Table; import org.apache.calcite.sql.SqlNode; import com.google.common.base.Preconditions; @@ -76,6 +79,17 @@ class TableNamespace extends AbstractNamespace { * be present if you ask for them. Phoenix uses them, for instance, to access * rarely used fields in the underlying HBase table. */ public TableNamespace extend(List<RelDataTypeField> extendedFields) { + final Table schemaTable = table.unwrap(Table.class); + if (schemaTable != null + && table instanceof RelOptTable + && schemaTable instanceof ExtensibleTable) { + final SqlValidatorTable validatorTable = + ((RelOptTable) table).extend(ImmutableList.copyOf( + Iterables.concat(this.extendedFields, extendedFields))) + .unwrap(SqlValidatorTable.class); + return new TableNamespace( + validator, validatorTable, ImmutableList.<RelDataTypeField>of()); + } return new TableNamespace(validator, table, ImmutableList.copyOf( Iterables.concat(this.extendedFields, extendedFields))); http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/main/java/org/apache/calcite/sql2rel/NullInitializerExpressionFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql2rel/NullInitializerExpressionFactory.java b/core/src/main/java/org/apache/calcite/sql2rel/NullInitializerExpressionFactory.java index 83ecdf3..f0acd94 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/NullInitializerExpressionFactory.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/NullInitializerExpressionFactory.java @@ -29,7 +29,7 @@ import java.util.List; * An implementation of {@link InitializerExpressionFactory} that always supplies NULL. */ public class NullInitializerExpressionFactory implements InitializerExpressionFactory { - private final RexBuilder rexBuilder; + protected final RexBuilder rexBuilder; public NullInitializerExpressionFactory(RelDataTypeFactory typeFactory) { this.rexBuilder = new RexBuilder(typeFactory); http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/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 e832f19..06d593b 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java @@ -1948,31 +1948,14 @@ public class SqlToRelConverter { return; case IDENTIFIER: - final SqlValidatorNamespace fromNamespace = - validator.getNamespace(from).resolve(); - if (fromNamespace.getNode() != null) { - convertFrom(bb, fromNamespace.getNode()); - return; - } - final String datasetName = - datasetStack.isEmpty() ? null : datasetStack.peek(); - boolean[] usedDataset = {false}; - RelOptTable table = - SqlValidatorUtil.getRelOptTable( - fromNamespace, - catalogReader, - datasetName, - usedDataset); - final RelNode tableRel; - if (config.isConvertTableAccess()) { - tableRel = toRel(table); - } else { - tableRel = LogicalTableScan.create(cluster, table); - } - bb.setRoot(tableRel, true); - if (usedDataset[0]) { - bb.setDataset(datasetName); - } + convertIdentifier(bb, (SqlIdentifier) from, null); + return; + + case EXTEND: + call = (SqlCall) from; + SqlIdentifier id = (SqlIdentifier) call.getOperandList().get(0); + SqlNodeList extendedColumns = (SqlNodeList) call.getOperandList().get(1); + convertIdentifier(bb, id, extendedColumns); return; case JOIN: @@ -2149,6 +2132,41 @@ public class SqlToRelConverter { bb.setRoot(rel, false); } + private void convertIdentifier(Blackboard bb, SqlIdentifier id, + SqlNodeList extendedColumns) { + final SqlValidatorNamespace fromNamespace = + validator.getNamespace(id).resolve(); + if (fromNamespace.getNode() != null) { + convertFrom(bb, fromNamespace.getNode()); + return; + } + final String datasetName = + datasetStack.isEmpty() ? null : datasetStack.peek(); + final boolean[] usedDataset = {false}; + RelOptTable table = + SqlValidatorUtil.getRelOptTable(fromNamespace, catalogReader, + datasetName, usedDataset); + if (extendedColumns != null && extendedColumns.size() > 0) { + assert table != null; + final SqlValidatorTable validatorTable = + table.unwrap(SqlValidatorTable.class); + final List<RelDataTypeField> extendedFields = + SqlValidatorUtil.getExtendedColumns(validator, validatorTable, + extendedColumns); + table = table.extend(extendedFields); + } + final RelNode tableRel; + if (config.isConvertTableAccess()) { + tableRel = toRel(table); + } else { + tableRel = LogicalTableScan.create(cluster, table); + } + bb.setRoot(tableRel, true); + if (usedDataset[0]) { + bb.setDataset(datasetName); + } + } + protected void convertCollectionTable( Blackboard bb, SqlCall call) { http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/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 75cbe07..4fb7c9a 100644 --- a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties +++ b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties @@ -206,8 +206,10 @@ StreamMustOrderByMonotonic=Streaming ORDER BY must start with monotonic expressi StreamSetOpInconsistentInputs=Set operator cannot combine streaming and non-streaming inputs CannotStreamValues=Cannot stream VALUES ModifiableViewMustBeBasedOnSingleTable=Modifiable view must be based on a single table +ModifiableViewMustHaveOnlyEqualityPredicates=Modifiable view must be predicated only on equality expressions 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}'' +ViewConstraintNotSatisfied=Modifiable view constraint is not satisfied for column ''{0}'' of base table ''{1}'' StarRequiresRecordType=Not a record type. The ''*'' operator requires a record FilterMustBeBoolean=FILTER expression must be of type BOOLEAN CannotStreamResultsForNonStreamingInputs=Cannot stream results of a query with no streaming inputs: ''{0}''. At least one input should be convertible to a stream http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/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 24aae33..5f48a24 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 @@ -72,6 +72,8 @@ public class SqlAdvisorTest extends SqlValidatorTestCase { "TABLE(CATALOG.SALES.EMPDEFAULTS)", "TABLE(CATALOG.SALES.EMPNULLABLES)", "TABLE(CATALOG.SALES.EMP_B)", + "TABLE(CATALOG.SALES.EMP_MODIFIABLEVIEW)", + "TABLE(CATALOG.SALES.EMP_MODIFIABLEVIEW2)", "TABLE(CATALOG.SALES.EMP_20)", "TABLE(CATALOG.SALES.EMPNULLABLES_20)", "TABLE(CATALOG.SALES.EMP_ADDRESS)", http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/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 2fcbb0a..8138ebf 100644 --- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java +++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java @@ -348,8 +348,7 @@ public class JdbcTest { 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'"); + "Modifiable view must be predicated only on equality expressions"); // Deduce "deptno = 10" from the constraint, and add a further // condition "deptno < 20 OR commission > 1000". @@ -358,10 +357,15 @@ public class JdbcTest { + "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], 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 }]])"); + .throws_( + "Modifiable view must be predicated only on equality expressions"); + modelWithView("select \"name\", \"empid\" as e, \"salary\" " + + "from \"MUTABLE_EMPLOYEES\"\n" + + "where \"deptno\" = 10 AND (\"deptno\" > 20 AND \"commission\" > 1000)", + true) + .query("insert into \"adhoc\".v values ('n',1,2)") + .throws_( + "Modifiable view must be predicated only on equality expressions"); modelWithView( "select \"name\", \"empid\" as e, \"salary\" " http://git-wip-us.apache.org/repos/asf/calcite/blob/e0a1f7d3/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java b/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java index 420354d..f7802a7 100644 --- a/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java +++ b/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java @@ -16,6 +16,8 @@ */ package org.apache.calcite.test; +import org.apache.calcite.adapter.java.JavaTypeFactory; +import org.apache.calcite.jdbc.CalcitePrepare; import org.apache.calcite.jdbc.CalciteSchema; import org.apache.calcite.linq4j.Ord; import org.apache.calcite.linq4j.QueryProvider; @@ -51,6 +53,7 @@ import org.apache.calcite.rex.RexInputRef; import org.apache.calcite.rex.RexNode; import org.apache.calcite.rex.RexUtil; import org.apache.calcite.schema.CustomColumnResolvingTable; +import org.apache.calcite.schema.ExtensibleTable; import org.apache.calcite.schema.Path; import org.apache.calcite.schema.Schema; import org.apache.calcite.schema.SchemaPlus; @@ -58,8 +61,12 @@ import org.apache.calcite.schema.Schemas; import org.apache.calcite.schema.Statistic; import org.apache.calcite.schema.StreamableTable; import org.apache.calcite.schema.Table; +import org.apache.calcite.schema.TableMacro; +import org.apache.calcite.schema.TranslatableTable; import org.apache.calcite.schema.Wrapper; import org.apache.calcite.schema.impl.AbstractSchema; +import org.apache.calcite.schema.impl.ModifiableViewTable; +import org.apache.calcite.schema.impl.ViewTableMacro; import org.apache.calcite.sql.SqlAccessType; import org.apache.calcite.sql.SqlCollation; import org.apache.calcite.sql.SqlFunction; @@ -83,6 +90,7 @@ import org.apache.calcite.util.Pair; import org.apache.calcite.util.Util; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -192,9 +200,7 @@ public class MockCatalogReader extends CalciteCatalogReader { // Register "EMPDEFAULTS" table with default values for some columns. final InitializerExpressionFactory empInitializerExpressionFactory = new NullInitializerExpressionFactory(typeFactory) { - @Override public RexNode newColumnDefaultValue(RelOptTable table, - int iColumn) { - final RexBuilder rexBuilder = new RexBuilder(typeFactory); + @Override public RexNode newColumnDefaultValue(RelOptTable table, int iColumn) { switch (iColumn) { case 0: return rexBuilder.makeExactLiteral(new BigDecimal(123), @@ -483,6 +489,8 @@ public class MockCatalogReader extends CalciteCatalogReader { // SELECT * // FROM T // WHERE F0.C0 = 10 + // This table uses MockViewTable which does not populate the constrained columns with default + // values on INSERT. final ImmutableIntList m1 = ImmutableIntList.of(0, 1, 2, 3, 4, 5, 6, 7, 8); MockTable struct10View = new MockViewTable(this, structTypeSchema.getCatalogName(), @@ -504,6 +512,42 @@ public class MockCatalogReader extends CalciteCatalogReader { struct10View.addColumn(column.getName(), column.type); } registerTable(struct10View); + + return init2(salesSchema); + } + + private MockCatalogReader init2(MockSchema salesSchema) { + // Same as "EMP_20" except it uses ModifiableViewTable which populates + // constrained columns with default values on INSERT and has a single constraint on DEPTNO. + List<String> empModifiableViewNames = ImmutableList.of( + salesSchema.getCatalogName(), salesSchema.name, "EMP_MODIFIABLEVIEW"); + TableMacro empModifiableViewMacro = MockModifiableViewRelOptTable.viewMacro(rootSchema, + "select EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, SLACKER from EMPDEFAULTS" + + " where DEPTNO = 20", empModifiableViewNames.subList(0, 2), + ImmutableList.of(empModifiableViewNames.get(2)), true); + TranslatableTable empModifiableView = empModifiableViewMacro.apply(ImmutableList.of()); + MockModifiableViewRelOptTable mockEmpViewTable = MockModifiableViewRelOptTable.create( + (MockModifiableViewRelOptTable.MockModifiableViewTable) empModifiableView, this, + empModifiableViewNames.get(0), empModifiableViewNames.get(1), + empModifiableViewNames.get(2), false, 20, null); + registerTable(mockEmpViewTable); + + // Same as "EMP_MODIFIABLEVIEW" except that all columns are in the view, columns are reordered, + // and there is an `extra` extended column. + List<String> empModifiableViewNames2 = ImmutableList.of( + salesSchema.getCatalogName(), salesSchema.name, "EMP_MODIFIABLEVIEW2"); + TableMacro empModifiableViewMacro2 = MockModifiableViewRelOptTable.viewMacro(rootSchema, + "select ENAME, EMPNO, JOB, DEPTNO, SLACKER, SAL, EXTRA, HIREDATE, MGR, COMM" + + " from EMPDEFAULTS extend (EXTRA boolean)" + + " where DEPTNO = 20", empModifiableViewNames2.subList(0, 2), + ImmutableList.of(empModifiableViewNames.get(2)), true); + TranslatableTable empModifiableView2 = empModifiableViewMacro2.apply(ImmutableList.of()); + MockModifiableViewRelOptTable mockEmpViewTable2 = MockModifiableViewRelOptTable.create( + (MockModifiableViewRelOptTable.MockModifiableViewTable) empModifiableView2, this, + empModifiableViewNames2.get(0), empModifiableViewNames2.get(1), + empModifiableViewNames2.get(2), false, 20, null); + registerTable(mockEmpViewTable2); + return this; } @@ -511,22 +555,27 @@ public class MockCatalogReader extends CalciteCatalogReader { protected void registerTable(final MockTable table) { table.onRegister(typeFactory); - assert table.names.get(0).equals(DEFAULT_CATALOG); - final CalciteSchema schema = - rootSchema.getSubSchema(table.names.get(1), true); final WrapperTable wrapperTable = new WrapperTable(table); if (table.stream) { - schema.add(table.names.get(2), + registerTable(table.names, new StreamableWrapperTable(table) { public Table stream() { return wrapperTable; } }); } else { - schema.add(table.names.get(2), wrapperTable); + registerTable(table.names, wrapperTable); } } + private void registerTable(final List<String> names, final Table table) { + assert names.get(0).equals(DEFAULT_CATALOG); + final String schemaName = names.get(1); + final String tableName = names.get(2); + final CalciteSchema schema = rootSchema.getSubSchema(schemaName, true); + schema.add(tableName, table); + } + protected void registerSchema(MockSchema schema) { rootSchema.add(schema.name, new AbstractSchema()); } @@ -598,34 +647,62 @@ public class MockCatalogReader extends CalciteCatalogReader { */ public static class MockTable extends Prepare.AbstractPreparingTable { protected final MockCatalogReader catalogReader; - private final boolean stream; - private final double rowCount; + protected final boolean stream; + protected final double rowCount; protected final List<Map.Entry<String, RelDataType>> columnList = new ArrayList<>(); protected final List<Integer> keyList = new ArrayList<>(); protected RelDataType rowType; - private List<RelCollation> collationList; + protected List<RelCollation> collationList; protected final List<String> names; - private final Set<String> monotonicColumnSet = Sets.newHashSet(); - private StructKind kind = StructKind.FULLY_QUALIFIED; + protected final Set<String> monotonicColumnSet = Sets.newHashSet(); + protected StructKind kind = StructKind.FULLY_QUALIFIED; protected final ColumnResolver resolver; - private final InitializerExpressionFactory initializerFactory; + protected final InitializerExpressionFactory initializerFactory; public MockTable(MockCatalogReader catalogReader, String catalogName, String schemaName, String name, boolean stream, double rowCount, ColumnResolver resolver, InitializerExpressionFactory initializerFactory) { + this(catalogReader, ImmutableList.of(catalogName, schemaName, name), stream, rowCount, + resolver, initializerFactory); + } + + private MockTable(MockCatalogReader catalogReader, List<String> names, boolean stream, + double rowCount, ColumnResolver resolver, InitializerExpressionFactory initializerFactory) { this.catalogReader = catalogReader; this.stream = stream; this.rowCount = rowCount; - this.names = ImmutableList.of(catalogName, schemaName, name); + this.names = names; this.resolver = resolver; this.initializerFactory = initializerFactory; } + /** + * Copy constructor. + */ + protected MockTable(MockCatalogReader catalogReader, boolean stream, double rowCount, + List<Map.Entry<String, RelDataType>> columnList, List<Integer> keyList, + RelDataType rowType, List<RelCollation> collationList, List<String> names, + Set<String> monotonicColumnSet, StructKind kind, ColumnResolver resolver, + InitializerExpressionFactory initializerFactory) { + this.catalogReader = catalogReader; + this.stream = stream; + this.rowCount = rowCount; + this.rowType = rowType; + this.collationList = collationList; + this.names = names; + this.kind = kind; + this.resolver = resolver; + this.initializerFactory = initializerFactory; + for (String name : monotonicColumnSet) { + addMonotonic(name); + } + } + /** Implementation of AbstractModifiableTable. */ private class ModifiableTable extends JdbcTest.AbstractModifiableTable - implements Wrapper { + implements ExtensibleTable, Wrapper { protected ModifiableTable(String tableName) { super(tableName); } @@ -657,9 +734,35 @@ public class MockCatalogReader extends CalciteCatalogReader { @Override public <C> C unwrap(Class<C> aClass) { if (aClass.isInstance(initializerFactory)) { return aClass.cast(initializerFactory); + } else if (aClass.isInstance(MockTable.this)) { + return aClass.cast(MockTable.this); } return null; } + + @Override public Table extend(final List<RelDataTypeField> fields) { + return new ModifiableTable(Util.last(names)) { + @Override public RelDataType getRowType(RelDataTypeFactory typeFactory) { + ImmutableList<RelDataTypeField> allFields = ImmutableList.copyOf( + Iterables.concat( + rowType.getFieldList(), + fields)); + return typeFactory.createStructType(allFields); + } + }; + } + + @Override public int getExtendedColumnOffset() { + return rowType.getFieldCount(); + } + } + + @Override protected RelOptTable extend(final Table extendedTable) { + return new MockTable(catalogReader, names, stream, rowCount, resolver, initializerFactory) { + @Override public RelDataType getRowType() { + return extendedTable.getRowType(catalogReader.typeFactory); + } + }; } /** @@ -677,13 +780,6 @@ public class MockCatalogReader extends CalciteCatalogReader { RelDataType rowType, RelDataTypeFactory typeFactory, List<String> names) { return resolver.resolveColumn(rowType, typeFactory, names); } - - @Override public <C> C unwrap(Class<C> aClass) { - if (aClass.isInstance(initializerFactory)) { - return aClass.cast(initializerFactory); - } - return null; - } } public static MockTable create(MockCatalogReader catalogReader, @@ -815,6 +911,136 @@ public class MockCatalogReader extends CalciteCatalogReader { } /** + * Alternative to MockViewTable that exercises code paths in ModifiableViewTable + * and ModifiableViewTableInitializerExpressionFactory. + */ + public static class MockModifiableViewRelOptTable extends MockTable { + private final MockModifiableViewTable modifiableViewTable; + + private MockModifiableViewRelOptTable(MockModifiableViewTable modifiableViewTable, + MockCatalogReader catalogReader, String catalogName, String schemaName, String name, + boolean stream, double rowCount, ColumnResolver resolver, + InitializerExpressionFactory initializerExpressionFactory) { + super(catalogReader, ImmutableList.of(catalogName, schemaName, name), stream, rowCount, + resolver, initializerExpressionFactory); + this.modifiableViewTable = modifiableViewTable; + } + + /** + * Copy constructor. + */ + private MockModifiableViewRelOptTable(MockModifiableViewTable modifiableViewTable, + MockCatalogReader catalogReader, boolean stream, double rowCount, + List<Map.Entry<String, RelDataType>> columnList, List<Integer> keyList, + RelDataType rowType, List<RelCollation> collationList, List<String> names, + Set<String> monotonicColumnSet, StructKind kind, ColumnResolver resolver, + InitializerExpressionFactory initializerFactory) { + super(catalogReader, stream, rowCount, columnList, keyList, rowType, collationList, names, + monotonicColumnSet, kind, resolver, initializerFactory); + this.modifiableViewTable = modifiableViewTable; + } + + public static MockModifiableViewRelOptTable create(MockModifiableViewTable modifiableViewTable, + MockCatalogReader catalogReader, String catalogName, String schemaName, String name, + boolean stream, double rowCount, ColumnResolver resolver) { + final Table underlying = modifiableViewTable.unwrap(Table.class); + final InitializerExpressionFactory maybeInitializerExpressionFactory = + underlying != null && underlying instanceof Wrapper + ? ((Wrapper) underlying).unwrap(InitializerExpressionFactory.class) + : new NullInitializerExpressionFactory(catalogReader.typeFactory); + final InitializerExpressionFactory initializerExpressionFactory = + maybeInitializerExpressionFactory == null + ? new NullInitializerExpressionFactory(catalogReader.typeFactory) + : maybeInitializerExpressionFactory; + return new MockModifiableViewRelOptTable(modifiableViewTable, catalogReader, catalogName, + schemaName, name, stream, rowCount, resolver, initializerExpressionFactory); + } + + public static MockViewTableMacro viewMacro(CalciteSchema schema, String viewSql, + List<String> schemaPath, List<String> viewPath, Boolean modifiable) { + return new MockViewTableMacro(schema, viewSql, schemaPath, viewPath, modifiable); + } + + @Override public RelDataType getRowType() { + return modifiableViewTable.getRowType(catalogReader.typeFactory); + } + + @Override public RelOptTable extend(List<RelDataTypeField> extendedFields) { + final ExtensibleTable table = modifiableViewTable.unwrap(ExtensibleTable.class); + final Table extendedTable = table.extend(extendedFields); + final MockModifiableViewTable mockModifiableViewTableExtended = + new MockModifiableViewTable(modifiableViewTable.elementType, + RelDataTypeImpl.proto(extendedTable.getRowType(modifiableViewTable.typeFactory)), + modifiableViewTable.viewSql, modifiableViewTable.schemaPath, + modifiableViewTable.viewPath, extendedTable, modifiableViewTable.tablePath, + modifiableViewTable.constraint, modifiableViewTable.columnMapping, + modifiableViewTable.typeFactory); + return new MockModifiableViewRelOptTable(mockModifiableViewTableExtended, catalogReader, + stream, rowCount, columnList, keyList, rowType, collationList, names, monotonicColumnSet, + kind, resolver, initializerFactory); + } + + @Override public <T> T unwrap(Class<T> clazz) { + if (clazz.isInstance(modifiableViewTable)) { + return clazz.cast(modifiableViewTable); + } + return super.unwrap(clazz); + } + + /** + * A TableMacro that creates mock ModifiableViewTable. + */ + public static class MockViewTableMacro extends ViewTableMacro { + MockViewTableMacro(CalciteSchema schema, String viewSql, List<String> schemaPath, + List<String> viewPath, Boolean modifiable) { + super(schema, viewSql, schemaPath, viewPath, modifiable); + } + + @Override protected ModifiableViewTable modifiableViewTable( + CalcitePrepare.AnalyzeViewResult parsed, String viewSql, + List<String> schemaPath, List<String> viewPath, CalciteSchema schema) { + final JavaTypeFactory typeFactory = (JavaTypeFactory) parsed.typeFactory; + final Type elementType = typeFactory.getJavaClass(parsed.rowType); + return new MockModifiableViewTable(elementType, + RelDataTypeImpl.proto(parsed.rowType), viewSql, schemaPath, viewPath, + parsed.table, Schemas.path(schema.root(), parsed.tablePath), + parsed.constraint, parsed.columnMapping, parsed.typeFactory); + } + } + + /** + * A mock of ModifiableViewTable that can unwrap a mock RelOptTable. + */ + private static class MockModifiableViewTable extends ModifiableViewTable { + private final Type elementType; + private final String viewSql; + private final List<String> schemaPath; + private final List<String> viewPath; + private final Path tablePath; + private final RexNode constraint; + private final ImmutableIntList columnMapping; + private final RelDataTypeFactory typeFactory; + + MockModifiableViewTable(Type elementType, RelProtoDataType rowType, + String viewSql, List<String> schemaPath, List<String> viewPath, + Table table, Path tablePath, RexNode constraint, + ImmutableIntList columnMapping, RelDataTypeFactory typeFactory) { + super(elementType, rowType, viewSql, schemaPath, viewPath, table, + tablePath, constraint, columnMapping, typeFactory); + this.elementType = elementType; + this.viewSql = viewSql; + this.schemaPath = schemaPath; + this.viewPath = viewPath; + this.tablePath = tablePath; + this.constraint = constraint; + this.columnMapping = columnMapping; + this.typeFactory = typeFactory; + } + } + + } + + /** * Mock implementation of * {@link org.apache.calcite.prepare.Prepare.PreparingTable} for views. */
