SQL: Groundwork for views. (#3962)

* SQL: Groundwork for views.

They are not actually exposed to users at this point, but enough is there
to have some test cases in CalciteQueryTest.

* Remove unused imports.

* Fix injection problem.
This commit is contained in:
Gian Merlino 2017-03-20 11:53:11 -07:00 committed by Jonathan Wei
parent ad477cb454
commit 64248d31b6
11 changed files with 568 additions and 69 deletions

View File

@ -23,8 +23,10 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import com.google.inject.Inject; import com.google.inject.Inject;
@ -50,7 +52,10 @@ import io.druid.server.coordination.DruidServerMetadata;
import io.druid.sql.calcite.planner.PlannerConfig; import io.druid.sql.calcite.planner.PlannerConfig;
import io.druid.sql.calcite.table.DruidTable; import io.druid.sql.calcite.table.DruidTable;
import io.druid.sql.calcite.table.RowSignature; import io.druid.sql.calcite.table.RowSignature;
import io.druid.sql.calcite.view.DruidViewMacro;
import io.druid.sql.calcite.view.ViewManager;
import io.druid.timeline.DataSegment; import io.druid.timeline.DataSegment;
import org.apache.calcite.schema.Function;
import org.apache.calcite.schema.Table; import org.apache.calcite.schema.Table;
import org.apache.calcite.schema.impl.AbstractSchema; import org.apache.calcite.schema.impl.AbstractSchema;
import org.joda.time.DateTime; import org.joda.time.DateTime;
@ -73,6 +78,7 @@ public class DruidSchema extends AbstractSchema
private final QuerySegmentWalker walker; private final QuerySegmentWalker walker;
private final TimelineServerView serverView; private final TimelineServerView serverView;
private final PlannerConfig config; private final PlannerConfig config;
private final ViewManager viewManager;
private final ExecutorService cacheExec; private final ExecutorService cacheExec;
private final ConcurrentMap<String, Table> tables; private final ConcurrentMap<String, Table> tables;
@ -92,12 +98,14 @@ public class DruidSchema extends AbstractSchema
public DruidSchema( public DruidSchema(
final QuerySegmentWalker walker, final QuerySegmentWalker walker,
final TimelineServerView serverView, final TimelineServerView serverView,
final PlannerConfig config final PlannerConfig config,
final ViewManager viewManager
) )
{ {
this.walker = Preconditions.checkNotNull(walker, "walker"); this.walker = Preconditions.checkNotNull(walker, "walker");
this.serverView = Preconditions.checkNotNull(serverView, "serverView"); this.serverView = Preconditions.checkNotNull(serverView, "serverView");
this.config = Preconditions.checkNotNull(config, "config"); this.config = Preconditions.checkNotNull(config, "config");
this.viewManager = Preconditions.checkNotNull(viewManager, "viewManager");
this.cacheExec = ScheduledExecutors.fixed(1, "DruidSchema-Cache-%d"); this.cacheExec = ScheduledExecutors.fixed(1, "DruidSchema-Cache-%d");
this.tables = Maps.newConcurrentMap(); this.tables = Maps.newConcurrentMap();
} }
@ -274,6 +282,16 @@ public class DruidSchema extends AbstractSchema
return ImmutableMap.copyOf(tables); return ImmutableMap.copyOf(tables);
} }
@Override
protected Multimap<String, Function> getFunctionMultimap()
{
final ImmutableMultimap.Builder<String, Function> builder = ImmutableMultimap.builder();
for (Map.Entry<String, DruidViewMacro> entry : viewManager.getViews().entrySet()) {
builder.put(entry);
}
return builder.build();
}
private DruidTable computeTable(final String dataSource) private DruidTable computeTable(final String dataSource)
{ {
final SegmentMetadataQuery segmentMetadataQuery = new SegmentMetadataQuery( final SegmentMetadataQuery segmentMetadataQuery = new SegmentMetadataQuery(

View File

@ -21,8 +21,11 @@ package io.druid.sql.calcite.schema;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable; import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.inject.Inject; import com.google.inject.Inject;
import io.druid.segment.column.ValueType; import io.druid.segment.column.ValueType;
import io.druid.sql.calcite.table.RowSignature; import io.druid.sql.calcite.table.RowSignature;
@ -39,16 +42,19 @@ import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.schema.Statistic; import org.apache.calcite.schema.Statistic;
import org.apache.calcite.schema.Statistics; import org.apache.calcite.schema.Statistics;
import org.apache.calcite.schema.Table; import org.apache.calcite.schema.Table;
import org.apache.calcite.schema.TableMacro;
import org.apache.calcite.schema.impl.AbstractSchema; import org.apache.calcite.schema.impl.AbstractSchema;
import org.apache.calcite.sql.type.SqlTypeName; import org.apache.calcite.sql.type.SqlTypeName;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.Set;
public class InformationSchema extends AbstractSchema public class InformationSchema extends AbstractSchema
{ {
public static final String NAME = "INFORMATION_SCHEMA"; public static final String NAME = "INFORMATION_SCHEMA";
private static final String EMPTY_CATALOG = "";
private static final String SCHEMATA_TABLE = "SCHEMATA"; private static final String SCHEMATA_TABLE = "SCHEMATA";
private static final String TABLES_TABLE = "TABLES"; private static final String TABLES_TABLE = "TABLES";
private static final String COLUMNS_TABLE = "COLUMNS"; private static final String COLUMNS_TABLE = "COLUMNS";
@ -126,7 +132,7 @@ public class InformationSchema extends AbstractSchema
{ {
final SchemaPlus subSchema = rootSchema.getSubSchema(schemaName); final SchemaPlus subSchema = rootSchema.getSubSchema(schemaName);
return new Object[]{ return new Object[]{
"", // CATALOG_NAME EMPTY_CATALOG, // CATALOG_NAME
subSchema.getName(), // SCHEMA_NAME subSchema.getName(), // SCHEMA_NAME
null, // SCHEMA_OWNER null, // SCHEMA_OWNER
null, // DEFAULT_CHARACTER_SET_CATALOG null, // DEFAULT_CHARACTER_SET_CATALOG
@ -174,21 +180,44 @@ public class InformationSchema extends AbstractSchema
public Iterable<Object[]> apply(final String schemaName) public Iterable<Object[]> apply(final String schemaName)
{ {
final SchemaPlus subSchema = rootSchema.getSubSchema(schemaName); final SchemaPlus subSchema = rootSchema.getSubSchema(schemaName);
final Set<String> tableNames = subSchema.getTableNames(); return Iterables.filter(
return FluentIterable.from(tableNames).transform( Iterables.concat(
new Function<String, Object[]>() FluentIterable.from(subSchema.getTableNames()).transform(
{ new Function<String, Object[]>()
@Override {
public Object[] apply(final String tableName) @Override
{ public Object[] apply(final String tableName)
return new Object[]{ {
null, // TABLE_CATALOG return new Object[]{
schemaName, // TABLE_SCHEMA EMPTY_CATALOG, // TABLE_CATALOG
tableName, // TABLE_NAME schemaName, // TABLE_SCHEMA
subSchema.getTable(tableName).getJdbcTableType().toString() // TABLE_TYPE tableName, // TABLE_NAME
}; subSchema.getTable(tableName).getJdbcTableType().toString() // TABLE_TYPE
} };
} }
}
),
FluentIterable.from(subSchema.getFunctionNames()).transform(
new Function<String, Object[]>()
{
@Override
public Object[] apply(final String functionName)
{
if (getView(subSchema, functionName) != null) {
return new Object[]{
EMPTY_CATALOG, // TABLE_CATALOG
schemaName, // TABLE_SCHEMA
functionName, // TABLE_NAME
"VIEW" // TABLE_TYPE
};
} else {
return null;
}
}
}
)
),
Predicates.notNull()
); );
} }
} }
@ -230,50 +259,49 @@ public class InformationSchema extends AbstractSchema
public Iterable<Object[]> apply(final String schemaName) public Iterable<Object[]> apply(final String schemaName)
{ {
final SchemaPlus subSchema = rootSchema.getSubSchema(schemaName); final SchemaPlus subSchema = rootSchema.getSubSchema(schemaName);
final Set<String> tableNames = subSchema.getTableNames();
final JavaTypeFactoryImpl typeFactory = new JavaTypeFactoryImpl(TYPE_SYSTEM); final JavaTypeFactoryImpl typeFactory = new JavaTypeFactoryImpl(TYPE_SYSTEM);
return FluentIterable.from(tableNames).transformAndConcat(
new Function<String, Iterable<Object[]>>() return Iterables.concat(
{ Iterables.filter(
@Override Iterables.concat(
public Iterable<Object[]> apply(final String tableName) FluentIterable.from(subSchema.getTableNames()).transform(
{ new Function<String, Iterable<Object[]>>()
return FluentIterable
.from(subSchema.getTable(tableName).getRowType(typeFactory).getFieldList())
.transform(
new Function<RelDataTypeField, Object[]>()
{ {
@Override @Override
public Object[] apply(final RelDataTypeField field) public Iterable<Object[]> apply(final String tableName)
{ {
final RelDataType type = field.getType(); return generateColumnMetadata(
boolean isNumeric = SqlTypeName.NUMERIC_TYPES.contains(type.getSqlTypeName()); schemaName,
boolean isCharacter = SqlTypeName.CHAR_TYPES.contains(type.getSqlTypeName()); tableName,
boolean isDateTime = SqlTypeName.DATETIME_TYPES.contains(type.getSqlTypeName()); subSchema.getTable(tableName),
return new Object[]{ typeFactory
"", // TABLE_CATALOG );
schemaName, // TABLE_SCHEMA
tableName, // TABLE_NAME
field.getName(), // COLUMN_NAME
String.valueOf(field.getIndex()), // ORDINAL_POSITION
"", // COLUMN_DEFAULT
type.isNullable() ? "YES" : "NO", // IS_NULLABLE
type.getSqlTypeName().toString(), // DATA_TYPE
null, // CHARACTER_MAXIMUM_LENGTH
null, // CHARACTER_OCTET_LENGTH
isNumeric ? String.valueOf(type.getPrecision()) : null, // NUMERIC_PRECISION
isNumeric ? "10" : null, // NUMERIC_PRECISION_RADIX
isNumeric ? String.valueOf(type.getScale()) : null, // NUMERIC_SCALE
isDateTime ? String.valueOf(type.getPrecision()) : null, // DATETIME_PRECISION
isCharacter ? type.getCharset().name() : null, // CHARACTER_SET_NAME
isCharacter ? type.getCollation().getCollationName() : null, // COLLATION_NAME
type.getSqlTypeName().getJdbcOrdinal() // JDBC_TYPE (Druid extension)
};
} }
} }
); ),
} FluentIterable.from(subSchema.getFunctionNames()).transform(
} new Function<String, Iterable<Object[]>>()
{
@Override
public Iterable<Object[]> apply(final String functionName)
{
final TableMacro viewMacro = getView(subSchema, functionName);
if (viewMacro == null) {
return null;
}
return generateColumnMetadata(
schemaName,
functionName,
viewMacro.apply(ImmutableList.of()),
typeFactory
);
}
}
)
),
Predicates.notNull()
)
); );
} }
} }
@ -299,5 +327,78 @@ public class InformationSchema extends AbstractSchema
{ {
return TableType.SYSTEM_TABLE; return TableType.SYSTEM_TABLE;
} }
@Nullable
private Iterable<Object[]> generateColumnMetadata(
final String schemaName,
final String tableName,
final Table table,
final RelDataTypeFactory typeFactory
)
{
if (table == null) {
return null;
}
return FluentIterable
.from(table.getRowType(typeFactory).getFieldList())
.transform(
new Function<RelDataTypeField, Object[]>()
{
@Override
public Object[] apply(final RelDataTypeField field)
{
final RelDataType type = field.getType();
boolean isNumeric = SqlTypeName.NUMERIC_TYPES.contains(type.getSqlTypeName());
boolean isCharacter = SqlTypeName.CHAR_TYPES.contains(type.getSqlTypeName());
boolean isDateTime = SqlTypeName.DATETIME_TYPES.contains(type.getSqlTypeName());
return new Object[]{
EMPTY_CATALOG, // TABLE_CATALOG
schemaName, // TABLE_SCHEMA
tableName, // TABLE_NAME
field.getName(), // COLUMN_NAME
String.valueOf(field.getIndex()), // ORDINAL_POSITION
"", // COLUMN_DEFAULT
type.isNullable() ? "YES" : "NO", // IS_NULLABLE
type.getSqlTypeName().toString(), // DATA_TYPE
null, // CHARACTER_MAXIMUM_LENGTH
null, // CHARACTER_OCTET_LENGTH
isNumeric ? String.valueOf(type.getPrecision()) : null, // NUMERIC_PRECISION
isNumeric ? "10" : null, // NUMERIC_PRECISION_RADIX
isNumeric ? String.valueOf(type.getScale()) : null, // NUMERIC_SCALE
isDateTime ? String.valueOf(type.getPrecision()) : null, // DATETIME_PRECISION
isCharacter ? type.getCharset().name() : null, // CHARACTER_SET_NAME
isCharacter ? type.getCollation().getCollationName() : null, // COLLATION_NAME
type.getSqlTypeName().getJdbcOrdinal() // JDBC_TYPE (Druid extension)
};
}
}
);
}
}
/**
* Return a view macro that may or may not be defined in a certain schema. If it's not defined, returns null.
*
* @param schemaPlus schema
* @param functionName function name
*
* @return view, or null
*/
@Nullable
private static TableMacro getView(final SchemaPlus schemaPlus, final String functionName)
{
// Look for a zero-arg function that is also a TableMacro. The returned value
// is never null so we don't need to check for that.
final Collection<org.apache.calcite.schema.Function> functions =
schemaPlus.getFunctions(functionName);
for (org.apache.calcite.schema.Function function : functions) {
if (function.getParameters().isEmpty() && function instanceof TableMacro) {
return (TableMacro) function;
}
}
return null;
} }
} }

View File

@ -0,0 +1,72 @@
/*
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Metamarkets 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 io.druid.sql.calcite.view;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import io.druid.sql.calcite.planner.DruidPlanner;
import io.druid.sql.calcite.planner.PlannerFactory;
import io.druid.sql.calcite.schema.DruidSchema;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeImpl;
import org.apache.calcite.schema.FunctionParameter;
import org.apache.calcite.schema.TableMacro;
import org.apache.calcite.schema.TranslatableTable;
import org.apache.calcite.schema.impl.ViewTable;
import java.util.List;
public class DruidViewMacro implements TableMacro
{
private final PlannerFactory plannerFactory;
private final String viewSql;
public DruidViewMacro(final PlannerFactory plannerFactory, final String viewSql)
{
this.plannerFactory = plannerFactory;
this.viewSql = viewSql;
}
@Override
public TranslatableTable apply(final List<Object> arguments)
{
final RelDataType rowType;
try (final DruidPlanner planner = plannerFactory.createPlanner(null)) {
rowType = planner.plan(viewSql).rowType();
}
catch (Exception e) {
throw Throwables.propagate(e);
}
return new ViewTable(
null,
RelDataTypeImpl.proto(rowType),
viewSql,
ImmutableList.of(DruidSchema.NAME),
null
);
}
@Override
public List<FunctionParameter> getParameters()
{
return ImmutableList.of();
}
}

View File

@ -0,0 +1,74 @@
/*
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Metamarkets 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 io.druid.sql.calcite.view;
import com.google.inject.Inject;
import io.druid.java.util.common.ISE;
import io.druid.sql.calcite.planner.PlannerFactory;
import org.apache.calcite.schema.TableMacro;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* View manager that stores all views in-process. Not meant for serious usage, since views are not saved nor
* are they shared across processes.
*/
public class InProcessViewManager implements ViewManager
{
private final ConcurrentMap<String, DruidViewMacro> views;
@Inject
public InProcessViewManager()
{
this.views = new ConcurrentHashMap<>();
}
public void createView(final PlannerFactory plannerFactory, final String viewName, final String viewSql)
{
final TableMacro oldValue = views.putIfAbsent(viewName, new DruidViewMacro(plannerFactory, viewSql));
if (oldValue != null) {
throw new ISE("View[%s] already exists", viewName);
}
}
public void alterView(final PlannerFactory plannerFactory, final String viewName, final String viewSql)
{
final TableMacro oldValue = views.replace(viewName, new DruidViewMacro(plannerFactory, viewSql));
if (oldValue != null) {
throw new ISE("View[%s] does not exist", viewName);
}
}
public void dropView(final String viewName)
{
final TableMacro oldValue = views.remove(viewName);
if (oldValue == null) {
throw new ISE("View[%s] does not exist", viewName);
}
}
@Override
public Map<String, DruidViewMacro> getViews()
{
return views;
}
}

View File

@ -0,0 +1,55 @@
/*
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Metamarkets 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 io.druid.sql.calcite.view;
import com.google.common.collect.ImmutableMap;
import io.druid.sql.calcite.planner.PlannerFactory;
import java.util.Map;
/**
* View manager that does not support views.
*/
public class NoopViewManager implements ViewManager
{
@Override
public void createView(final PlannerFactory plannerFactory, final String viewName, final String viewSql)
{
throw new UnsupportedOperationException("Noop view manager cannot do anything");
}
@Override
public void alterView(final PlannerFactory plannerFactory, final String viewName, final String viewSql)
{
throw new UnsupportedOperationException("Noop view manager cannot do anything");
}
@Override
public void dropView(final String viewName)
{
throw new UnsupportedOperationException("Noop view manager cannot do anything");
}
@Override
public Map<String, DruidViewMacro> getViews()
{
return ImmutableMap.of();
}
}

View File

@ -0,0 +1,39 @@
/*
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Metamarkets 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 io.druid.sql.calcite.view;
import io.druid.sql.calcite.planner.PlannerFactory;
import java.util.Map;
/**
* View managers allow {@link io.druid.sql.calcite.schema.DruidSchema} to support views. They must be
* thread-safe.
*/
public interface ViewManager
{
void createView(final PlannerFactory plannerFactory, final String viewName, final String viewSql);
void alterView(final PlannerFactory plannerFactory, final String viewName, final String viewSql);
void dropView(final String viewName);
Map<String, DruidViewMacro> getViews();
}

View File

@ -24,7 +24,7 @@ import com.google.common.collect.ImmutableList;
import com.google.inject.Binder; import com.google.inject.Binder;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Module; import com.google.inject.Module;
import com.google.inject.Provides; import com.google.inject.Provider;
import io.druid.guice.Jerseys; import io.druid.guice.Jerseys;
import io.druid.guice.JsonConfigProvider; import io.druid.guice.JsonConfigProvider;
import io.druid.guice.LazySingleton; import io.druid.guice.LazySingleton;
@ -45,6 +45,8 @@ import io.druid.sql.calcite.expression.SubstringExtractionOperator;
import io.druid.sql.calcite.planner.Calcites; import io.druid.sql.calcite.planner.Calcites;
import io.druid.sql.calcite.planner.PlannerConfig; import io.druid.sql.calcite.planner.PlannerConfig;
import io.druid.sql.calcite.schema.DruidSchema; import io.druid.sql.calcite.schema.DruidSchema;
import io.druid.sql.calcite.view.NoopViewManager;
import io.druid.sql.calcite.view.ViewManager;
import io.druid.sql.http.SqlResource; import io.druid.sql.http.SqlResource;
import org.apache.calcite.schema.SchemaPlus; import org.apache.calcite.schema.SchemaPlus;
@ -85,6 +87,8 @@ public class SqlModule implements Module
JsonConfigProvider.bind(binder, "druid.sql.planner", PlannerConfig.class); JsonConfigProvider.bind(binder, "druid.sql.planner", PlannerConfig.class);
JsonConfigProvider.bind(binder, "druid.sql.avatica", AvaticaServerConfig.class); JsonConfigProvider.bind(binder, "druid.sql.avatica", AvaticaServerConfig.class);
LifecycleModule.register(binder, DruidSchema.class); LifecycleModule.register(binder, DruidSchema.class);
binder.bind(ViewManager.class).to(NoopViewManager.class);
binder.bind(SchemaPlus.class).toProvider(SchemaPlusProvider.class);
for (Class<? extends SqlAggregator> clazz : DEFAULT_AGGREGATOR_CLASSES) { for (Class<? extends SqlAggregator> clazz : DEFAULT_AGGREGATOR_CLASSES) {
SqlBindings.addAggregator(binder, clazz); SqlBindings.addAggregator(binder, clazz);
@ -106,13 +110,15 @@ public class SqlModule implements Module
} }
} }
@Provides public static class SchemaPlusProvider implements Provider<SchemaPlus>
public SchemaPlus createRootSchema(final DruidSchema druidSchema)
{ {
if (isEnabled()) { @Inject
private DruidSchema druidSchema;
@Override
public SchemaPlus get()
{
return Calcites.createRootSchema(druidSchema); return Calcites.createRootSchema(druidSchema);
} else {
throw new IllegalStateException("Cannot provide SchemaPlus when SQL is disabled.");
} }
} }

View File

@ -272,13 +272,13 @@ public class DruidAvaticaHandlerTest
Assert.assertEquals( Assert.assertEquals(
ImmutableList.of( ImmutableList.of(
ROW( ROW(
Pair.of("TABLE_CAT", null), Pair.of("TABLE_CAT", ""),
Pair.of("TABLE_NAME", "foo"), Pair.of("TABLE_NAME", "foo"),
Pair.of("TABLE_SCHEM", "druid"), Pair.of("TABLE_SCHEM", "druid"),
Pair.of("TABLE_TYPE", "TABLE") Pair.of("TABLE_TYPE", "TABLE")
), ),
ROW( ROW(
Pair.of("TABLE_CAT", null), Pair.of("TABLE_CAT", ""),
Pair.of("TABLE_NAME", "foo2"), Pair.of("TABLE_NAME", "foo2"),
Pair.of("TABLE_SCHEM", "druid"), Pair.of("TABLE_SCHEM", "druid"),
Pair.of("TABLE_TYPE", "TABLE") Pair.of("TABLE_TYPE", "TABLE")

View File

@ -92,6 +92,7 @@ import io.druid.sql.calcite.schema.DruidSchema;
import io.druid.sql.calcite.util.CalciteTests; import io.druid.sql.calcite.util.CalciteTests;
import io.druid.sql.calcite.util.QueryLogHook; import io.druid.sql.calcite.util.QueryLogHook;
import io.druid.sql.calcite.util.SpecificSegmentsQuerySegmentWalker; import io.druid.sql.calcite.util.SpecificSegmentsQuerySegmentWalker;
import io.druid.sql.calcite.view.InProcessViewManager;
import org.apache.calcite.plan.RelOptPlanner; import org.apache.calcite.plan.RelOptPlanner;
import org.apache.calcite.schema.SchemaPlus; import org.apache.calcite.schema.SchemaPlus;
import org.joda.time.DateTime; import org.joda.time.DateTime;
@ -264,11 +265,13 @@ public class CalciteQueryTest
testQuery( testQuery(
"SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE\n" "SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE\n"
+ "FROM INFORMATION_SCHEMA.TABLES\n" + "FROM INFORMATION_SCHEMA.TABLES\n"
+ "WHERE TABLE_TYPE IN ('SYSTEM_TABLE', 'TABLE')", + "WHERE TABLE_TYPE IN ('SYSTEM_TABLE', 'TABLE', 'VIEW')",
ImmutableList.<Query>of(), ImmutableList.<Query>of(),
ImmutableList.of( ImmutableList.of(
new Object[]{"druid", "foo", "TABLE"}, new Object[]{"druid", "foo", "TABLE"},
new Object[]{"druid", "foo2", "TABLE"}, new Object[]{"druid", "foo2", "TABLE"},
new Object[]{"druid", "aview", "VIEW"},
new Object[]{"druid", "bview", "VIEW"},
new Object[]{"INFORMATION_SCHEMA", "COLUMNS", "SYSTEM_TABLE"}, new Object[]{"INFORMATION_SCHEMA", "COLUMNS", "SYSTEM_TABLE"},
new Object[]{"INFORMATION_SCHEMA", "SCHEMATA", "SYSTEM_TABLE"}, new Object[]{"INFORMATION_SCHEMA", "SCHEMATA", "SYSTEM_TABLE"},
new Object[]{"INFORMATION_SCHEMA", "TABLES", "SYSTEM_TABLE"} new Object[]{"INFORMATION_SCHEMA", "TABLES", "SYSTEM_TABLE"}
@ -277,7 +280,7 @@ public class CalciteQueryTest
} }
@Test @Test
public void testInformationSchemaColumns() throws Exception public void testInformationSchemaColumnsOnTable() throws Exception
{ {
testQuery( testQuery(
"SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE\n" "SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE\n"
@ -295,6 +298,20 @@ public class CalciteQueryTest
); );
} }
@Test
public void testInformationSchemaColumnsOnView() throws Exception
{
testQuery(
"SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE\n"
+ "FROM INFORMATION_SCHEMA.COLUMNS\n"
+ "WHERE TABLE_SCHEMA = 'druid' AND TABLE_NAME = 'aview'",
ImmutableList.<Query>of(),
ImmutableList.of(
new Object[]{"dim1_firstchar", "VARCHAR", "NO"}
)
);
}
@Test @Test
public void testExplainInformationSchemaColumns() throws Exception public void testExplainInformationSchemaColumns() throws Exception
{ {
@ -970,6 +987,49 @@ public class CalciteQueryTest
); );
} }
@Test
public void testCountStarOnView() throws Exception
{
testQuery(
"SELECT COUNT(*) FROM druid.aview WHERE dim1_firstchar <> 'z'",
ImmutableList.<Query>of(
Druids.newTimeseriesQueryBuilder()
.dataSource(CalciteTests.DATASOURCE1)
.intervals(QSS(Filtration.eternity()))
.filters(AND(
SELECTOR("dim2", "a", null),
NOT(SELECTOR("dim1", "z", new SubstringDimExtractionFn(0, 1)))
))
.granularity(Granularities.ALL)
.aggregators(AGGS(new CountAggregatorFactory("a0")))
.context(TIMESERIES_CONTEXT_DEFAULT)
.build()
),
ImmutableList.of(
new Object[]{2L}
)
);
}
@Test
public void testExplainCountStarOnView() throws Exception
{
testQuery(
"EXPLAIN PLAN FOR SELECT COUNT(*) FROM aview WHERE dim1_firstchar <> 'z'",
ImmutableList.<Query>of(),
ImmutableList.of(
new Object[]{
"DruidQueryRel(dataSource=[foo], "
+ "filter=[(dim2 = a && !substring(0, 1)(dim1) = z)], "
+ "dimensions=[[]], "
+ "aggregations=[[Aggregation{aggregatorFactories=[CountAggregatorFactory{name='a0'}], "
+ "postAggregator=null, "
+ "finalizingPostAggregatorFactory=null}]])\n"
}
)
);
}
@Test @Test
public void testCountStarWithLikeFilter() throws Exception public void testCountStarWithLikeFilter() throws Exception
{ {
@ -2810,6 +2870,50 @@ public class CalciteQueryTest
); );
} }
@Test
public void testFilterOnCurrentTimestampOnView() throws Exception
{
testQuery(
"SELECT * FROM bview",
ImmutableList.<Query>of(
Druids.newTimeseriesQueryBuilder()
.dataSource(CalciteTests.DATASOURCE1)
.intervals(QSS(new Interval("2000-01-02/2002")))
.granularity(Granularities.ALL)
.aggregators(AGGS(new CountAggregatorFactory("a0")))
.context(TIMESERIES_CONTEXT_DEFAULT)
.build()
),
ImmutableList.of(
new Object[]{5L}
)
);
}
@Test
public void testFilterOnCurrentTimestampLosAngelesOnView() throws Exception
{
// Tests that query context still applies to view SQL; note the result is different from
// "testFilterOnCurrentTimestampOnView" above.
testQuery(
PlannerContext.create(PLANNER_CONFIG_DEFAULT, QUERY_CONTEXT_LOS_ANGELES),
"SELECT * FROM bview",
ImmutableList.<Query>of(
Druids.newTimeseriesQueryBuilder()
.dataSource(CalciteTests.DATASOURCE1)
.intervals(QSS(new Interval("2000-01-02T08Z/2002-01-01T08Z")))
.granularity(Granularities.ALL)
.aggregators(AGGS(new CountAggregatorFactory("a0")))
.context(TIMESERIES_CONTEXT_LOS_ANGELES)
.build()
),
ImmutableList.of(
new Object[]{4L}
)
);
}
@Test @Test
public void testFilterOnNotTimeFloor() throws Exception public void testFilterOnNotTimeFloor() throws Exception
{ {
@ -3892,10 +3996,26 @@ public class CalciteQueryTest
) throws Exception ) throws Exception
{ {
final PlannerConfig plannerConfig = plannerContext.getPlannerConfig(); final PlannerConfig plannerConfig = plannerContext.getPlannerConfig();
final DruidSchema druidSchema = CalciteTests.createMockSchema(walker, plannerConfig); final InProcessViewManager viewManager = new InProcessViewManager();
final DruidSchema druidSchema = CalciteTests.createMockSchema(walker, plannerConfig, viewManager);
final SchemaPlus rootSchema = Calcites.createRootSchema(druidSchema); final SchemaPlus rootSchema = Calcites.createRootSchema(druidSchema);
final DruidOperatorTable operatorTable = CalciteTests.createOperatorTable(); final DruidOperatorTable operatorTable = CalciteTests.createOperatorTable();
final PlannerFactory plannerFactory = new PlannerFactory(rootSchema, walker, operatorTable, plannerConfig); final PlannerFactory plannerFactory = new PlannerFactory(rootSchema, walker, operatorTable, plannerConfig);
viewManager.createView(
plannerFactory,
"aview",
"SELECT SUBSTRING(dim1, 1, 1) AS dim1_firstchar FROM foo WHERE dim2 = 'a'"
);
viewManager.createView(
plannerFactory,
"bview",
"SELECT COUNT(*) FROM druid.foo\n"
+ "WHERE __time >= CURRENT_TIMESTAMP + INTERVAL '1' DAY AND __time < TIMESTAMP '2002-01-01 00:00:00'"
);
try (DruidPlanner planner = plannerFactory.createPlanner(plannerContext.getQueryContext())) { try (DruidPlanner planner = plannerFactory.createPlanner(plannerContext.getQueryContext())) {
final PlannerResult plan = planner.plan(sql); final PlannerResult plan = planner.plan(sql);
return Sequences.toList(plan.run(), Lists.<Object[]>newArrayList()); return Sequences.toList(plan.run(), Lists.<Object[]>newArrayList());

View File

@ -38,6 +38,7 @@ import io.druid.sql.calcite.table.DruidTable;
import io.druid.sql.calcite.util.CalciteTests; import io.druid.sql.calcite.util.CalciteTests;
import io.druid.sql.calcite.util.SpecificSegmentsQuerySegmentWalker; import io.druid.sql.calcite.util.SpecificSegmentsQuerySegmentWalker;
import io.druid.sql.calcite.util.TestServerInventoryView; import io.druid.sql.calcite.util.TestServerInventoryView;
import io.druid.sql.calcite.view.NoopViewManager;
import io.druid.timeline.DataSegment; import io.druid.timeline.DataSegment;
import io.druid.timeline.partition.LinearShardSpec; import io.druid.timeline.partition.LinearShardSpec;
import org.apache.calcite.jdbc.JavaTypeFactoryImpl; import org.apache.calcite.jdbc.JavaTypeFactoryImpl;
@ -148,7 +149,8 @@ public class DruidSchemaTest
schema = new DruidSchema( schema = new DruidSchema(
walker, walker,
new TestServerInventoryView(walker.getSegments()), new TestServerInventoryView(walker.getSegments()),
PLANNER_CONFIG_DEFAULT PLANNER_CONFIG_DEFAULT,
new NoopViewManager()
); );
schema.start(); schema.start();

View File

@ -80,6 +80,8 @@ import io.druid.sql.calcite.expression.SqlExtractionOperator;
import io.druid.sql.calcite.planner.DruidOperatorTable; import io.druid.sql.calcite.planner.DruidOperatorTable;
import io.druid.sql.calcite.planner.PlannerConfig; import io.druid.sql.calcite.planner.PlannerConfig;
import io.druid.sql.calcite.schema.DruidSchema; import io.druid.sql.calcite.schema.DruidSchema;
import io.druid.sql.calcite.view.NoopViewManager;
import io.druid.sql.calcite.view.ViewManager;
import io.druid.sql.guice.SqlModule; import io.druid.sql.guice.SqlModule;
import io.druid.timeline.DataSegment; import io.druid.timeline.DataSegment;
import io.druid.timeline.partition.LinearShardSpec; import io.druid.timeline.partition.LinearShardSpec;
@ -360,11 +362,21 @@ public class CalciteTests
final SpecificSegmentsQuerySegmentWalker walker, final SpecificSegmentsQuerySegmentWalker walker,
final PlannerConfig plannerConfig final PlannerConfig plannerConfig
) )
{
return createMockSchema(walker, plannerConfig, new NoopViewManager());
}
public static DruidSchema createMockSchema(
final SpecificSegmentsQuerySegmentWalker walker,
final PlannerConfig plannerConfig,
final ViewManager viewManager
)
{ {
final DruidSchema schema = new DruidSchema( final DruidSchema schema = new DruidSchema(
walker, walker,
new TestServerInventoryView(walker.getSegments()), new TestServerInventoryView(walker.getSegments()),
plannerConfig plannerConfig,
viewManager
); );
schema.start(); schema.start();