blob: 879052166a98a91acdaf2617f086a40abb7c6ca0 [file] [log] [blame]
/*
* 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.rel.core;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptCost;
import org.apache.calcite.plan.RelOptPlanner;
import org.apache.calcite.plan.RelOptSchema;
import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.prepare.Prepare;
import org.apache.calcite.rel.RelInput;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.RelWriter;
import org.apache.calcite.rel.SingleRel;
import org.apache.calcite.rel.externalize.RelEnumTypes;
import org.apache.calcite.rel.metadata.RelMetadataQuery;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.type.SqlTypeUtil;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.List;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;
/**
* Relational expression that modifies a table.
*
* <p>It is similar to {@link org.apache.calcite.rel.core.TableScan},
* but represents a request to modify a table rather than read from it.
* It takes one child which produces the modified rows. Those rows are:
*
* <ul>
* <li>For {@code INSERT}, those rows are the new values;
* <li>for {@code DELETE}, the old values;
* <li>for {@code UPDATE}, all old values plus updated new values.
* </ul>
*/
public abstract class TableModify extends SingleRel {
//~ Enums ------------------------------------------------------------------
/**
* Enumeration of supported modification operations.
*/
public enum Operation {
INSERT, UPDATE, DELETE, MERGE
}
//~ Instance fields --------------------------------------------------------
/**
* The connection to the optimizing session.
*/
protected Prepare.CatalogReader catalogReader;
/**
* The table definition.
*/
protected final RelOptTable table;
private final Operation operation;
private final @Nullable List<String> updateColumnList;
private final @Nullable List<RexNode> sourceExpressionList;
private @MonotonicNonNull RelDataType inputRowType;
private final boolean flattened;
//~ Constructors -----------------------------------------------------------
/**
* Creates a {@code TableModify}.
*
* <p>The UPDATE operation has format like this:
* <blockquote>
* <pre>UPDATE table SET iden1 = exp1, ident2 = exp2 WHERE condition</pre>
* </blockquote>
*
* @param cluster Cluster this relational expression belongs to
* @param traitSet Traits of this relational expression
* @param table Target table to modify
* @param catalogReader accessor to the table metadata.
* @param input Sub-query or filter condition
* @param operation Modify operation (INSERT, UPDATE, DELETE)
* @param updateColumnList List of column identifiers to be updated
* (e.g. ident1, ident2); null if not UPDATE
* @param sourceExpressionList List of value expressions to be set
* (e.g. exp1, exp2); null if not UPDATE
* @param flattened Whether set flattens the input row type
*/
protected TableModify(
RelOptCluster cluster,
RelTraitSet traitSet,
RelOptTable table,
Prepare.CatalogReader catalogReader,
RelNode input,
Operation operation,
@Nullable List<String> updateColumnList,
@Nullable List<RexNode> sourceExpressionList,
boolean flattened) {
super(cluster, traitSet, input);
this.table = table;
this.catalogReader = catalogReader;
this.operation = operation;
this.updateColumnList = updateColumnList;
this.sourceExpressionList = sourceExpressionList;
if (operation == Operation.UPDATE) {
requireNonNull(updateColumnList, "updateColumnList");
requireNonNull(sourceExpressionList, "sourceExpressionList");
checkArgument(sourceExpressionList.size() == updateColumnList.size());
} else {
if (operation == Operation.MERGE) {
requireNonNull(updateColumnList, "updateColumnList");
} else {
checkArgument(updateColumnList == null);
}
checkArgument(sourceExpressionList == null);
}
RelOptSchema relOptSchema = table.getRelOptSchema();
if (relOptSchema != null) {
cluster.getPlanner().registerSchema(relOptSchema);
}
this.flattened = flattened;
}
/**
* Creates a TableModify by parsing serialized output.
*/
protected TableModify(RelInput input) {
this(input.getCluster(),
input.getTraitSet(),
input.getTable("table"),
(Prepare.CatalogReader) requireNonNull(
input.getTable("table").getRelOptSchema(),
"relOptSchema"),
input.getInput(),
requireNonNull(input.getEnum("operation", Operation.class), "operation"),
input.getStringList("updateColumnList"),
input.getExpressionList("sourceExpressionList"),
input.getBoolean("flattened", false));
}
//~ Methods ----------------------------------------------------------------
public Prepare.CatalogReader getCatalogReader() {
return catalogReader;
}
@Override public RelOptTable getTable() {
return table;
}
public @Nullable List<String> getUpdateColumnList() {
return updateColumnList;
}
public @Nullable List<RexNode> getSourceExpressionList() {
return sourceExpressionList;
}
public boolean isFlattened() {
return flattened;
}
public Operation getOperation() {
return operation;
}
public boolean isInsert() {
return operation == Operation.INSERT;
}
public boolean isUpdate() {
return operation == Operation.UPDATE;
}
public boolean isDelete() {
return operation == Operation.DELETE;
}
public boolean isMerge() {
return operation == Operation.MERGE;
}
@Override public RelDataType deriveRowType() {
return RelOptUtil.createDmlRowType(
SqlKind.INSERT, getCluster().getTypeFactory());
}
@Override public RelDataType getExpectedInputRowType(int ordinalInParent) {
assert ordinalInParent == 0;
if (inputRowType != null) {
return inputRowType;
}
final RelDataTypeFactory typeFactory = getCluster().getTypeFactory();
final RelDataType rowType = table.getRowType();
switch (operation) {
case UPDATE:
assert updateColumnList != null : "updateColumnList must not be null for " + operation;
inputRowType =
typeFactory.createJoinType(rowType,
getCatalogReader().createTypeFromProjection(rowType,
updateColumnList));
break;
case MERGE:
assert updateColumnList != null : "updateColumnList must not be null for " + operation;
inputRowType =
typeFactory.createJoinType(
typeFactory.createJoinType(rowType, rowType),
getCatalogReader().createTypeFromProjection(rowType,
updateColumnList));
break;
default:
inputRowType = rowType;
break;
}
if (flattened) {
inputRowType =
SqlTypeUtil.flattenRecordType(
typeFactory,
inputRowType,
null);
}
return inputRowType;
}
@Override public RelWriter explainTerms(RelWriter pw) {
return super.explainTerms(pw)
.item("table", table.getQualifiedName())
.item("operation", RelEnumTypes.fromEnum(getOperation()))
.itemIf("updateColumnList", updateColumnList, updateColumnList != null)
.itemIf("sourceExpressionList", sourceExpressionList,
sourceExpressionList != null)
.item("flattened", flattened);
}
@Override public @Nullable RelOptCost computeSelfCost(RelOptPlanner planner,
RelMetadataQuery mq) {
// REVIEW jvs 21-Apr-2006: Just for now...
double rowCount = mq.getRowCount(this);
return planner.getCostFactory().makeCost(rowCount, 0, 0);
}
}