blob: 641b6bbc313e4bb517d056ca103a63db937ad431 [file] [log] [blame]
/*
* 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.Collections;
import java.util.List;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.*;
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.utils.ByteBufferUtil;
import org.apache.cassandra.utils.Pair;
import static com.google.common.collect.Lists.newArrayList;
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,
CFMetaData cfm,
Operations operations,
StatementRestrictions restrictions,
Conditions conditions,
Attributes attrs)
{
super(type, boundTerms, cfm, 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() && cfm.isCQLTable())
params.addPrimaryKeyLivenessInfo();
List<Operation> updates = getRegularOperations();
// For compact table, when we translate it to thrift, we don't have a row marker. So we don't accept an insert/update
// that only sets the PK unless the is no declared non-PK columns (in the latter we just set the value empty).
// For a dense layout, when we translate it to thrift, we don't have a row marker. So 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 ((cfm.isCompactTable() && !cfm.isSuper()) && updates.isEmpty())
{
checkTrue(CompactTables.hasEmptyCompactValue(cfm),
"Column %s is mandatory for this COMPACT STORAGE table",
cfm.compactValueColumn().name);
updates = Collections.<Operation>singletonList(new Constants.Setter(cfm.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<ColumnIdentifier.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<ColumnIdentifier.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(CFMetaData cfm,
VariableSpecifications boundNames,
Conditions conditions,
Attributes attrs)
{
// Created from an INSERT
checkFalse(cfm.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;
if (cfm.isSuper() && cfm.isDense())
{
// SuperColumn familiy updates are always row-level
hasClusteringColumnsSet = true;
SuperColumnCompatibility.prepareInsertOperations(cfm, columnNames, whereClause, columnValues, boundNames, operations);
}
else
{
for (int i = 0; i < columnNames.size(); i++)
{
ColumnDefinition def = getColumnDefinition(cfm, 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(cfm.ksName, def);
operation.collectMarkerSpecification(boundNames);
operations.add(operation);
}
}
}
boolean applyOnlyToStaticColumns = appliesOnlyToStaticColumns(operations, conditions) && !hasClusteringColumnsSet;
StatementRestrictions restrictions = new StatementRestrictions(type,
cfm,
whereClause.build(),
boundNames,
applyOnlyToStaticColumns,
false,
false,
false);
return new UpdateStatement(type,
boundNames.size(),
cfm,
operations,
restrictions,
conditions,
attrs);
}
}
/**
* A parsed INSERT JSON statement.
*/
public static class ParsedInsertJson extends ModificationStatement.Parsed
{
private final Json.Raw jsonValue;
public ParsedInsertJson(CFName name, Attributes.Raw attrs, Json.Raw jsonValue, boolean ifNotExists)
{
super(name, StatementType.INSERT, attrs, null, ifNotExists, false);
this.jsonValue = jsonValue;
}
@Override
protected ModificationStatement prepareInternal(CFMetaData cfm,
VariableSpecifications boundNames,
Conditions conditions,
Attributes attrs)
{
checkFalse(cfm.isCounter(), "INSERT statements are not allowed on counter tables, use UPDATE instead");
List<ColumnDefinition> defs = newArrayList(cfm.allColumnsInSelectOrder());
Json.Prepared prepared = jsonValue.prepareAndCollectMarkers(cfm, defs, boundNames);
WhereClause.Builder whereClause = new WhereClause.Builder();
Operations operations = new Operations(type);
boolean hasClusteringColumnsSet = false;
if (cfm.isSuper() && cfm.isDense())
{
hasClusteringColumnsSet = true;
SuperColumnCompatibility.prepareInsertJSONOperations(cfm, defs, boundNames, prepared, whereClause, operations);
}
else
{
for (ColumnDefinition def : defs)
{
if (def.isClusteringColumn())
hasClusteringColumnsSet = true;
Term.Raw raw = prepared.getRawTermForColumn(def);
if (def.isPrimaryKeyColumn())
{
whereClause.add(new SingleColumnRelation(new ColumnIdentifier.ColumnIdentifierValue(def.name), Operator.EQ, raw));
}
else
{
Operation operation = new Operation.SetValue(raw).prepare(cfm.ksName, def);
operation.collectMarkerSpecification(boundNames);
operations.add(operation);
}
}
}
boolean applyOnlyToStaticColumns = appliesOnlyToStaticColumns(operations, conditions) && !hasClusteringColumnsSet;
StatementRestrictions restrictions = new StatementRestrictions(type,
cfm,
whereClause.build(),
boundNames,
applyOnlyToStaticColumns,
false,
false,
false);
return new UpdateStatement(type,
boundNames.size(),
cfm,
operations,
restrictions,
conditions,
attrs);
}
}
public static class ParsedUpdate extends ModificationStatement.Parsed
{
// Provided for an UPDATE
private final List<Pair<ColumnIdentifier.Raw, Operation.RawUpdate>> updates;
private 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<ColumnIdentifier.Raw, Operation.RawUpdate>> updates,
WhereClause whereClause,
List<Pair<ColumnIdentifier.Raw, ColumnCondition.Raw>> conditions,
boolean ifExists)
{
super(name, StatementType.UPDATE, attrs, conditions, false, ifExists);
this.updates = updates;
this.whereClause = whereClause;
}
@Override
protected ModificationStatement prepareInternal(CFMetaData cfm,
VariableSpecifications boundNames,
Conditions conditions,
Attributes attrs)
{
Operations operations = new Operations(type);
if (cfm.isSuper() && cfm.isDense())
{
conditions = SuperColumnCompatibility.rebuildLWTColumnConditions(conditions, cfm, whereClause);
whereClause = SuperColumnCompatibility.prepareUpdateOperations(cfm, whereClause, updates, boundNames, operations);
}
else
{
for (Pair<ColumnIdentifier.Raw, Operation.RawUpdate> entry : updates)
{
ColumnDefinition def = getColumnDefinition(cfm, entry.left);
checkFalse(def.isPrimaryKeyColumn(), "PRIMARY KEY part %s found in SET part", def.name);
Operation operation = entry.right.prepare(cfm.ksName, def);
operation.collectMarkerSpecification(boundNames);
operations.add(operation);
}
}
StatementRestrictions restrictions = newRestrictions(cfm,
boundNames,
operations,
whereClause,
conditions);
return new UpdateStatement(type,
boundNames.size(),
cfm,
operations,
restrictions,
conditions,
attrs);
}
}
}