blob: 9ff12c0e0e353f6904520ed49677f27fc2855dab [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.test;
import org.apache.calcite.adapter.java.JavaTypeFactory;
import org.apache.calcite.jdbc.CalcitePrepare;
import org.apache.calcite.jdbc.CalciteSchema;
import org.apache.calcite.jdbc.ContextSqlValidator;
import org.apache.calcite.linq4j.Enumerator;
import org.apache.calcite.linq4j.Linq4j;
import org.apache.calcite.linq4j.QueryProvider;
import org.apache.calcite.linq4j.Queryable;
import org.apache.calcite.linq4j.tree.Expression;
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.schema.SchemaPlus;
import org.apache.calcite.schema.Schemas;
import org.apache.calcite.schema.TranslatableTable;
import org.apache.calcite.schema.impl.AbstractTableQueryable;
import org.apache.calcite.schema.impl.ViewTable;
import org.apache.calcite.schema.impl.ViewTableMacro;
import org.apache.calcite.server.DdlExecutor;
import org.apache.calcite.server.DdlExecutorImpl;
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlUtil;
import org.apache.calcite.sql.dialect.CalciteSqlDialect;
import org.apache.calcite.sql.parser.SqlAbstractParserImpl;
import org.apache.calcite.sql.parser.SqlParseException;
import org.apache.calcite.sql.parser.SqlParserImplFactory;
import org.apache.calcite.sql.parser.parserextensiontesting.ExtensionSqlParserImpl;
import org.apache.calcite.sql.parser.parserextensiontesting.SqlCreateTable;
import org.apache.calcite.sql.pretty.SqlPrettyWriter;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.tools.FrameworkConfig;
import org.apache.calcite.tools.Frameworks;
import org.apache.calcite.tools.Planner;
import org.apache.calcite.tools.RelConversionException;
import org.apache.calcite.tools.ValidationException;
import org.apache.calcite.util.Util;
import com.google.common.collect.ImmutableList;
import java.io.Reader;
import java.lang.reflect.Type;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import static org.apache.calcite.util.Static.RESOURCE;
/** Executes the few DDL commands supported by
* {@link ExtensionSqlParserImpl}. */
public class ExtensionDdlExecutor extends DdlExecutorImpl {
static final ExtensionDdlExecutor INSTANCE = new ExtensionDdlExecutor();
/** Parser factory. */
@SuppressWarnings("unused") // used via reflection
public static final SqlParserImplFactory PARSER_FACTORY =
new SqlParserImplFactory() {
@Override public SqlAbstractParserImpl getParser(Reader stream) {
return ExtensionSqlParserImpl.FACTORY.getParser(stream);
}
@Override public DdlExecutor getDdlExecutor() {
return ExtensionDdlExecutor.INSTANCE;
}
};
/** Executes a {@code CREATE TABLE} command. Called via reflection. */
public void execute(SqlCreateTable create, CalcitePrepare.Context context) {
final CalciteSchema schema =
Schemas.subSchema(context.getRootSchema(),
context.getDefaultSchemaPath());
final JavaTypeFactory typeFactory = context.getTypeFactory();
final RelDataType queryRowType;
if (create.query != null) {
// A bit of a hack: pretend it's a view, to get its row type
final String sql =
create.query.toSqlString(CalciteSqlDialect.DEFAULT).getSql();
final ViewTableMacro viewTableMacro =
ViewTable.viewMacro(schema.plus(), sql, schema.path(null),
context.getObjectPath(), false);
final TranslatableTable x = viewTableMacro.apply(ImmutableList.of());
queryRowType = x.getRowType(typeFactory);
if (create.columnList != null
&& queryRowType.getFieldCount() != create.columnList.size()) {
throw SqlUtil.newContextException(create.columnList.getParserPosition(),
RESOURCE.columnCountMismatch());
}
} else {
queryRowType = null;
}
final RelDataTypeFactory.Builder builder = typeFactory.builder();
if (create.columnList != null) {
final SqlValidator validator = new ContextSqlValidator(context, false);
create.forEachNameType((name, typeSpec) ->
builder.add(name.getSimple(), typeSpec.deriveType(validator, true)));
} else {
if (queryRowType == null) {
// "CREATE TABLE t" is invalid; because there is no "AS query" we need
// a list of column names and types, "CREATE TABLE t (INT c)".
throw SqlUtil.newContextException(create.name.getParserPosition(),
RESOURCE.createTableRequiresColumnList());
}
builder.addAll(queryRowType.getFieldList());
}
final RelDataType rowType = builder.build();
schema.add(create.name.getSimple(),
new MutableArrayTable(create.name.getSimple(),
RelDataTypeImpl.proto(rowType)));
if (create.query != null) {
populate(create.name, create.query, context);
}
}
/** Populates the table called {@code name} by executing {@code query}. */
protected static void populate(SqlIdentifier name, SqlNode query,
CalcitePrepare.Context context) {
// Generate, prepare and execute an "INSERT INTO table query" statement.
// (It's a bit inefficient that we convert from SqlNode to SQL and back
// again.)
final FrameworkConfig config = Frameworks.newConfigBuilder()
.defaultSchema(
Objects.requireNonNull(
Schemas.subSchema(context.getRootSchema(),
context.getDefaultSchemaPath())).plus())
.build();
final Planner planner = Frameworks.getPlanner(config);
try {
final StringBuilder buf = new StringBuilder();
final SqlPrettyWriter w =
new SqlPrettyWriter(
SqlPrettyWriter.config()
.withDialect(CalciteSqlDialect.DEFAULT)
.withAlwaysUseParentheses(false),
buf);
buf.append("INSERT INTO ");
name.unparse(w, 0, 0);
buf.append(" ");
query.unparse(w, 0, 0);
final String sql = buf.toString();
final SqlNode query1 = planner.parse(sql);
final SqlNode query2 = planner.validate(query1);
final RelRoot r = planner.rel(query2);
final PreparedStatement prepare =
context.getRelRunner().prepareStatement(r.rel);
int rowCount = prepare.executeUpdate();
Util.discard(rowCount);
prepare.close();
} catch (SqlParseException | ValidationException
| RelConversionException | SQLException e) {
throw Util.throwAsRuntime(e);
}
}
/** Table backed by a Java list. */
private static class MutableArrayTable
extends AbstractModifiableTable {
final List list = new ArrayList();
private final RelProtoDataType protoRowType;
MutableArrayTable(String name, RelProtoDataType protoRowType) {
super(name);
this.protoRowType = protoRowType;
}
public Collection getModifiableCollection() {
return list;
}
public <T> Queryable<T> asQueryable(QueryProvider queryProvider,
SchemaPlus schema, String tableName) {
return new AbstractTableQueryable<T>(queryProvider, schema, this,
tableName) {
public Enumerator<T> enumerator() {
//noinspection unchecked
return (Enumerator<T>) Linq4j.enumerator(list);
}
};
}
public Type getElementType() {
return Object[].class;
}
public Expression getExpression(SchemaPlus schema, String tableName,
Class clazz) {
return Schemas.tableExpression(schema, getElementType(),
tableName, clazz);
}
public RelDataType getRowType(RelDataTypeFactory typeFactory) {
return protoRowType.apply(typeFactory);
}
}
}