/*
* 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.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.conditions.ColumnCondition;
import org.apache.cassandra.cql3.conditions.Conditions;
import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
import org.apache.cassandra.db.Clustering;
import org.apache.cassandra.db.CompactTables;
import org.apache.cassandra.db.Slice;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.Pair;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkContainsNoDuplicates;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
/**
* An <code>UPDATE</code> statement parsed from a CQL query statement.
*
*/
public class UpdateStatement extends ModificationStatement
{
private static final Constants.Value EMPTY = new Constants.Value(ByteBufferUtil.EMPTY_BYTE_BUFFER);
private UpdateStatement(StatementType type,
int boundTerms,
TableMetadata metadata,
Operations operations,
StatementRestrictions restrictions,
Conditions conditions,
Attributes attrs)
{
super(type, boundTerms, metadata, operations, restrictions, conditions, attrs);
}
public boolean requireFullClusteringKey()
{
return true;
}
@Override
public void addUpdateForKey(PartitionUpdate update, Clustering clustering, UpdateParameters params)
{
if (updatesRegularRows())
{
params.newRow(clustering);
// We update the row timestamp (ex-row marker) only on INSERT (#6782)
// Further, COMPACT tables semantic differs from "CQL3" ones in that a row exists only if it has
// a non-null column, so we don't want to set the row timestamp for them.
if (type.isInsert() && metadata().isCQLTable())
params.addPrimaryKeyLivenessInfo();
List<Operation> updates = getRegularOperations();
// For compact table, we don't accept an insert/update that only sets the PK unless the is no
// declared non-PK columns (which we recognize because in that case
// the compact value is of type "EmptyType").
if (metadata().isCompactTable() && updates.isEmpty())
{
checkTrue(CompactTables.hasEmptyCompactValue(metadata),
"Column %s is mandatory for this COMPACT STORAGE table",
metadata().compactValueColumn.name);
updates = Collections.<Operation>singletonList(new Constants.Setter(metadata().compactValueColumn, EMPTY));
}
for (Operation op : updates)
op.execute(update.partitionKey(), params);
update.add(params.buildRow());
}
if (updatesStaticRow())
{
params.newRow(Clustering.STATIC_CLUSTERING);
for (Operation op : getStaticOperations())
op.execute(update.partitionKey(), params);
update.add(params.buildRow());
}
}
@Override
public void addUpdateForKey(PartitionUpdate update, Slice slice, UpdateParameters params)
{
throw new UnsupportedOperationException();
}
public static class ParsedInsert extends ModificationStatement.Parsed
{
private final List<ColumnMetadata.Raw> columnNames;
private final List<Term.Raw> columnValues;
/**
* A parsed <code>INSERT</code> statement.
*
* @param name column family being operated on
* @param attrs additional attributes for statement (CL, timestamp, timeToLive)
* @param columnNames list of column names
* @param columnValues list of column values (corresponds to names)
* @param ifNotExists true if an IF NOT EXISTS condition was specified, false otherwise
*/
public ParsedInsert(CFName name,
Attributes.Raw attrs,
List<ColumnMetadata.Raw> columnNames,
List<Term.Raw> columnValues,
boolean ifNotExists)
{
super(name, StatementType.INSERT, attrs, null, ifNotExists, false);
this.columnNames = columnNames;
this.columnValues = columnValues;
}
@Override
protected ModificationStatement prepareInternal(TableMetadata metadata,
VariableSpecifications boundNames,
Conditions conditions,
Attributes attrs)
{
// Created from an INSERT
checkFalse(metadata.isCounter(), "INSERT statements are not allowed on counter tables, use UPDATE instead");
checkFalse(columnNames == null, "Column names for INSERT must be provided when using VALUES");
checkFalse(columnNames.isEmpty(), "No columns provided to INSERT");
checkFalse(columnNames.size() != columnValues.size(), "Unmatched column names/values");
checkContainsNoDuplicates(columnNames, "The column names contains duplicates");
WhereClause.Builder whereClause = new WhereClause.Builder();
Operations operations = new Operations(type);
boolean hasClusteringColumnsSet = false;
for (int i = 0; i < columnNames.size(); i++)
{
ColumnMetadata def = getColumnDefinition(metadata, columnNames.get(i));
if (def.isClusteringColumn())
hasClusteringColumnsSet = true;
Term.Raw value = columnValues.get(i);
if (def.isPrimaryKeyColumn())
{
whereClause.add(new SingleColumnRelation(columnNames.get(i), Operator.EQ, value));
}
else
{
Operation operation = new Operation.SetValue(value).prepare(metadata, def);
operation.collectMarkerSpecification(boundNames);
operations.add(operation);
}
}
boolean applyOnlyToStaticColumns = !hasClusteringColumnsSet && appliesOnlyToStaticColumns(operations, conditions);
StatementRestrictions restrictions = new StatementRestrictions(type,
metadata,
whereClause.build(),
boundNames,
applyOnlyToStaticColumns,
false,
false);
return new UpdateStatement(type,
boundNames.size(),
metadata,
operations,
restrictions,
conditions,
attrs);
}
}
/**
* A parsed INSERT JSON statement.
*/
public static class ParsedInsertJson extends ModificationStatement.Parsed
{
private final Json.Raw jsonValue;
private final boolean defaultUnset;
public ParsedInsertJson(CFName name, Attributes.Raw attrs, Json.Raw jsonValue, boolean defaultUnset, boolean ifNotExists)
{
super(name, StatementType.INSERT, attrs, null, ifNotExists, false);
this.jsonValue = jsonValue;
this.defaultUnset = defaultUnset;
}
@Override
protected ModificationStatement prepareInternal(TableMetadata metadata,
VariableSpecifications boundNames,
Conditions conditions,
Attributes attrs)
{
checkFalse(metadata.isCounter(), "INSERT statements are not allowed on counter tables, use UPDATE instead");
Collection<ColumnMetadata> defs = metadata.columns();
Json.Prepared prepared = jsonValue.prepareAndCollectMarkers(metadata, defs, boundNames);
WhereClause.Builder whereClause = new WhereClause.Builder();
Operations operations = new Operations(type);
boolean hasClusteringColumnsSet = false;
for (ColumnMetadata def : defs)
{
if (def.isClusteringColumn())
hasClusteringColumnsSet = true;
Term.Raw raw = prepared.getRawTermForColumn(def, defaultUnset);
if (def.isPrimaryKeyColumn())
{
whereClause.add(new SingleColumnRelation(ColumnMetadata.Raw.forColumn(def), Operator.EQ, raw));
}
else
{
Operation operation = new Operation.SetValue(raw).prepare(metadata, def);
operation.collectMarkerSpecification(boundNames);
operations.add(operation);
}
}
boolean applyOnlyToStaticColumns = !hasClusteringColumnsSet && appliesOnlyToStaticColumns(operations, conditions);
StatementRestrictions restrictions = new StatementRestrictions(type,
metadata,
whereClause.build(),
boundNames,
applyOnlyToStaticColumns,
false,
false);
return new UpdateStatement(type,
boundNames.size(),
metadata,
operations,
restrictions,
conditions,
attrs);
}
}
public static class ParsedUpdate extends ModificationStatement.Parsed
{
// Provided for an UPDATE
private final List<Pair<ColumnMetadata.Raw, Operation.RawUpdate>> updates;
private final WhereClause whereClause;
/**
* Creates a new UpdateStatement from a column family name, columns map, consistency
* level, and key term.
*
* @param name column family being operated on
* @param attrs additional attributes for statement (timestamp, timeToLive)
* @param updates a map of column operations to perform
* @param whereClause the where clause
* @param ifExists flag to check if row exists
* */
public ParsedUpdate(CFName name,
Attributes.Raw attrs,
List<Pair<ColumnMetadata.Raw, Operation.RawUpdate>> updates,
WhereClause whereClause,
List<Pair<ColumnMetadata.Raw, ColumnCondition.Raw>> conditions,
boolean ifExists)
{
super(name, StatementType.UPDATE, attrs, conditions, false, ifExists);
this.updates = updates;
this.whereClause = whereClause;
}
@Override
protected ModificationStatement prepareInternal(TableMetadata metadata,
VariableSpecifications boundNames,
Conditions conditions,
Attributes attrs)
{
Operations operations = new Operations(type);
for (Pair<ColumnMetadata.Raw, Operation.RawUpdate> entry : updates)
{
ColumnMetadata def = getColumnDefinition(metadata, entry.left);
checkFalse(def.isPrimaryKeyColumn(), "PRIMARY KEY part %s found in SET part", def.name);
Operation operation = entry.right.prepare(metadata, def);
operation.collectMarkerSpecification(boundNames);
operations.add(operation);
}
StatementRestrictions restrictions = newRestrictions(metadata,
boundNames,
operations,
whereClause,
conditions);
return new UpdateStatement(type,
boundNames.size(),
metadata,
operations,
restrictions,
conditions,
attrs);
}
}
}