/* * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor * license agreements. See the NOTICE file distributed with this work for * additional information regarding copyright ownership. Crate 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. * * However, if you have executed another commercial license agreement * with Crate these terms will supersede the license and you may use the * software solely pursuant to the terms of the relevant commercial agreement. */ package io.crate.analyze; import com.google.common.collect.ImmutableList; import io.crate.action.sql.SessionContext; import io.crate.analyze.expressions.ExpressionToStringVisitor; import io.crate.data.Row; import io.crate.metadata.*; import io.crate.metadata.information.InformationSchemaInfo; import io.crate.metadata.pg_catalog.PgCatalogSchemaInfo; import io.crate.metadata.sys.SysSchemaInfo; import io.crate.sql.tree.*; import org.elasticsearch.cluster.metadata.IndexMetaData; import java.util.Collection; import java.util.Collections; import java.util.Locale; public class CreateTableStatementAnalyzer extends DefaultTraversalVisitor<CreateTableAnalyzedStatement, CreateTableStatementAnalyzer.Context> { private static final String CLUSTERED_BY_IN_PARTITIONED_ERROR = "Cannot use CLUSTERED BY column in PARTITIONED BY clause"; private final Schemas schemas; private final FulltextAnalyzerResolver fulltextAnalyzerResolver; private final Functions functions; private final NumberOfShards numberOfShards; static final Collection<String> READ_ONLY_SCHEMAS = ImmutableList.of( SysSchemaInfo.NAME, InformationSchemaInfo.NAME, PgCatalogSchemaInfo.NAME ); static class Context { private final CreateTableAnalyzedStatement statement; private final ParameterContext parameterContext; public Context(CreateTableAnalyzedStatement statement, ParameterContext parameterContext) { this.statement = statement; this.parameterContext = parameterContext; } } public CreateTableStatementAnalyzer(Schemas schemas, FulltextAnalyzerResolver fulltextAnalyzerResolver, Functions functions, NumberOfShards numberOfShards) { this.schemas = schemas; this.fulltextAnalyzerResolver = fulltextAnalyzerResolver; this.functions = functions; this.numberOfShards = numberOfShards; } @Override protected CreateTableAnalyzedStatement visitNode(Node node, Context context) { throw new RuntimeException( String.format(Locale.ENGLISH, "Encountered node %s but expected a CreateTable node", node)); } public CreateTableAnalyzedStatement analyze(CreateTable createTable, ParameterContext parameterContext, SessionContext sessionContext) { CreateTableAnalyzedStatement statement = new CreateTableAnalyzedStatement(); Row parameters = parameterContext.parameters(); TableIdent tableIdent = getTableIdent(createTable, sessionContext); statement.table(tableIdent, createTable.ifNotExists(), schemas); // apply default in case it is not specified in the genericProperties, // if it is it will get overwritten afterwards. TablePropertiesAnalyzer.analyze( statement.tableParameter(), new TableParameterInfo(), createTable.properties(), parameters, true ); AnalyzedTableElements tableElements = TableElementsAnalyzer.analyze( createTable.tableElements(), parameters, fulltextAnalyzerResolver, null); // validate table elements tableElements.finalizeAndValidate( tableIdent, Collections.emptyList(), functions, parameterContext, sessionContext); // update table settings statement.tableParameter().settingsBuilder().put(tableElements.settings()); statement.tableParameter().settingsBuilder().put( IndexMetaData.SETTING_NUMBER_OF_SHARDS, numberOfShards.defaultNumberOfShards()); Context context = new Context(statement, parameterContext); statement.analyzedTableElements(tableElements); for (CrateTableOption option : createTable.crateTableOptions()) { process(option, context); } return statement; } private TableIdent getTableIdent(CreateTable node, SessionContext sessionContext) { TableIdent tableIdent = TableIdent.of(node.name(), sessionContext.defaultSchema()); if (READ_ONLY_SCHEMAS.contains(tableIdent.schema())) { throw new IllegalArgumentException( String.format(Locale.ENGLISH, "Cannot create table in read-only schema '%s'", tableIdent.schema()) ); } return tableIdent; } @Override public CreateTableAnalyzedStatement visitClusteredBy(ClusteredBy clusteredBy, Context context) { if (clusteredBy.column().isPresent()) { ColumnIdent routingColumn = ColumnIdent.fromPath( ExpressionToStringVisitor.convert(clusteredBy.column().get(), context.parameterContext.parameters())); for (AnalyzedColumnDefinition column : context.statement.analyzedTableElements().partitionedByColumns) { if (column.ident().equals(routingColumn)) { throw new IllegalArgumentException(CLUSTERED_BY_IN_PARTITIONED_ERROR); } } if (!context.statement.hasColumnDefinition(routingColumn)) { throw new IllegalArgumentException( String.format(Locale.ENGLISH, "Invalid or non-existent routing column \"%s\"", routingColumn)); } if (context.statement.primaryKeys().size() > 0 && !context.statement.primaryKeys().contains(routingColumn.fqn())) { throw new IllegalArgumentException("Clustered by column must be part of primary keys"); } context.statement.routing(routingColumn); } context.statement.tableParameter().settingsBuilder().put( IndexMetaData.SETTING_NUMBER_OF_SHARDS, numberOfShards.fromClusteredByClause(clusteredBy, context.parameterContext.parameters()) ); return context.statement; } @Override public CreateTableAnalyzedStatement visitPartitionedBy(PartitionedBy node, Context context) { for (Expression partitionByColumn : node.columns()) { ColumnIdent partitionedByIdent = ColumnIdent.fromPath( ExpressionToStringVisitor.convert(partitionByColumn, context.parameterContext.parameters())); context.statement.analyzedTableElements().changeToPartitionedByColumn(partitionedByIdent, false); ColumnIdent routing = context.statement.routing(); if (routing != null && routing.equals(partitionedByIdent)) { throw new IllegalArgumentException(CLUSTERED_BY_IN_PARTITIONED_ERROR); } } return null; } }