/* * 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.base.Predicate; import com.google.common.collect.Iterables; import io.crate.analyze.expressions.ExpressionAnalysisContext; import io.crate.analyze.expressions.ExpressionAnalyzer; import io.crate.analyze.expressions.ValueNormalizer; import io.crate.analyze.relations.*; import io.crate.analyze.symbol.Literal; import io.crate.analyze.symbol.Reference; import io.crate.analyze.symbol.Symbol; import io.crate.analyze.symbol.Symbols; import io.crate.analyze.where.WhereClauseAnalyzer; import io.crate.core.collections.StringObjectMaps; import io.crate.exceptions.ColumnValidationException; import io.crate.exceptions.NoPermissionException; import io.crate.exceptions.UnsupportedFeatureException; import io.crate.metadata.ColumnIdent; import io.crate.metadata.GeneratedReferenceInfo; import io.crate.metadata.ReferenceInfo; import io.crate.metadata.RowGranularity; import io.crate.metadata.doc.DocSysColumns; import io.crate.metadata.doc.DocTableInfo; import io.crate.metadata.table.TableInfo; import io.crate.sql.tree.Assignment; import io.crate.sql.tree.DefaultTraversalVisitor; import io.crate.sql.tree.Node; import io.crate.sql.tree.Update; import io.crate.types.ArrayType; import io.crate.types.DataTypes; import org.elasticsearch.authentication.AuthResult; import org.elasticsearch.authentication.AuthService; import org.elasticsearch.cluster.metadata.PrivilegeType; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Singleton; import org.elasticsearch.rest.RestStatus; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; @Singleton public class UpdateStatementAnalyzer extends DefaultTraversalVisitor<AnalyzedStatement, Analysis> { public static final String VERSION_SEARCH_EX_MSG = "_version is not allowed in update queries without specifying a primary key"; private static final UnsupportedFeatureException VERSION_SEARCH_EX = new UnsupportedFeatureException( VERSION_SEARCH_EX_MSG); private static final Predicate<ReferenceInfo> IS_OBJECT_ARRAY = new Predicate<ReferenceInfo>() { @Override public boolean apply(@Nullable ReferenceInfo input) { return input != null && input.type().id() == ArrayType.ID && ((ArrayType)input.type()).innerType().equals(DataTypes.OBJECT); } }; private final AnalysisMetaData analysisMetaData; private final RelationAnalyzer relationAnalyzer; private final ValueNormalizer valueNormalizer; @Inject public UpdateStatementAnalyzer(AnalysisMetaData analysisMetaData, RelationAnalyzer relationAnalyzer) { this.analysisMetaData = analysisMetaData; this.relationAnalyzer = relationAnalyzer; this.valueNormalizer = new ValueNormalizer(analysisMetaData.schemas(), new EvaluatingNormalizer( analysisMetaData.functions(), RowGranularity.CLUSTER, analysisMetaData.referenceResolver())); } public AnalyzedStatement analyze(Node node, Analysis analysis) { analysis.expectsAffectedRows(true); return process(node, analysis); } @Override public AnalyzedStatement visitUpdate(Update node, Analysis analysis) { RelationAnalysisContext relationAnalysisContext = new RelationAnalysisContext( analysis.parameterContext(), analysisMetaData); AnalyzedRelation analyzedRelation = relationAnalyzer.analyze(node.relation(), relationAnalysisContext); if (Relations.isReadOnly(analyzedRelation)) { throw new UnsupportedOperationException(String.format(Locale.ENGLISH, "relation \"%s\" is read-only and cannot be updated", analyzedRelation)); } assert analyzedRelation instanceof DocTableRelation : "sourceRelation must be a DocTableRelation"; DocTableRelation tableRelation = ((DocTableRelation) analyzedRelation); // Add SQL Authentication // GaoPan 2016/06/16 AuthResult authResult = AuthService.sqlAuthenticate(analysis.parameterContext().getLoginUserContext(), tableRelation.tableInfo().ident().schema(), tableRelation.tableInfo().ident().name(), PrivilegeType.READ_WRITE); if (authResult.getStatus() != RestStatus.OK) { throw new NoPermissionException(authResult.getStatus().getStatus(), authResult.getMessage()); } FieldProvider columnFieldProvider = new NameFieldProvider(analyzedRelation); ExpressionAnalyzer columnExpressionAnalyzer = new ExpressionAnalyzer(analysisMetaData, analysis.parameterContext(), columnFieldProvider, tableRelation); columnExpressionAnalyzer.resolveWritableFields(true); assert Iterables.getOnlyElement(relationAnalysisContext.sources().values()) == tableRelation; FieldProvider fieldProvider = new FullQualifedNameFieldProvider(relationAnalysisContext.sources()); ExpressionAnalyzer expressionAnalyzer = new ExpressionAnalyzer(analysisMetaData, analysis.parameterContext(), fieldProvider, tableRelation); ExpressionAnalysisContext expressionAnalysisContext = new ExpressionAnalysisContext(); int numNested = 1; if (analysis.parameterContext().bulkParameters.length > 0) { numNested = analysis.parameterContext().bulkParameters.length; } WhereClauseAnalyzer whereClauseAnalyzer = new WhereClauseAnalyzer(analysisMetaData, tableRelation); List<UpdateAnalyzedStatement.NestedAnalyzedStatement> nestedAnalyzedStatements = new ArrayList<>(numNested); for (int i = 0; i < numNested; i++) { analysis.parameterContext().setBulkIdx(i); WhereClause whereClause = expressionAnalyzer.generateWhereClause(node.whereClause(), expressionAnalysisContext); whereClause = whereClauseAnalyzer.analyze(whereClause); if (!whereClause.docKeys().isPresent() && Symbols.containsColumn(whereClause.query(), DocSysColumns.VERSION)) { throw VERSION_SEARCH_EX; } UpdateAnalyzedStatement.NestedAnalyzedStatement nestedAnalyzedStatement = new UpdateAnalyzedStatement.NestedAnalyzedStatement(whereClause); for (Assignment assignment : node.assignements()) { analyzeAssignment( assignment, nestedAnalyzedStatement, tableRelation, expressionAnalyzer, columnExpressionAnalyzer, expressionAnalysisContext ); } nestedAnalyzedStatements.add(nestedAnalyzedStatement); } return new UpdateAnalyzedStatement(tableRelation, nestedAnalyzedStatements); } public void analyzeAssignment(Assignment node, UpdateAnalyzedStatement.NestedAnalyzedStatement nestedAnalyzedStatement, DocTableRelation tableRelation, ExpressionAnalyzer expressionAnalyzer, ExpressionAnalyzer columnExpressionAnalyzer, ExpressionAnalysisContext expressionAnalysisContext) { // unknown columns in strict objects handled in here Reference reference = (Reference) columnExpressionAnalyzer.normalize( columnExpressionAnalyzer.convert(node.columnName(), expressionAnalysisContext)); final ColumnIdent ident = reference.info().ident().columnIdent(); if (ident.name().startsWith("_")) { throw new IllegalArgumentException("Updating system columns is not allowed"); } if (hasMatchingParent(tableRelation.tableInfo(), reference.info(), IS_OBJECT_ARRAY)) { // cannot update fields of object arrays throw new IllegalArgumentException("Updating fields of object arrays is not supported"); } Symbol value = expressionAnalyzer.normalize( expressionAnalyzer.convert(node.expression(), expressionAnalysisContext)); try { value = valueNormalizer.normalizeInputForReference(value, reference); ensureUpdateIsAllowed(tableRelation.tableInfo(), ident, value); } catch (IllegalArgumentException | UnsupportedOperationException e) { throw new ColumnValidationException(ident.sqlFqn(), e); } nestedAnalyzedStatement.addAssignment(reference, value); } public static void ensureUpdateIsAllowed(DocTableInfo tableInfo, ColumnIdent column, Symbol value) { if (tableInfo.clusteredBy() != null) { ensureNotUpdated(column, value, tableInfo.clusteredBy(), "Updating a clustered-by column is not supported"); } for (ColumnIdent pkIdent : tableInfo.primaryKey()) { ensureNotUpdated(column, value, pkIdent, "Updating a primary key is not supported"); } List<GeneratedReferenceInfo> generatedReferenceInfos = tableInfo.generatedColumns(); for (ColumnIdent partitionIdent : tableInfo.partitionedBy()) { ensureNotUpdated(column, value, partitionIdent, "Updating a partitioned-by column is not supported"); int idx = generatedReferenceInfos.indexOf(tableInfo.getReferenceInfo(partitionIdent)); if (idx >= 0) { GeneratedReferenceInfo generatedReferenceInfo = generatedReferenceInfos.get(idx); for (ReferenceInfo referenceInfo : generatedReferenceInfo.referencedReferenceInfos()) { ensureNotUpdated(column, value, referenceInfo.ident().columnIdent(), "Updating a column which is referenced in a partitioned by generated column expression is not supported"); } } } } private static void ensureNotUpdated(ColumnIdent columnUpdated, Symbol newValue, ColumnIdent protectedColumnIdent, String errorMessage) { if (columnUpdated.equals(protectedColumnIdent)) { throw new UnsupportedOperationException(errorMessage); } if (protectedColumnIdent.isChildOf(columnUpdated)) { if (newValue.valueType().equals(DataTypes.OBJECT) && newValue.symbolType().isValueSymbol() && StringObjectMaps.fromMapByPath((Map) ((Literal) newValue).value(), protectedColumnIdent.path()) == null) { return; } throw new UnsupportedOperationException(errorMessage); } } private boolean hasMatchingParent(TableInfo tableInfo, ReferenceInfo info, Predicate<ReferenceInfo> parentMatchPredicate) { ColumnIdent parent = info.ident().columnIdent().getParent(); while (parent != null) { ReferenceInfo parentInfo = tableInfo.getReferenceInfo(parent); if (parentMatchPredicate.apply(parentInfo)) { return true; } parent = parent.getParent(); } return false; } }