blob: 5eace66fbe625346c2e13770e04b6d3b453002fc [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.selection;
import java.nio.ByteBuffer;
import org.apache.cassandra.cql3.ColumnSpecification;
import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.Term;
import org.apache.cassandra.cql3.selection.SimpleSelector.SimpleSelectorFactory;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.db.rows.CellPath;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.ByteBufferUtil;
/**
* Selector class handling element (c[x]) and slice (c[x..y]) selections over collections.
*/
abstract class ElementsSelector extends Selector
{
protected final Selector selected;
protected ElementsSelector(Selector selected)
{
this.selected = selected;
}
private static boolean isUnset(ByteBuffer bb)
{
return bb == ByteBufferUtil.UNSET_BYTE_BUFFER;
}
// For sets and maps, return the type corresponding to the element of a selection (that is, x in c[x]).
private static AbstractType<?> keyType(CollectionType<?> type)
{
return type.nameComparator();
}
// For sets and maps, return the type corresponding to the result of a selection (that is, c[x] in c[x]).
public static AbstractType<?> valueType(CollectionType<?> type)
{
return type instanceof MapType ? type.valueComparator() : type.nameComparator();
}
private static CollectionType<?> getCollectionType(Selector selected)
{
AbstractType<?> type = selected.getType();
if (type instanceof ReversedType)
type = ((ReversedType<?>) type).baseType;
assert type instanceof MapType || type instanceof SetType : "this shouldn't have passed validation in Selectable";
return (CollectionType<?>) type;
}
private static abstract class AbstractFactory extends Factory
{
protected final String name;
protected final Selector.Factory factory;
protected final CollectionType<?> type;
protected AbstractFactory(String name, Selector.Factory factory, CollectionType<?> type)
{
this.name = name;
this.factory = factory;
this.type = type;
}
protected String getColumnName()
{
return name;
}
protected void addColumnMapping(SelectionColumnMapping mapping, ColumnSpecification resultsColumn)
{
factory.addColumnMapping(mapping, resultsColumn);
}
public boolean isAggregateSelectorFactory()
{
return factory.isAggregateSelectorFactory();
}
}
/**
* Creates a {@code Selector.Factory} for the selection of an element of a collection.
*
* @param name a string representing the selection the factory is for. Something like "c[x]".
* @param factory the {@code Selector.Factory} corresponding to the collection on which an element
* is selected.
* @param type the type of the collection.
* @param key the element within the value represented by {@code factory} that is selected.
* @return the created factory.
*/
public static Factory newElementFactory(String name, Selector.Factory factory, CollectionType<?> type, final Term key)
{
return new AbstractFactory(name, factory, type)
{
protected AbstractType<?> getReturnType()
{
return valueType(type);
}
public Selector newInstance(QueryOptions options) throws InvalidRequestException
{
ByteBuffer keyValue = key.bindAndGet(options);
if (keyValue == null)
throw new InvalidRequestException("Invalid null value for element selection on " + factory.getColumnName());
if (keyValue == ByteBufferUtil.UNSET_BYTE_BUFFER)
throw new InvalidRequestException("Invalid unset value for element selection on " + factory.getColumnName());
return new ElementSelector(factory.newInstance(options), keyValue);
}
public boolean areAllFetchedColumnsKnown()
{
// If we known all the fetched columns, it means that we don't have to wait execution to create
// the ColumnFilter (through addFetchedColumns below).
// That's the case if either there is no particular subselection
// to add, or if there is one but the selected key is terminal. In other words,
// we known all the fetched columns if all the feched columns of the factory are known and either:
// 1) the type is frozen (in which case there isn't subselection to do).
// 2) the factory (the left-hand-side) isn't a simple column selection (here again, no
// subselection we can do).
// 3) the element selected is terminal.
return factory.areAllFetchedColumnsKnown()
&& (!type.isMultiCell() || !factory.isSimpleSelectorFactory() || key.isTerminal());
}
public void addFetchedColumns(ColumnFilter.Builder builder)
{
if (!type.isMultiCell() || !factory.isSimpleSelectorFactory())
{
factory.addFetchedColumns(builder);
return;
}
ColumnMetadata column = ((SimpleSelectorFactory) factory).getColumn();
builder.select(column, CellPath.create(((Term.Terminal)key).get(ProtocolVersion.V3)));
}
};
}
/**
* Creates a {@code Selector.Factory} for the selection of a slice of a collection.
*
* @param name a string representing the selection the factory is for. Something like "c[x..y]".
* @param factory the {@code Selector.Factory} corresponding to the collection on which a slice
* is selected.
* @param type the type of the collection.
* @param from the starting bound of the selected slice. This cannot be {@code null} but can be
* {@code Constants.UNSET_VALUE} if the slice doesn't have a start.
* @param to the ending bound of the selected slice. This cannot be {@code null} but can be
* {@code Constants.UNSET_VALUE} if the slice doesn't have an end.
* @return the created factory.
*/
public static Factory newSliceFactory(String name, Selector.Factory factory, CollectionType<?> type, final Term from, final Term to)
{
return new AbstractFactory(name, factory, type)
{
protected AbstractType<?> getReturnType()
{
return type;
}
public Selector newInstance(QueryOptions options) throws InvalidRequestException
{
ByteBuffer fromValue = from.bindAndGet(options);
ByteBuffer toValue = to.bindAndGet(options);
// Note that we use UNSET values to represent no bound, so null is truly invalid
if (fromValue == null || toValue == null)
throw new InvalidRequestException("Invalid null value for slice selection on " + factory.getColumnName());
return new SliceSelector(factory.newInstance(options), from.bindAndGet(options), to.bindAndGet(options));
}
public boolean areAllFetchedColumnsKnown()
{
// If we known all the fetched columns, it means that we don't have to wait execution to create
// the ColumnFilter (through addFetchedColumns below).
// That's the case if either there is no particular subselection
// to add, or if there is one but the selected bound are terminal. In other words,
// we known all the fetched columns if all the feched columns of the factory are known and either:
// 1) the type is frozen (in which case there isn't subselection to do).
// 2) the factory (the left-hand-side) isn't a simple column selection (here again, no
// subselection we can do).
// 3) the bound of the selected slice are terminal.
return factory.areAllFetchedColumnsKnown()
&& (!type.isMultiCell() || !factory.isSimpleSelectorFactory() || (from.isTerminal() && to.isTerminal()));
}
public void addFetchedColumns(ColumnFilter.Builder builder)
{
if (!type.isMultiCell() || !factory.isSimpleSelectorFactory())
{
factory.addFetchedColumns(builder);
return;
}
ColumnMetadata column = ((SimpleSelectorFactory) factory).getColumn();
ByteBuffer fromBB = ((Term.Terminal)from).get(ProtocolVersion.V3);
ByteBuffer toBB = ((Term.Terminal)to).get(ProtocolVersion.V3);
builder.slice(column, isUnset(fromBB) ? CellPath.BOTTOM : CellPath.create(fromBB), isUnset(toBB) ? CellPath.TOP : CellPath.create(toBB));
}
};
}
public ByteBuffer getOutput(ProtocolVersion protocolVersion) throws InvalidRequestException
{
ByteBuffer value = selected.getOutput(protocolVersion);
return value == null ? null : extractSelection(value);
}
protected abstract ByteBuffer extractSelection(ByteBuffer collection);
public void addInput(ProtocolVersion protocolVersion, ResultSetBuilder rs) throws InvalidRequestException
{
selected.addInput(protocolVersion, rs);
}
public void reset()
{
selected.reset();
}
private static class ElementSelector extends ElementsSelector
{
private final CollectionType<?> type;
private final ByteBuffer key;
private ElementSelector(Selector selected, ByteBuffer key)
{
super(selected);
this.type = getCollectionType(selected);
this.key = key;
}
public void addFetchedColumns(ColumnFilter.Builder builder)
{
if (type.isMultiCell() && selected instanceof SimpleSelector)
{
ColumnMetadata column = ((SimpleSelector)selected).column;
builder.select(column, CellPath.create(key));
}
else
{
selected.addFetchedColumns(builder);
}
}
protected ByteBuffer extractSelection(ByteBuffer collection)
{
return type.getSerializer().getSerializedValue(collection, key, keyType(type));
}
public AbstractType<?> getType()
{
return valueType(type);
}
@Override
public String toString()
{
return String.format("%s[%s]", selected, keyType(type).getString(key));
}
}
private static class SliceSelector extends ElementsSelector
{
private final CollectionType<?> type;
// Note that neither from nor to can be null, but they can both be ByteBufferUtil.UNSET_BYTE_BUFFER to represent no particular bound
private final ByteBuffer from;
private final ByteBuffer to;
private SliceSelector(Selector selected, ByteBuffer from, ByteBuffer to)
{
super(selected);
assert from != null && to != null : "We can have unset buffers, but not nulls";
this.type = getCollectionType(selected);
this.from = from;
this.to = to;
}
public void addFetchedColumns(ColumnFilter.Builder builder)
{
if (type.isMultiCell() && selected instanceof SimpleSelector)
{
ColumnMetadata column = ((SimpleSelector)selected).column;
builder.slice(column, isUnset(from) ? CellPath.BOTTOM : CellPath.create(from), isUnset(to) ? CellPath.TOP : CellPath.create(to));
}
else
{
selected.addFetchedColumns(builder);
}
}
protected ByteBuffer extractSelection(ByteBuffer collection)
{
return type.getSerializer().getSliceFromSerialized(collection, from, to, type.nameComparator(), type.isFrozenCollection());
}
public AbstractType<?> getType()
{
return type;
}
@Override
public String toString()
{
boolean fromUnset = isUnset(from);
boolean toUnset = isUnset(to);
return fromUnset && toUnset
? selected.toString()
: String.format("%s[%s..%s]", selected, fromUnset ? "" : keyType(type).getString(from), toUnset ? "" : keyType(type).getString(to));
}
}
}