blob: 27d4d4c779cb31adc40e2b5e07d8989a070dfa06 [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.schema.impl;
import org.apache.calcite.materialize.Lattice;
import org.apache.calcite.plan.Convention;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptCost;
import org.apache.calcite.plan.RelOptPlanner;
import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.TableScan;
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.schema.Schema;
import org.apache.calcite.schema.Table;
import org.apache.calcite.schema.TranslatableTable;
import org.apache.calcite.util.ImmutableIntList;
import org.apache.calcite.util.Pair;
import com.google.common.collect.ImmutableList;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static org.apache.calcite.linq4j.Nullness.castNonNull;
/**
* Virtual table that is composed of two or more tables joined together.
*
* <p>Star tables do not occur in end-user queries. They are introduced by the
* optimizer to help matching queries to materializations, and used only
* during the planning process.</p>
*
* <p>When a materialization is defined, if it involves a join, it is converted
* to a query on top of a star table. Queries that are candidates to map onto
* the materialization are mapped onto the same star table.</p>
*/
public class StarTable extends AbstractTable implements TranslatableTable {
public final Lattice lattice;
// TODO: we'll also need a list of join conditions between tables. For now
// we assume that join conditions match
public final ImmutableList<Table> tables;
/** Number of fields in each table's row type. */
public @MonotonicNonNull ImmutableIntList fieldCounts;
/** Creates a StarTable. */
private StarTable(Lattice lattice, ImmutableList<Table> tables) {
this.lattice = Objects.requireNonNull(lattice, "lattice");
this.tables = tables;
}
/** Creates a StarTable and registers it in a schema. */
public static StarTable of(Lattice lattice, List<Table> tables) {
return new StarTable(lattice, ImmutableList.copyOf(tables));
}
@Override public Schema.TableType getJdbcTableType() {
return Schema.TableType.STAR;
}
@Override public RelDataType getRowType(RelDataTypeFactory typeFactory) {
final List<RelDataType> typeList = new ArrayList<>();
final List<Integer> fieldCounts = new ArrayList<>();
for (Table table : tables) {
final RelDataType rowType = table.getRowType(typeFactory);
typeList.addAll(RelOptUtil.getFieldTypeList(rowType));
fieldCounts.add(rowType.getFieldCount());
}
// Compute fieldCounts the first time this method is called. Safe to assume
// that the field counts will be the same whichever type factory is used.
if (this.fieldCounts == null) {
this.fieldCounts = ImmutableIntList.copyOf(fieldCounts);
}
return typeFactory.createStructType(typeList, lattice.uniqueColumnNames());
}
@Override public RelNode toRel(RelOptTable.ToRelContext context, RelOptTable table) {
// Create a table scan of infinite cost.
return new StarTableScan(context.getCluster(), table);
}
public StarTable add(Table table) {
return of(lattice,
ImmutableList.<Table>builder().addAll(tables).add(table).build());
}
/** Returns the column offset of the first column of {@code table} in this
* star table's output row type.
*
* @param table Table
* @return Column offset
* @throws IllegalArgumentException if table is not in this star
*/
public int columnOffset(Table table) {
int n = 0;
for (Pair<Table, Integer> pair : Pair.zip(tables, castNonNull(fieldCounts))) {
if (pair.left == table) {
return n;
}
n += pair.right;
}
throw new IllegalArgumentException("star table " + this
+ " does not contain table " + table);
}
/** Relational expression that scans a {@link StarTable}.
*
* <p>It has infinite cost.
*/
public static class StarTableScan extends TableScan {
public StarTableScan(RelOptCluster cluster, RelOptTable relOptTable) {
super(cluster, cluster.traitSetOf(Convention.NONE), ImmutableList.of(), relOptTable);
}
@Override public @Nullable RelOptCost computeSelfCost(RelOptPlanner planner,
RelMetadataQuery mq) {
return planner.getCostFactory().makeInfiniteCost();
}
}
}