com.hazelcast.jet.sql.impl.schema.HazelcastRelOptTable Maven / Gradle / Ivy
/*
* Copyright 2021 Hazelcast Inc.
*
* Licensed under the Hazelcast Community License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://hazelcast.com/hazelcast-community-license
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hazelcast.jet.sql.impl.schema;
import com.hazelcast.jet.sql.impl.opt.common.CalcIntoScanRule;
import com.hazelcast.org.apache.calcite.linq4j.tree.Expression;
import com.hazelcast.org.apache.calcite.plan.RelOptSchema;
import com.hazelcast.org.apache.calcite.plan.RelOptTable;
import com.hazelcast.org.apache.calcite.prepare.Prepare;
import com.hazelcast.org.apache.calcite.rel.RelCollation;
import com.hazelcast.org.apache.calcite.rel.RelDistribution;
import com.hazelcast.org.apache.calcite.rel.RelNode;
import com.hazelcast.org.apache.calcite.rel.RelReferentialConstraint;
import com.hazelcast.org.apache.calcite.rel.RelWriter;
import com.hazelcast.org.apache.calcite.rel.core.TableScan;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalTableScan;
import com.hazelcast.org.apache.calcite.rel.type.RelDataType;
import com.hazelcast.org.apache.calcite.rel.type.RelDataTypeField;
import com.hazelcast.org.apache.calcite.schema.ColumnStrategy;
import com.hazelcast.org.apache.calcite.sql.SqlAccessType;
import com.hazelcast.org.apache.calcite.sql.validate.SqlModality;
import com.hazelcast.org.apache.calcite.sql.validate.SqlMonotonicity;
import com.hazelcast.org.apache.calcite.sql2rel.InitializerContext;
import com.hazelcast.org.apache.calcite.util.ImmutableBitSet;
import java.util.ArrayList;
import java.util.List;
/**
* Custom implementation of Apache Calcite table.
*
* Tables are used inside {@link TableScan} operators. During logical planning we attempt to flatten the relational tree by
* pushing down projects and filters into the table, see {@link CalcIntoScanRule}.
*
* It is important to distinguish {@code TableScan} operators with and without pushdown. Otherwise scans that produce different
* results will be merged into a single equivalence group, leading to incorrect query results.
*
* To mitigate this we provide our own table implementation that overrides {@link #getQualifiedName()} method, used for scan
* signature calculation (see {@link TableScan#explainTerms(RelWriter)}). The overridden version adds information about
* pushed-down projections and scans to the table name, thus avoiding the problem.
*
* For example, a table scan over table {@code p} without pushdowns may have a signature:
*
* ...table=[[hazelcast, p]]....
*
*
* A scan over the same table with project and filter will have a signature:
*
* ...table=[[hazelcast, p[projects=[0, 1], filter=>($2, 1)]]]...
*
*/
public class HazelcastRelOptTable implements Prepare.PreparingTable {
private final Prepare.PreparingTable delegate;
public HazelcastRelOptTable(Prepare.PreparingTable delegate) {
this.delegate = delegate;
}
public Prepare.PreparingTable getDelegate() {
return delegate;
}
@Override
public List getQualifiedName() {
// Get original names.
List names = delegate.getQualifiedName();
assert names != null && !names.isEmpty();
List res = new ArrayList<>(names);
// Extend the table name (the last element) with project/filter signature.
int lastElement = res.size() - 1;
res.set(lastElement, res.get(lastElement) + getTableSignature());
return res;
}
private String getTableSignature() {
HazelcastTable table = delegate.unwrap(HazelcastTable.class);
assert table != null;
return table.getSignature();
}
@Override
public double getRowCount() {
return delegate.getRowCount();
}
@Override
public RelDataType getRowType() {
return delegate.getRowType();
}
@Override
public RelOptSchema getRelOptSchema() {
return delegate.getRelOptSchema();
}
@Override
public RelNode toRel(ToRelContext context) {
// Override this method to pass this table to the LogicalTableScan.
// Otherwise the delegate would be used, which is incorrect.
return LogicalTableScan.create(context.getCluster(), this, context.getTableHints());
}
@Override
public List getCollationList() {
return delegate.getCollationList();
}
@Override
public RelDistribution getDistribution() {
return delegate.getDistribution();
}
@Override
public boolean isKey(ImmutableBitSet columns) {
return delegate.isKey(columns);
}
@Override
public List getKeys() {
return delegate.getKeys();
}
@Override
public List getReferentialConstraints() {
return delegate.getReferentialConstraints();
}
@Override
public Expression getExpression(Class clazz) {
return delegate.getExpression(clazz);
}
@Override
public RelOptTable extend(List extendedFields) {
return delegate.extend(extendedFields);
}
@Override
public List getColumnStrategies() {
return delegate.getColumnStrategies();
}
@Override
public SqlMonotonicity getMonotonicity(String columnName) {
return delegate.getMonotonicity(columnName);
}
@Override
public SqlAccessType getAllowedAccess() {
return delegate.getAllowedAccess();
}
@Override
public boolean supportsModality(SqlModality modality) {
return delegate.supportsModality(modality);
}
@Override
public boolean isTemporal() {
return delegate.isTemporal();
}
@SuppressWarnings("deprecation")
@Override
public boolean columnHasDefaultValue(RelDataType rowType, int ordinal, InitializerContext initializerContext) {
return delegate.columnHasDefaultValue(rowType, ordinal, initializerContext);
}
@Override
public C unwrap(Class aClass) {
return delegate.unwrap(aClass);
}
}