/*
* 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.*;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.cql3.functions.Functions;
import org.apache.cassandra.db.CounterColumn;
import org.apache.cassandra.db.ExpiringColumn;
import org.apache.cassandra.db.Column;
import org.apache.cassandra.db.context.CounterContext;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.Int32Type;
import org.apache.cassandra.db.marshal.LongType;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.utils.ByteBufferUtil;
public abstract class Selection
{
private final List<CFDefinition.Name> columns;
private final SelectionColumns columnMapping;
private final boolean collectTimestamps;
private final boolean collectTTLs;
protected Selection(List<CFDefinition.Name> columns, SelectionColumns columnMapping, boolean collectTimestamps, boolean collectTTLs)
{
this.columns = columns;
this.columnMapping = columnMapping;
this.collectTimestamps = collectTimestamps;
this.collectTTLs = collectTTLs;
}
// Overriden by SimpleSelection when appropriate.
public boolean isWildcard()
{
return false;
}
public ResultSet.Metadata getResultMetadata()
{
return new ResultSet.Metadata(columnMapping.getColumnSpecifications());
}
public static Selection wildcard(CFDefinition cfDef)
{
List<CFDefinition.Name> all = new ArrayList<CFDefinition.Name>();
for (CFDefinition.Name name : cfDef)
all.add(name);
return new SimpleSelection(all, true);
}
public static Selection forColumns(List<CFDefinition.Name> columns)
{
return new SimpleSelection(columns, false);
}
private static boolean selectionsNeedProcessing(List<RawSelector> rawSelectors)
{
for (RawSelector rawSelector : rawSelectors)
{
if (rawSelector.processesSelection())
return true;
}
return false;
}
private static int addAndGetIndex(CFDefinition.Name name, List<CFDefinition.Name> l)
{
int idx = l.indexOf(name);
if (idx < 0)
{
idx = l.size();
l.add(name);
}
return idx;
}
private static Selector makeSelector(CFDefinition cfDef,
RawSelector raw,
List<CFDefinition.Name> names,
SelectionColumnMapping columnMapping) throws InvalidRequestException
{
Selectable selectable = raw.selectable.prepare(cfDef.cfm);
return makeSelector(cfDef, selectable, raw.alias, names, columnMapping);
}
private static Selector makeSelector(CFDefinition cfDef,
Selectable selectable,
ColumnIdentifier alias,
List<CFDefinition.Name> names,
SelectionColumnMapping columnMapping) throws InvalidRequestException
{
if (selectable instanceof ColumnIdentifier)
{
CFDefinition.Name name = cfDef.get((ColumnIdentifier) selectable);
if (name == null)
throw new InvalidRequestException(String.format("Undefined name %s in selection clause", selectable));
if (columnMapping != null)
columnMapping.addMapping(alias == null ? name : makeAliasSpec(cfDef, name.type, alias), name);
return new SimpleSelector(name.toString(), addAndGetIndex(name, names), name.type);
}
else if (selectable instanceof Selectable.WritetimeOrTTL)
{
Selectable.WritetimeOrTTL tot = (Selectable.WritetimeOrTTL)selectable;
CFDefinition.Name name = cfDef.get(tot.id);
if (name == null)
throw new InvalidRequestException(String.format("Undefined name %s in selection clause", tot.id));
if (name.isPrimaryKeyColumn())
throw new InvalidRequestException(String.format("Cannot use selection function %s on PRIMARY KEY part %s", tot.isWritetime ? "writeTime" : "ttl", name));
if (name.type.isCollection())
throw new InvalidRequestException(String.format("Cannot use selection function %s on collections", tot.isWritetime ? "writeTime" : "ttl"));
if (columnMapping != null)
columnMapping.addMapping(makeWritetimeOrTTLSpec(cfDef, tot, alias), name);
return new WritetimeOrTTLSelector(name.toString(), addAndGetIndex(name, names), tot.isWritetime);
}
else
{
Selectable.WithFunction withFun = (Selectable.WithFunction)selectable;
List<Selector> args = new ArrayList<Selector>(withFun.args.size());
// use a temporary column mapping to collate the columns used by all the function args
SelectionColumnMapping tmpMapping = SelectionColumnMapping.newMapping();
for (Selectable rawArg : withFun.args)
args.add(makeSelector(cfDef, rawArg, null, names, tmpMapping));
AbstractType<?> returnType = Functions.getReturnType(withFun.functionName, cfDef.cfm.ksName, cfDef.cfm.cfName);
if (returnType == null)
throw new InvalidRequestException(String.format("Unknown function '%s'", withFun.functionName));
ColumnSpecification spec = makeFunctionSpec(cfDef, withFun, returnType, alias);
Function fun = Functions.get(withFun.functionName, args, spec);
if (columnMapping != null)
columnMapping.addMapping(spec, tmpMapping.getMappings().values());
return new FunctionSelector(fun, args);
}
}
private static ColumnSpecification makeWritetimeOrTTLSpec(CFDefinition cfDef, Selectable.WritetimeOrTTL tot, ColumnIdentifier alias)
{
return new ColumnSpecification(cfDef.cfm.ksName,
cfDef.cfm.cfName,
alias == null ? new ColumnIdentifier(tot.toString(), true) : alias,
tot.isWritetime ? LongType.instance : Int32Type.instance);
}
private static ColumnSpecification makeFunctionSpec(CFDefinition cfDef,
Selectable.WithFunction fun,
AbstractType<?> returnType,
ColumnIdentifier alias) throws InvalidRequestException
{
if (returnType == null)
throw new InvalidRequestException(String.format("Unknown function %s called in selection clause", fun.functionName));
return new ColumnSpecification(cfDef.cfm.ksName,
cfDef.cfm.cfName,
alias == null ? new ColumnIdentifier(fun.toString(), true) : alias,
returnType);
}
private static ColumnSpecification makeAliasSpec(CFDefinition cfDef, AbstractType<?> type, ColumnIdentifier alias)
{
return new ColumnSpecification(cfDef.cfm.ksName, cfDef.cfm.cfName, alias, type);
}
public static Selection fromSelectors(CFDefinition cfDef, List<RawSelector> rawSelectors) throws InvalidRequestException
{
boolean needsProcessing = selectionsNeedProcessing(rawSelectors);
if (needsProcessing)
{
List<CFDefinition.Name> names = new ArrayList<CFDefinition.Name>();
SelectionColumnMapping columnMapping = SelectionColumnMapping.newMapping();
List<Selector> selectors = new ArrayList<Selector>(rawSelectors.size());
boolean collectTimestamps = false;
boolean collectTTLs = false;
for (RawSelector rawSelector : rawSelectors)
{
Selector selector = makeSelector(cfDef, rawSelector, names, columnMapping);
selectors.add(selector);
collectTimestamps |= selector.usesTimestamps();
collectTTLs |= selector.usesTTLs();
}
return new SelectionWithProcessing(names, columnMapping, selectors, collectTimestamps, collectTTLs);
}
else
{
List<CFDefinition.Name> names = new ArrayList<CFDefinition.Name>(rawSelectors.size());
SelectionColumnMapping columnMapping = SelectionColumnMapping.newMapping();
for (RawSelector rawSelector : rawSelectors)
{
assert rawSelector.selectable instanceof ColumnIdentifier.Raw;
ColumnIdentifier id = ((ColumnIdentifier.Raw)rawSelector.selectable).prepare(cfDef.cfm);
CFDefinition.Name name = cfDef.get(id);
if (name == null)
throw new InvalidRequestException(String.format("Undefined name %s in selection clause", id));
names.add(name);
columnMapping.addMapping(rawSelector.alias == null ? name : makeAliasSpec(cfDef,
name.type,
rawSelector.alias),
name);
}
return new SimpleSelection(names, columnMapping, false);
}
}
protected abstract List<ByteBuffer> handleRow(ResultSetBuilder rs) throws InvalidRequestException;
/**
* @return the list of CQL3 "regular" (the "COLUMN_METADATA" ones) column names to fetch.
*/
public List<ColumnIdentifier> regularAndStaticColumnsToFetch()
{
List<ColumnIdentifier> toFetch = new ArrayList<ColumnIdentifier>();
for (CFDefinition.Name name : columns)
{
if (name.kind == CFDefinition.Name.Kind.COLUMN_METADATA || name.kind == CFDefinition.Name.Kind.STATIC)
toFetch.add(name.name);
}
return toFetch;
}
/**
* @return the list of CQL3 columns value this SelectionClause needs.
*/
public List<CFDefinition.Name> getColumns()
{
return columns;
}
/**
* @return the mappings between resultset columns and the underlying columns
*/
public SelectionColumns getColumnMapping()
{
return columnMapping;
}
public ResultSetBuilder resultSetBuilder(long now)
{
return new ResultSetBuilder(now);
}
private static ByteBuffer value(Column c)
{
return (c instanceof CounterColumn)
? ByteBufferUtil.bytes(CounterContext.instance().total(c.value()))
: c.value();
}
public class ResultSetBuilder
{
private final ResultSet resultSet;
/*
* We'll build CQL3 row one by one.
* The currentRow is the values for the (CQL3) columns we've fetched.
* We also collect timestamps and ttls for the case where the writetime and
* ttl functions are used. Note that we might collect timestamp and/or ttls
* we don't care about, but since the array below are allocated just once,
* it doesn't matter performance wise.
*/
List<ByteBuffer> current;
final long[] timestamps;
final int[] ttls;
final long now;
private ResultSetBuilder(long now)
{
this.resultSet = new ResultSet(columnMapping.getColumnSpecifications());
this.timestamps = collectTimestamps ? new long[columns.size()] : null;
this.ttls = collectTTLs ? new int[columns.size()] : null;
this.now = now;
}
public void add(ByteBuffer v)
{
current.add(v);
}
public void add(Column c)
{
current.add(isDead(c) ? null : value(c));
if (timestamps != null)
{
timestamps[current.size() - 1] = isDead(c) ? Long.MIN_VALUE : c.timestamp();
}
if (ttls != null)
{
int ttl = -1;
if (!isDead(c) && c instanceof ExpiringColumn)
ttl = c.getLocalDeletionTime() - (int) (now / 1000);
ttls[current.size() - 1] = ttl;
}
}
private boolean isDead(Column c)
{
return c == null || c.isMarkedForDelete(now);
}
public void newRow() throws InvalidRequestException
{
if (current != null)
resultSet.addRow(handleRow(this));
current = new ArrayList<ByteBuffer>(columns.size());
}
public ResultSet build() throws InvalidRequestException
{
if (current != null)
{
resultSet.addRow(handleRow(this));
current = null;
}
return resultSet;
}
}
// Special cased selection for when no function is used (this save some allocations).
private static class SimpleSelection extends Selection
{
private final boolean isWildcard;
public SimpleSelection(List<CFDefinition.Name> columns, boolean isWildcard)
{
this(columns, SelectionColumnMapping.simpleMapping(columns), isWildcard);
}
public SimpleSelection(List<CFDefinition.Name> columns, SelectionColumnMapping columnMapping, boolean isWildcard)
{
/*
* In theory, even a simple selection could have multiple time the same column, so we
* could filter those duplicate out of columns. But since we're very unlikely to
* get much duplicate in practice, it's more efficient not to bother.
*/
super(columns, columnMapping, false, false);
this.isWildcard = isWildcard;
}
protected List<ByteBuffer> handleRow(ResultSetBuilder rs)
{
return rs.current;
}
@Override
public boolean isWildcard()
{
return isWildcard;
}
}
private static class SelectionWithProcessing extends Selection
{
private final List<Selector> selectors;
public SelectionWithProcessing(List<CFDefinition.Name> columns, SelectionColumns columnMapping, List<Selector> selectors, boolean collectTimestamps, boolean collectTTLs)
{
super(columns, columnMapping, collectTimestamps, collectTTLs);
this.selectors = selectors;
}
protected List<ByteBuffer> handleRow(ResultSetBuilder rs) throws InvalidRequestException
{
List<ByteBuffer> result = new ArrayList<ByteBuffer>();
for (Selector selector : selectors)
{
result.add(selector.compute(rs));
}
return result;
}
}
private interface Selector extends AssignementTestable
{
public ByteBuffer compute(ResultSetBuilder rs) throws InvalidRequestException;
/** Returns true if the selector acts on a column's timestamp, false otherwise. */
public boolean usesTimestamps();
/** Returns true if the selector acts on a column's TTL, false otherwise. */
public boolean usesTTLs();
}
private static class SimpleSelector implements Selector
{
private final String columnName;
private final int idx;
private final AbstractType<?> type;
public SimpleSelector(String columnName, int idx, AbstractType<?> type)
{
this.columnName = columnName;
this.idx = idx;
this.type = type;
}
public ByteBuffer compute(ResultSetBuilder rs)
{
return rs.current.get(idx);
}
public boolean isAssignableTo(ColumnSpecification receiver)
{
return receiver.type.isValueCompatibleWith(type);
}
public boolean usesTimestamps()
{
return false;
}
public boolean usesTTLs()
{
return false;
}
@Override
public String toString()
{
return columnName;
}
}
private static class FunctionSelector implements Selector
{
private final Function fun;
private final List<Selector> argSelectors;
public FunctionSelector(Function fun, List<Selector> argSelectors)
{
this.fun = fun;
this.argSelectors = argSelectors;
}
public ByteBuffer compute(ResultSetBuilder rs) throws InvalidRequestException
{
List<ByteBuffer> args = new ArrayList<ByteBuffer>(argSelectors.size());
for (Selector s : argSelectors)
args.add(s.compute(rs));
return fun.execute(args);
}
public boolean isAssignableTo(ColumnSpecification receiver)
{
return receiver.type.isValueCompatibleWith(fun.returnType());
}
public boolean usesTimestamps()
{
for (Selector s : argSelectors)
if (s.usesTimestamps())
return true;
return false;
}
public boolean usesTTLs()
{
for (Selector s : argSelectors)
if (s.usesTTLs())
return true;
return false;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(fun.name()).append("(");
for (int i = 0; i < argSelectors.size(); i++)
{
if (i > 0)
sb.append(", ");
sb.append(argSelectors.get(i));
}
return sb.append(")").toString();
}
}
private static class WritetimeOrTTLSelector implements Selector
{
private final String columnName;
private final int idx;
private final boolean isWritetime;
public WritetimeOrTTLSelector(String columnName, int idx, boolean isWritetime)
{
this.columnName = columnName;
this.idx = idx;
this.isWritetime = isWritetime;
}
public ByteBuffer compute(ResultSetBuilder rs)
{
if (isWritetime)
{
long ts = rs.timestamps[idx];
return ts != Long.MIN_VALUE ? ByteBufferUtil.bytes(ts) : null;
}
int ttl = rs.ttls[idx];
return ttl > 0 ? ByteBufferUtil.bytes(ttl) : null;
}
public boolean isAssignableTo(ColumnSpecification receiver)
{
return receiver.type.isValueCompatibleWith(isWritetime ? LongType.instance : Int32Type.instance);
}
public boolean usesTimestamps()
{
return isWritetime;
}
public boolean usesTTLs()
{
return !isWritetime;
}
@Override
public String toString()
{
return columnName;
}
}
}