/* * 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 io.crate.analyze.expressions.ExpressionToStringVisitor; import io.crate.data.Row; import io.crate.metadata.ColumnIdent; import io.crate.metadata.FulltextAnalyzerResolver; import io.crate.metadata.Reference; import io.crate.metadata.table.TableInfo; import io.crate.sql.tree.*; import io.crate.types.DataTypes; import org.elasticsearch.common.settings.Settings; import javax.annotation.Nullable; import java.util.Arrays; import java.util.List; import java.util.Locale; public class TableElementsAnalyzer { private final static InnerTableElementsAnalyzer ANALYZER = new InnerTableElementsAnalyzer(); public static AnalyzedTableElements analyze(List<TableElement> tableElements, Row parameters, FulltextAnalyzerResolver fulltextAnalyzerResolver, @Nullable TableInfo tableInfo) { AnalyzedTableElements analyzedTableElements = new AnalyzedTableElements(); for (TableElement tableElement : tableElements) { ColumnDefinitionContext ctx = new ColumnDefinitionContext( null, parameters, fulltextAnalyzerResolver, analyzedTableElements, tableInfo); ANALYZER.process(tableElement, ctx); if (ctx.analyzedColumnDefinition.ident() != null) { analyzedTableElements.add(ctx.analyzedColumnDefinition); } } return analyzedTableElements; } public static AnalyzedTableElements analyze(TableElement tableElement, Row parameters, FulltextAnalyzerResolver fulltextAnalyzerResolver, TableInfo tableInfo) { return analyze(Arrays.asList(tableElement), parameters, fulltextAnalyzerResolver, tableInfo); } private static class ColumnDefinitionContext { private final Row parameters; final FulltextAnalyzerResolver fulltextAnalyzerResolver; AnalyzedColumnDefinition analyzedColumnDefinition; final AnalyzedTableElements analyzedTableElements; final TableInfo tableInfo; ColumnDefinitionContext(@Nullable AnalyzedColumnDefinition parent, Row parameters, FulltextAnalyzerResolver fulltextAnalyzerResolver, AnalyzedTableElements analyzedTableElements, @Nullable TableInfo tableInfo) { this.analyzedColumnDefinition = new AnalyzedColumnDefinition(parent); this.parameters = parameters; this.fulltextAnalyzerResolver = fulltextAnalyzerResolver; this.analyzedTableElements = analyzedTableElements; this.tableInfo = tableInfo; } } private static class InnerTableElementsAnalyzer extends DefaultTraversalVisitor<Void, ColumnDefinitionContext> { @Override public Void visitColumnDefinition(ColumnDefinition node, ColumnDefinitionContext context) { context.analyzedColumnDefinition.name(node.ident()); for (ColumnConstraint columnConstraint : node.constraints()) { process(columnConstraint, context); } if (node.type() != null) { process(node.type(), context); } if (node.expression() != null) { context.analyzedColumnDefinition.generatedExpression(node.expression()); } return null; } @Override public Void visitAddColumnDefinition(AddColumnDefinition node, ColumnDefinitionContext context) { String columnName = ExpressionToStringVisitor.convert(node.name(), context.parameters); ColumnIdent ident = ColumnIdent.fromPath(columnName); context.analyzedColumnDefinition.name(ident.name()); // nested columns can only be added using alter table so no other columns exist. assert context.analyzedTableElements.columns().size() == 0 : "context.analyzedTableElements.columns().size() must be 0"; AnalyzedColumnDefinition root = context.analyzedColumnDefinition; if (!ident.path().isEmpty()) { AnalyzedColumnDefinition parent = context.analyzedColumnDefinition; AnalyzedColumnDefinition leaf = parent; for (String name : ident.path()) { parent.dataType(DataTypes.OBJECT.getName()); // Check if parent is already defined. // If it is an array, set the collection type to array, or if it's an object keep the object column // policy. if (context.tableInfo != null) { Reference parentRef = context.tableInfo.getReference(parent.ident()); if (parentRef != null) { if (parentRef.valueType().equals(DataTypes.OBJECT_ARRAY)) { parent.collectionType("array"); } else { parent.objectType(String.valueOf(parentRef.columnPolicy().mappingValue())); } } } parent.markAsParentColumn(); leaf = new AnalyzedColumnDefinition(parent); leaf.name(name); parent.addChild(leaf); parent = leaf; } context.analyzedColumnDefinition = leaf; } for (ColumnConstraint columnConstraint : node.constraints()) { process(columnConstraint, context); } if (node.type() != null) { process(node.type(), context); } if (node.generatedExpression() != null) { context.analyzedColumnDefinition.generatedExpression(node.generatedExpression()); } context.analyzedColumnDefinition = root; return null; } @Override public Void visitColumnType(ColumnType node, ColumnDefinitionContext context) { context.analyzedColumnDefinition.dataType(node.name()); return null; } @Override public Void visitObjectColumnType(ObjectColumnType node, ColumnDefinitionContext context) { context.analyzedColumnDefinition.dataType(node.name()); switch (node.objectType().orElse("dynamic").toLowerCase(Locale.ENGLISH)) { case "dynamic": context.analyzedColumnDefinition.objectType("true"); break; case "strict": context.analyzedColumnDefinition.objectType("strict"); break; case "ignored": context.analyzedColumnDefinition.objectType("false"); break; } for (ColumnDefinition columnDefinition : node.nestedColumns()) { ColumnDefinitionContext childContext = new ColumnDefinitionContext( context.analyzedColumnDefinition, context.parameters, context.fulltextAnalyzerResolver, context.analyzedTableElements, context.tableInfo ); process(columnDefinition, childContext); context.analyzedColumnDefinition.addChild(childContext.analyzedColumnDefinition); } return null; } @Override public Void visitCollectionColumnType(CollectionColumnType node, ColumnDefinitionContext context) { if (node.type() == ColumnType.Type.SET) { throw new UnsupportedOperationException("the SET dataType is currently not supported"); } context.analyzedColumnDefinition.collectionType("array"); if (node.innerType().type() != ColumnType.Type.PRIMITIVE) { throw new UnsupportedOperationException("Nesting ARRAY or SET types is not supported"); } process(node.innerType(), context); return null; } @Override public Void visitPrimaryKeyColumnConstraint(PrimaryKeyColumnConstraint node, ColumnDefinitionContext context) { context.analyzedColumnDefinition.setPrimaryKeyConstraint(); return null; } @Override public Void visitPrimaryKeyConstraint(PrimaryKeyConstraint node, ColumnDefinitionContext context) { for (Expression expression : node.columns()) { context.analyzedTableElements.addPrimaryKey( ExpressionToStringVisitor.convert(expression, context.parameters)); } return null; } @Override public Void visitIndexColumnConstraint(IndexColumnConstraint node, ColumnDefinitionContext context) { if (node.indexMethod().equals("fulltext")) { setAnalyzer(node.properties(), context, node.indexMethod()); } else if (node.indexMethod().equalsIgnoreCase("plain")) { context.analyzedColumnDefinition.indexConstraint(Reference.IndexType.NOT_ANALYZED); } else if (node.indexMethod().equalsIgnoreCase("OFF")) { context.analyzedColumnDefinition.indexConstraint(Reference.IndexType.NO); } else if (node.indexMethod().equals("quadtree") || node.indexMethod().equals("geohash")) { setGeoType(node.properties(), context, node.indexMethod()); } else { throw new IllegalArgumentException( String.format(Locale.ENGLISH, "Invalid index method \"%s\"", node.indexMethod())); } return null; } @Override public Void visitNotNullColumnConstraint(NotNullColumnConstraint node, ColumnDefinitionContext context) { context.analyzedColumnDefinition.setNotNullConstraint(); return null; } @Override public Void visitIndexDefinition(IndexDefinition node, ColumnDefinitionContext context) { context.analyzedColumnDefinition.setAsIndexColumn(); context.analyzedColumnDefinition.dataType("text"); context.analyzedColumnDefinition.name(node.ident()); setAnalyzer(node.properties(), context, node.method()); for (Expression expression : node.columns()) { String expressionName = ExpressionToStringVisitor.convert(expression, context.parameters); context.analyzedTableElements.addCopyTo(expressionName, node.ident()); } return null; } private void setGeoType(GenericProperties properties, ColumnDefinitionContext context, String indexMethod) { context.analyzedColumnDefinition.geoTree(indexMethod); Settings geoSettings = GenericPropertiesConverter.genericPropertiesToSettings(properties, context.parameters); context.analyzedColumnDefinition.geoSettings(geoSettings); } private void setAnalyzer(GenericProperties properties, ColumnDefinitionContext context, String indexMethod) { context.analyzedColumnDefinition.indexConstraint(Reference.IndexType.ANALYZED); Expression analyzerExpression = properties.get("analyzer"); if (analyzerExpression == null) { if (indexMethod.equals("plain")) { context.analyzedColumnDefinition.analyzer("keyword"); } else { context.analyzedColumnDefinition.analyzer("standard"); } return; } if (analyzerExpression instanceof ArrayLiteral) { throw new IllegalArgumentException("array literal not allowed for the analyzer property"); } String analyzerName = ExpressionToStringVisitor.convert(analyzerExpression, context.parameters); if (context.fulltextAnalyzerResolver.hasCustomAnalyzer(analyzerName)) { Settings settings = context.fulltextAnalyzerResolver.resolveFullCustomAnalyzerSettings(analyzerName); context.analyzedColumnDefinition.analyzerSettings(settings); } context.analyzedColumnDefinition.analyzer(analyzerName); } } }