Package org.apache.cassandra.cql3.statements

Source Code of org.apache.cassandra.cql3.statements.UpdateStatement

/*
* 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.cassandra.cql3.statements;

import java.nio.ByteBuffer;
import java.util.*;

import com.google.common.collect.ArrayListMultimap;

import org.apache.cassandra.cql3.*;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.cql3.operations.ColumnOperation;
import org.apache.cassandra.cql3.operations.Operation;
import org.apache.cassandra.cql3.operations.SetOperation;
import org.apache.cassandra.cql3.operations.PreparedOperation;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.exceptions.*;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.Pair;

import static org.apache.cassandra.cql.QueryProcessor.validateKey;
import static org.apache.cassandra.thrift.ThriftValidation.validateColumnFamily;

/**
* An <code>UPDATE</code> statement parsed from a CQL query statement.
*
*/
public class UpdateStatement extends ModificationStatement
{
    private CFDefinition cfDef;
    private final List<Pair<ColumnIdentifier, Operation>> columns;
    private final List<ColumnIdentifier> columnNames;
    private final List<Operation> columnOperations;
    private final List<Relation> whereClause;

    private final ArrayListMultimap<CFDefinition.Name, Operation> processedColumns = ArrayListMultimap.create();
    private final Map<ColumnIdentifier, List<Term>> processedKeys = new HashMap<ColumnIdentifier, List<Term>>();

    /**
     * Creates a new UpdateStatement from a column family name, columns map, consistency
     * level, and key term.
     *
     * @param name column family being operated on
     * @param columns a map of column name/values pairs
     * @param whereClause the where clause
     * @param attrs additional attributes for statement (CL, timestamp, timeToLive)
     */
    public UpdateStatement(CFName name,
                           List<Pair<ColumnIdentifier, Operation>> columns,
                           List<Relation> whereClause,
                           Attributes attrs)
    {
        super(name, attrs);

        this.columns = columns;
        this.whereClause = whereClause;
        this.columnNames = null;
        this.columnOperations = null;
    }

    /**
     * Creates a new UpdateStatement from a column family name, a consistency level,
     * key, and lists of column names and values.  It is intended for use with the
     * alternate update format, <code>INSERT</code>.
     *
     * @param name column family being operated on
     * @param columnNames list of column names
     * @param columnOperations list of column 'set' operations (corresponds to names)
     * @param attrs additional attributes for statement (CL, timestamp, timeToLive)
     */
    public UpdateStatement(CFName name,
                           Attributes attrs,
                           List<ColumnIdentifier> columnNames,
                           List<Operation> columnOperations)
    {
        super(name, attrs);

        this.columnNames = columnNames;
        this.columnOperations = columnOperations;
        this.whereClause = null;
        this.columns = null;
    }

    protected void validateConsistency(ConsistencyLevel cl) throws InvalidRequestException
    {
        if (type == Type.COUNTER)
            cl.validateCounterForWrite(cfDef.cfm);
        else
            cl.validateForWrite(cfDef.cfm.ksName);
    }

    /** {@inheritDoc} */
    public Collection<IMutation> getMutations(List<ByteBuffer> variables, boolean local, ConsistencyLevel cl, long now)
    throws RequestExecutionException, RequestValidationException
    {
        List<ByteBuffer> keys = buildKeyNames(cfDef, processedKeys, variables);

        ColumnNameBuilder builder = cfDef.getColumnNameBuilder();
        buildColumnNames(cfDef, processedKeys, builder, variables, true);

        // Lists SET operation incurs a read.
        Set<ByteBuffer> toRead = null;
        for (Map.Entry<CFDefinition.Name, Operation> entry : processedColumns.entries())
        {
            CFDefinition.Name name = entry.getKey();
            Operation value = entry.getValue();

            if (!(name.type instanceof ListType))
                continue;

            if (value.requiresRead(name.type))
            {
                if (toRead == null)
                    toRead = new TreeSet<ByteBuffer>(UTF8Type.instance);
                toRead.add(name.name.key);
            }
        }

        Map<ByteBuffer, ColumnGroupMap> rows = toRead != null ? readRows(keys, builder, toRead, (CompositeType)cfDef.cfm.comparator, local, cl) : null;

        Collection<IMutation> mutations = new LinkedList<IMutation>();
        UpdateParameters params = new UpdateParameters(variables, getTimestamp(now), getTimeToLive());

        for (ByteBuffer key: keys)
            mutations.add(mutationForKey(cfDef, key, builder, params, rows == null ? null : rows.get(key), cl));

        return mutations;
    }

    // Returns the first empty component or null if none are
    static CFDefinition.Name buildColumnNames(CFDefinition cfDef, Map<ColumnIdentifier, List<Term>> processed, ColumnNameBuilder builder, List<ByteBuffer> variables, boolean requireAllComponent)
    throws InvalidRequestException
    {
        CFDefinition.Name firstEmpty = null;
        for (CFDefinition.Name name : cfDef.columns.values())
        {
            List<Term> values = processed.get(name.name);
            if (values == null || values.isEmpty())
            {
                firstEmpty = name;
                if (requireAllComponent && cfDef.isComposite && !cfDef.isCompact)
                    throw new InvalidRequestException(String.format("Missing mandatory PRIMARY KEY part %s", name));
            }
            else if (firstEmpty != null)
            {
                throw new InvalidRequestException(String.format("Missing PRIMARY KEY part %s since %s is set", firstEmpty.name, name.name));
            }
            else
            {
                assert values.size() == 1; // We only allow IN for row keys so far
                builder.add(values.get(0), Relation.Type.EQ, variables);
            }
        }
        return firstEmpty;
    }

    static List<ByteBuffer> buildKeyNames(CFDefinition cfDef, Map<ColumnIdentifier, List<Term>> processed, List<ByteBuffer> variables)
    throws InvalidRequestException
    {
        ColumnNameBuilder keyBuilder = cfDef.getKeyNameBuilder();
        List<ByteBuffer> keys = new ArrayList<ByteBuffer>();
        for (CFDefinition.Name name : cfDef.keys.values())
        {
            List<Term> values = processed.get(name.name);
            if (values == null || values.isEmpty())
                throw new InvalidRequestException(String.format("Missing mandatory PRIMARY KEY part %s", name));

            if (keyBuilder.remainingCount() == 1)
            {
                for (Term t : values)
                    keys.add(keyBuilder.copy().add(t, Relation.Type.EQ, variables).build());
            }
            else
            {
                if (values.size() > 1)
                    throw new InvalidRequestException("IN is only supported on the last column of the partition key");
                keyBuilder.add(values.get(0), Relation.Type.EQ, variables);
            }
        }
        return keys;
    }

    /**
     * Compute a row mutation for a single key
     *
     * @return row mutation
     *
     * @throws InvalidRequestException on the wrong request
     */
    private IMutation mutationForKey(CFDefinition cfDef, ByteBuffer key, ColumnNameBuilder builder, UpdateParameters params, ColumnGroupMap group, ConsistencyLevel cl)
    throws InvalidRequestException
    {
        validateKey(key);

        QueryProcessor.validateKey(key);
        RowMutation rm = new RowMutation(cfDef.cfm.ksName, key);
        ColumnFamily cf = rm.addOrGet(cfDef.cfm.cfName);

        // Inserting the CQL row marker (see #4361)
        // We always need to insert a marker, because of the following situation:
        //   CREATE TABLE t ( k int PRIMARY KEY, c text );
        //   INSERT INTO t(k, c) VALUES (1, 1)
        //   DELETE c FROM t WHERE k = 1;
        //   SELECT * FROM t;
        // The last query should return one row (but with c == null). Adding
        // the marker with the insert make sure the semantic is correct (while making sure a
        // 'DELETE FROM t WHERE k = 1' does remove the row entirely)
        if (cfDef.isComposite && !cfDef.isCompact)
        {
            ByteBuffer name = builder.copy().add(ByteBufferUtil.EMPTY_BYTE_BUFFER).build();
            cf.addColumn(params.makeColumn(name, ByteBufferUtil.EMPTY_BYTE_BUFFER));
        }

        if (cfDef.isCompact)
        {
            if (builder.componentCount() == 0)
                throw new InvalidRequestException(String.format("Missing PRIMARY KEY part %s", cfDef.columns.values().iterator().next()));

            Operation operation;
            if (cfDef.value == null)
            {
                // No value was defined, we set to the empty value
                operation = ColumnOperation.SetToEmpty();
            }
            else
            {
                List<Operation> operations = processedColumns.get(cfDef.value);
                if (operations.isEmpty())
                    throw new InvalidRequestException(String.format("Missing mandatory column %s", cfDef.value));
                assert operations.size() == 1;
                operation = operations.get(0);
            }
            operation.execute(cf, builder.copy(), cfDef.value == null ? null : cfDef.value.type, params, null);
        }
        else
        {
            for (Map.Entry<CFDefinition.Name, Operation> entry : processedColumns.entries())
            {
                CFDefinition.Name name = entry.getKey();
                Operation op = entry.getValue();
                op.execute(cf, builder.copy().add(name.name.key), name.type, params, group == null || !op.requiresRead(name.type) ? null : group.getCollection(name.name.key));
            }
        }

        return type == Type.COUNTER ? new CounterMutation(rm, cl) : rm;
    }

    public ParsedStatement.Prepared prepare(ColumnSpecification[] boundNames) throws InvalidRequestException
    {
        // Deal here with the keyspace overwrite thingy to avoid mistake
        CFMetaData metadata = validateColumnFamily(keyspace(), columnFamily());
        cfDef = metadata.getCfDef();

        type = metadata.getDefaultValidator().isCommutative() ? Type.COUNTER : Type.LOGGED;

        if (columns == null)
        {
            // Created from an INSERT
            if (type == Type.COUNTER)
                throw new InvalidRequestException("INSERT statement are not allowed on counter tables, use UPDATE instead");
            if (columnNames.size() != columnOperations.size())
                throw new InvalidRequestException("unmatched column names/values");
            if (columnNames.size() < 1)
                throw new InvalidRequestException("no columns specified for INSERT");

            for (int i = 0; i < columnNames.size(); i++)
            {
                CFDefinition.Name name = cfDef.get(columnNames.get(i));
                if (name == null)
                    throw new InvalidRequestException(String.format("Unknown identifier %s", columnNames.get(i)));

                Operation operation = columnOperations.get(i);
                operation.addBoundNames(name, boundNames);

                switch (name.kind)
                {
                    case KEY_ALIAS:
                    case COLUMN_ALIAS:
                        if (processedKeys.containsKey(name.name))
                            throw new InvalidRequestException(String.format("Multiple definitions found for PRIMARY KEY part %s", name));
                        // We know collection are not accepted for key and column aliases
                        if (operation.getType() != Operation.Type.COLUMN && operation.getType() != Operation.Type.PREPARED)
                            throw new InvalidRequestException(String.format("Invalid definition for %s, not a collection type", name));
                        processedKeys.put(name.name, operation.getValues());
                        break;
                    case VALUE_ALIAS:
                    case COLUMN_METADATA:
                        if (processedColumns.containsKey(name))
                            throw new InvalidRequestException(String.format("Multiple definitions found for column %s", name));
                        addNewOperation(name, operation);
                        break;
                }
            }
        }
        else
        {
            // Created from an UPDATE
            for (Pair<ColumnIdentifier, Operation> entry : columns)
            {
                CFDefinition.Name name = cfDef.get(entry.left);
                if (name == null)
                    throw new InvalidRequestException(String.format("Unknown identifier %s", entry.left));

                Operation operation = entry.right;

                switch (operation.getType())
                {
                    case COUNTER:
                        if (type != Type.COUNTER)
                            throw new InvalidRequestException("Invalid counter operation on non-counter table.");
                        break;
                    case LIST:
                    case SET:
                    case MAP:
                        if (!name.type.isCollection())
                            throw new InvalidRequestException("Cannot apply collection operation on column " + name + " with " + name.type + " type.");
                    // Fallthrough on purpose
                    case COLUMN:
                        if (type == Type.COUNTER)
                            throw new InvalidRequestException("Invalid non-counter operation on counter table.");
                        break;
                    case PREPARED:
                        if (type == Type.COUNTER && !((PreparedOperation)operation).isPotentialCounterOperation())
                            throw new InvalidRequestException("Invalid non-counter operation on counter table.");
                        break;
                }

                switch (name.kind)
                {
                    case KEY_ALIAS:
                    case COLUMN_ALIAS:
                        throw new InvalidRequestException(String.format("PRIMARY KEY part %s found in SET part", entry.left));
                    case VALUE_ALIAS:
                    case COLUMN_METADATA:
                        for (Operation otherOp : processedColumns.get(name))
                            if (otherOp.getType() == Operation.Type.COLUMN)
                                throw new InvalidRequestException(String.format("Multiple definitions found for column %s", name));

                        operation.addBoundNames(name, boundNames);
                        addNewOperation(name, operation);
                        break;
                }
            }
            processKeys(cfDef, whereClause, processedKeys, boundNames);
        }

        return new ParsedStatement.Prepared(this, Arrays.<ColumnSpecification>asList(boundNames));
    }

    private void addNewOperation(CFDefinition.Name name, Operation operation)
    {
        // On the parser side, we're unable to differentiate an empty map from an empty set for add and set operations.
        // Fix it now that we have the actual type.
        if (operation.getType() == Operation.Type.SET && (name.type instanceof MapType))
            operation = ((SetOperation)operation).maybeConvertToEmptyMapOperation();

        processedColumns.put(name, operation);
    }

    public ParsedStatement.Prepared prepare() throws InvalidRequestException
    {
        ColumnSpecification[] names = new ColumnSpecification[getBoundsTerms()];
        return prepare(names);
    }

    // Reused by DeleteStatement
    static void processKeys(CFDefinition cfDef, List<Relation> keys, Map<ColumnIdentifier, List<Term>> processed, ColumnSpecification[] names) throws InvalidRequestException
    {
        for (Relation rel : keys)
        {
            CFDefinition.Name name = cfDef.get(rel.getEntity());
            if (name == null)
                throw new InvalidRequestException(String.format("Unknown key identifier %s", rel.getEntity()));

            switch (name.kind)
            {
                case KEY_ALIAS:
                case COLUMN_ALIAS:
                    List<Term> values;
                    if (rel.operator() == Relation.Type.EQ)
                        values = Collections.singletonList(rel.getValue());
                    else if (name.kind == CFDefinition.Name.Kind.KEY_ALIAS && rel.operator() == Relation.Type.IN)
                        values = rel.getInValues();
                    else
                        throw new InvalidRequestException(String.format("Invalid operator %s for key %s", rel.operator(), rel.getEntity()));

                    if (processed.containsKey(name.name))
                        throw new InvalidRequestException(String.format("Multiple definitions found for PRIMARY KEY part %s", name));
                    for (Term value : values)
                        if (value.isBindMarker())
                            names[value.bindIndex] = name;
                    processed.put(name.name, values);
                    break;
                case VALUE_ALIAS:
                case COLUMN_METADATA:
                    throw new InvalidRequestException(String.format("PRIMARY KEY part %s found in SET part", rel.getEntity()));
            }
        }
    }

    public String toString()
    {
        return String.format("UpdateStatement(name=%s, keys=%s, columns=%s, timestamp=%s, timeToLive=%s)",
                             cfName,
                             whereClause,
                             columns,
                             isSetTimestamp() ? getTimestamp(-1) : "<now>",
                             getTimeToLive());
    }
}
TOP

Related Classes of org.apache.cassandra.cql3.statements.UpdateStatement

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.