/*
* 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.ArrayList;
import java.util.List;
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> columnsList;
private final List<ColumnSpecification> metadata;
private final boolean collectTimestamps;
private final boolean collectTTLs;
protected Selection(List<CFDefinition.Name> columnsList, List<ColumnSpecification> metadata, boolean collectTimestamps, boolean collectTTLs)
{
this.columnsList = columnsList;
this.metadata = metadata;
this.collectTimestamps = collectTimestamps;
this.collectTTLs = collectTTLs;
}
public ResultSet.Metadata getResultMetadata()
{
return new ResultSet.Metadata(metadata);
}
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);
}
public static Selection forColumns(List<CFDefinition.Name> columnsList)
{
return new SimpleSelection(columnsList);
}
private static boolean isUsingFunction(List<RawSelector> rawSelectors)
{
for (RawSelector rawSelector : rawSelectors)
{
if (!(rawSelector.selectable instanceof ColumnIdentifier))
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, List<ColumnSpecification> metadata) throws InvalidRequestException
{
if (raw.selectable instanceof ColumnIdentifier)
{
CFDefinition.Name name = cfDef.get((ColumnIdentifier)raw.selectable);
if (name == null)
throw new InvalidRequestException(String.format("Undefined name %s in selection clause", raw.selectable));
if (metadata != null)
metadata.add(raw.alias == null ? name : makeAliasSpec(cfDef, name.type, raw.alias));
return new SimpleSelector(name.toString(), addAndGetIndex(name, names), name.type);
}
else if (raw.selectable instanceof Selectable.WritetimeOrTTL)
{
Selectable.WritetimeOrTTL tot = (Selectable.WritetimeOrTTL)raw.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.kind != CFDefinition.Name.Kind.COLUMN_METADATA && name.kind != CFDefinition.Name.Kind.VALUE_ALIAS)
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 (metadata != null)
metadata.add(makeWritetimeOrTTLSpec(cfDef, tot, raw.alias));
return new WritetimeOrTTLSelector(name.toString(), addAndGetIndex(name, names), tot.isWritetime);
}
else
{
Selectable.WithFunction withFun = (Selectable.WithFunction)raw.selectable;
List<Selector> args = new ArrayList<Selector>(withFun.args.size());
for (Selectable rawArg : withFun.args)
args.add(makeSelector(cfDef, new RawSelector(rawArg, null), names, null));
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, raw.alias);
Function fun = Functions.get(withFun.functionName, args, spec);
if (metadata != null)
metadata.add(spec);
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 usesFunction = isUsingFunction(rawSelectors);
if (usesFunction)
{
List<CFDefinition.Name> names = new ArrayList<CFDefinition.Name>();
List<ColumnSpecification> metadata = new ArrayList<ColumnSpecification>(rawSelectors.size());
List<Selector> selectors = new ArrayList<Selector>(rawSelectors.size());
boolean collectTimestamps = false;
boolean collectTTLs = false;
for (RawSelector rawSelector : rawSelectors)
{
Selector selector = makeSelector(cfDef, rawSelector, names, metadata);
selectors.add(selector);
if (selector instanceof WritetimeOrTTLSelector)
{
collectTimestamps |= ((WritetimeOrTTLSelector)selector).isWritetime;
collectTTLs |= !((WritetimeOrTTLSelector)selector).isWritetime;
}
}
return new SelectionWithFunctions(names, metadata, selectors, collectTimestamps, collectTTLs);
}
else
{
List<CFDefinition.Name> names = new ArrayList<CFDefinition.Name>(rawSelectors.size());
List<ColumnSpecification> metadata = new ArrayList<ColumnSpecification>(rawSelectors.size());
for (RawSelector rawSelector : rawSelectors)
{
assert rawSelector.selectable instanceof ColumnIdentifier;
CFDefinition.Name name = cfDef.get((ColumnIdentifier)rawSelector.selectable);
if (name == null)
throw new InvalidRequestException(String.format("Undefined name %s in selection clause", rawSelector.selectable));
names.add(name);
metadata.add(rawSelector.alias == null ? name : makeAliasSpec(cfDef, name.type, rawSelector.alias));
}
return new SimpleSelection(names, metadata);
}
}
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> regularColumnsToFetch()
{
List<ColumnIdentifier> toFetch = new ArrayList<ColumnIdentifier>();
for (CFDefinition.Name name : columnsList)
{
if (name.kind == CFDefinition.Name.Kind.COLUMN_METADATA)
toFetch.add(name.name);
}
return toFetch;
}
/**
* @return the list of CQL3 columns value this SelectionClause needs.
*/
public List<CFDefinition.Name> getColumnsList()
{
return columnsList;
}
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(metadata);
this.timestamps = collectTimestamps ? new long[columnsList.size()] : null;
this.ttls = collectTTLs ? new int[columnsList.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) ? -1 : 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>(columnsList.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
{
public SimpleSelection(List<CFDefinition.Name> columnsList)
{
this(columnsList, new ArrayList<ColumnSpecification>(columnsList));
}
public SimpleSelection(List<CFDefinition.Name> columnsList, List<ColumnSpecification> metadata)
{
/*
* In theory, even a simple selection could have multiple time the same column, so we
* could filter those duplicate out of columnsList. But since we're very unlikely to
* get much duplicate in practice, it's more efficient not to bother.
*/
super(columnsList, metadata, false, false);
}
protected List<ByteBuffer> handleRow(ResultSetBuilder rs)
{
return rs.current;
}
}
private interface Selector extends AssignementTestable
{
public ByteBuffer compute(ResultSetBuilder rs) throws InvalidRequestException;
}
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 type.asCQL3Type().equals(receiver.type.asCQL3Type());
}
@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 fun.returnType().asCQL3Type().equals(receiver.type.asCQL3Type());
}
@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 >= 0 ? ByteBufferUtil.bytes(ts) : null;
}
int ttl = rs.ttls[idx];
return ttl > 0 ? ByteBufferUtil.bytes(ttl) : null;
}
public boolean isAssignableTo(ColumnSpecification receiver)
{
return receiver.type.asCQL3Type().equals(isWritetime ? CQL3Type.Native.BIGINT : CQL3Type.Native.INT);
}
@Override
public String toString()
{
return columnName;
}
}
private static class SelectionWithFunctions extends Selection
{
private final List<Selector> selectors;
public SelectionWithFunctions(List<CFDefinition.Name> columnsList, List<ColumnSpecification> metadata, List<Selector> selectors, boolean collectTimestamps, boolean collectTTLs)
{
super(columnsList, metadata, 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;
}
}
}