blob: 9476deaf03f54d0031adda8a87b57181e78893a6 [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.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.functions.*;
import org.apache.cassandra.cql3.selection.Selector.Factory;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.Pair;
import static org.apache.cassandra.cql3.selection.SelectorFactories.createFactoriesAndCollectColumnDefinitions;
import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
public interface Selectable extends AssignmentTestable
{
public Selector.Factory newSelectorFactory(TableMetadata table, AbstractType<?> expectedType, List<ColumnMetadata> defs, VariableSpecifications boundNames);
/**
* The type of the {@code Selectable} if it can be infered.
*
* @param keyspace the keyspace on which the statement for which this is a
* {@code Selectable} is on.
* @return the type of this {@code Selectable} if inferrable, or {@code null}
* otherwise (for instance, the type isn't inferable for a bind marker. Even for
* literals, the exact type is not inferrable since they are valid for many
* different types and so this will return {@code null} too).
*/
public AbstractType<?> getExactTypeIfKnown(String keyspace);
/**
* Checks if this {@code Selectable} select columns matching the specified predicate.
* @return {@code true} if this {@code Selectable} select columns matching the specified predicate,
* {@code false} otherwise.
*/
public boolean selectColumns(Predicate<ColumnMetadata> predicate);
/**
* Checks if the specified Selectables select columns matching the specified predicate.
* @param selectables the selectables to check.
* @return {@code true} if the specified Selectables select columns matching the specified predicate,
{@code false} otherwise.
*/
public static boolean selectColumns(List<Selectable> selectables, Predicate<ColumnMetadata> predicate)
{
for (Selectable selectable : selectables)
{
if (selectable.selectColumns(predicate))
return true;
}
return false;
}
/**
* Checks if any processing is performed on the selected columns, {@code false} otherwise.
* @return {@code true} if any processing is performed on the selected columns, {@code false} otherwise.
*/
public default boolean processesSelection()
{
// ColumnMetadata is the only case that returns false (if the column is not masked) and overrides this
return true;
}
// Term.Raw overrides this since some literals can be WEAKLY_ASSIGNABLE
default public TestResult testAssignment(String keyspace, ColumnSpecification receiver)
{
AbstractType<?> type = getExactTypeIfKnown(keyspace);
return type == null ? TestResult.NOT_ASSIGNABLE : type.testAssignment(keyspace, receiver);
}
@Override
public default AbstractType<?> getCompatibleTypeIfKnown(String keyspace)
{
return getExactTypeIfKnown(keyspace);
}
default int addAndGetIndex(ColumnMetadata def, List<ColumnMetadata> l)
{
int idx = l.indexOf(def);
if (idx < 0)
{
idx = l.size();
l.add(def);
}
return idx;
}
default ColumnSpecification specForElementOrSlice(Selectable selected, ColumnSpecification receiver, CollectionType.Kind kind, String selectionType)
{
switch (kind)
{
case LIST: throw new InvalidRequestException(String.format("%s selection is only allowed on sets and maps, but %s is a list", selectionType, selected));
case SET: return Sets.valueSpecOf(receiver);
case MAP: return Maps.keySpecOf(receiver);
default: throw new AssertionError();
}
}
public interface Raw
{
public Selectable prepare(TableMetadata table);
}
public static class WithTerm implements Selectable
{
/**
* The names given to unamed bind markers found in selection. In selection clause, we often don't have a good
* name for bind markers, typically if you have:
* SELECT (int)? FROM foo;
* there isn't a good name for that marker. So we give the same name to all the markers. Note that we could try
* to differenciate the names by using some increasing number in the name (so [selection_1], [selection_2], ...)
* but it's actually not trivial to do in the current code and it's not really more helpful since if users wants
* to bind by position (which they will have to in this case), they can do so at the driver level directly. And
* so we don't bother.
* Note that users should really be using named bind markers if they want to be able to bind by names.
*/
private static final ColumnIdentifier bindMarkerNameInSelection = new ColumnIdentifier("[selection]", true);
private final Term.Raw rawTerm;
public WithTerm(Term.Raw rawTerm)
{
this.rawTerm = rawTerm;
}
@Override
public TestResult testAssignment(String keyspace, ColumnSpecification receiver)
{
return rawTerm.testAssignment(keyspace, receiver);
}
public Selector.Factory newSelectorFactory(TableMetadata table, AbstractType<?> expectedType, List<ColumnMetadata> defs, VariableSpecifications boundNames) throws InvalidRequestException
{
/*
* expectedType will be null if we have no constraint on what the type should be. For instance, if this term is a bind marker:
* - it will be null if we do "SELECT ? FROM foo"
* - it won't be null (and be LongType) if we do "SELECT bigint_as_blob(?) FROM foo" because the function constrain it.
*
* In the first case, we have to error out: we need to infer the type of the metadata of a SELECT at preparation time, which we can't
* here (users will have to do "SELECT (varint)? FROM foo" for instance).
* But in the 2nd case, we're fine and can use the expectedType to "prepare" the bind marker/collect the bound type.
*
* Further, the term might not be a bind marker, in which case we sometimes can default to some most-general type. For instance, in
* SELECT 3 FROM foo
* we'll just default the type to 'varint' as that's the most generic type for the literal '3' (this is mostly for convenience, the query
* is not terribly useful in practice and use can force the type as for the bind marker case through "SELECT (int)3 FROM foo").
* But note that not all literals can have such default type. For instance, there is no way to infer the type of a UDT literal in a vacuum,
* and so we simply error out if we have something like:
* SELECT { foo: 'bar' } FROM foo
*
* Lastly, note that if the term is a terminal literal, we don't have to check it's compatibility with 'expectedType' as any incompatibility
* would have been found at preparation time.
*/
AbstractType<?> type = getExactTypeIfKnown(table.keyspace);
if (type == null)
{
type = expectedType;
if (type == null)
throw new InvalidRequestException("Cannot infer type for term " + this + " in selection clause (try using a cast to force a type)");
}
// The fact we default the name to "[selection]" inconditionally means that any bind marker in a
// selection will have this name. Which isn't terribly helpful, but it's unclear how to provide
// something a lot more helpful and in practice user can bind those markers by position or, even better,
// use bind markers.
Term term = rawTerm.prepare(table.keyspace, new ColumnSpecification(table.keyspace, table.name, bindMarkerNameInSelection, type));
term.collectMarkerSpecification(boundNames);
return TermSelector.newFactory(rawTerm.getText(), term, type);
}
@Override
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
return rawTerm.getExactTypeIfKnown(keyspace);
}
@Override
public AbstractType<?> getCompatibleTypeIfKnown(String keyspace)
{
return rawTerm.getCompatibleTypeIfKnown(keyspace);
}
@Override
public boolean selectColumns(Predicate<ColumnMetadata> predicate)
{
return false;
}
@Override
public String toString()
{
return rawTerm.getText();
}
public static class Raw implements Selectable.Raw
{
private final Term.Raw term;
public Raw(Term.Raw term)
{
this.term = term;
}
public Selectable prepare(TableMetadata table)
{
return new WithTerm(term);
}
}
}
public static class WritetimeOrTTL implements Selectable
{
// The order of the variants in the Kind enum matters as they are used in ser/deser
public enum Kind
{
TTL("ttl", Int32Type.instance),
WRITE_TIME("writetime", LongType.instance),
MAX_WRITE_TIME("maxwritetime", LongType.instance); // maxwritetime is available after Cassandra 4.1 (exclusive)
public final String name;
public final AbstractType<?> returnType;
public static Kind fromOrdinal(int ordinal)
{
return values()[ordinal];
}
Kind(String name, AbstractType<?> returnType)
{
this.name = name;
this.returnType = returnType;
}
public boolean aggregatesMultiCell()
{
return this == MAX_WRITE_TIME;
}
}
public final ColumnMetadata column;
public final Selectable selectable;
public final Kind kind;
public WritetimeOrTTL(ColumnMetadata column, Selectable selectable, Kind kind)
{
this.column = column;
this.selectable = selectable;
this.kind = kind;
}
@Override
public String toString()
{
return kind.name + "(" + selectable + ")";
}
public Selector.Factory newSelectorFactory(TableMetadata table,
AbstractType<?> expectedType,
List<ColumnMetadata> defs,
VariableSpecifications boundNames)
{
if (column.isPrimaryKeyColumn())
throw new InvalidRequestException(
String.format("Cannot use selection function %s on PRIMARY KEY part %s",
kind.name,
column.name));
Selector.Factory factory = selectable.newSelectorFactory(table, expectedType, defs, boundNames);
boolean isMultiCell = factory.getColumnSpecification(table).type.isMultiCell();
return WritetimeOrTTLSelector.newFactory(factory, addAndGetIndex(column, defs), kind, isMultiCell);
}
@Override
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
AbstractType<?> type = kind.returnType;
return column.type.isMultiCell() && !kind.aggregatesMultiCell() ? ListType.getInstance(type, false) : type;
}
@Override
public boolean selectColumns(Predicate<ColumnMetadata> predicate)
{
return selectable.selectColumns(predicate);
}
public static class Raw implements Selectable.Raw
{
private final Selectable.RawIdentifier column;
private final Selectable.Raw selected;
private final Kind kind;
public Raw(Selectable.RawIdentifier column, Selectable.Raw selected, Kind kind)
{
this.column = column;
this.selected = selected;
this.kind = kind;
}
@Override
public WritetimeOrTTL prepare(TableMetadata table)
{
return new WritetimeOrTTL((ColumnMetadata) column.prepare(table), selected.prepare(table), kind);
}
}
}
public static class WithFunction implements Selectable
{
public final Function function;
public final List<Selectable> args;
public WithFunction(Function function, List<Selectable> args)
{
this.function = function;
this.args = args;
}
@Override
public String toString()
{
return function.columnName(args.stream().map(Object::toString).collect(Collectors.toList()));
}
public Selector.Factory newSelectorFactory(TableMetadata table, AbstractType<?> expectedType, List<ColumnMetadata> defs, VariableSpecifications boundNames)
{
SelectorFactories factories = SelectorFactories.createFactoriesAndCollectColumnDefinitions(args, function.argTypes(), table, defs, boundNames);
return AbstractFunctionSelector.newFactory(function, factories);
}
@Override
public boolean selectColumns(Predicate<ColumnMetadata> predicate)
{
return Selectable.selectColumns(args, predicate);
}
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
return function.returnType();
}
public static class Raw implements Selectable.Raw
{
private final FunctionName functionName;
private final List<Selectable.Raw> args;
public Raw(FunctionName functionName, List<Selectable.Raw> args)
{
this.functionName = functionName;
this.args = args;
}
public static Raw newCountRowsFunction()
{
return new Raw(AggregateFcts.countRowsFunction.name(),
Collections.emptyList());
}
public static Raw newOperation(char operator, Selectable.Raw left, Selectable.Raw right)
{
return new Raw(OperationFcts.getFunctionNameFromOperator(operator),
Arrays.asList(left, right));
}
public static Raw newNegation(Selectable.Raw arg)
{
return new Raw(FunctionName.nativeFunction(OperationFcts.NEGATION_FUNCTION_NAME),
Collections.singletonList(arg));
}
@Override
public Selectable prepare(TableMetadata table)
{
List<Selectable> preparedArgs = new ArrayList<>(args.size());
for (Selectable.Raw arg : args)
preparedArgs.add(arg.prepare(table));
FunctionName name = functionName;
// COUNT(x) is equivalent to COUNT(*) for any non-null term x (since count(x) don't care about its
// argument outside of check for nullness) and for backward compatibilty we want to support COUNT(1),
// but we actually have COUNT(x) method for every existing (simple) input types so currently COUNT(1)
// will throw as ambiguous (since 1 works for any type). So we have to special case COUNT.
if (functionName.equalsNativeFunction(FunctionName.nativeFunction("count"))
&& preparedArgs.size() == 1
&& (preparedArgs.get(0) instanceof WithTerm)
&& (((WithTerm)preparedArgs.get(0)).rawTerm instanceof Constants.Literal))
{
// Note that 'null' isn't a Constants.Literal
name = AggregateFcts.countRowsFunction.name();
preparedArgs = Collections.emptyList();
}
Function fun = FunctionResolver.get(table.keyspace, name, preparedArgs, table.keyspace, table.name, null);
if (fun == null)
throw new InvalidRequestException(String.format("Unknown function '%s'", functionName));
if (fun.returnType() == null)
throw new InvalidRequestException(String.format("Unknown function %s called in selection clause", functionName));
return new WithFunction(fun, preparedArgs);
}
}
}
public static class WithCast implements Selectable
{
private final CQL3Type type;
private final Selectable arg;
public WithCast(Selectable arg, CQL3Type type)
{
this.arg = arg;
this.type = type;
}
@Override
public String toString()
{
return String.format("cast(%s as %s)", arg, type.toString().toLowerCase());
}
public Selector.Factory newSelectorFactory(TableMetadata table, AbstractType<?> expectedType, List<ColumnMetadata> defs, VariableSpecifications boundNames)
{
List<Selectable> args = Collections.singletonList(arg);
SelectorFactories factories = SelectorFactories.createFactoriesAndCollectColumnDefinitions(args, null, table, defs, boundNames);
Selector.Factory factory = factories.get(0);
// If the user is trying to cast a type on its own type we simply ignore it.
if (type.getType().equals(factory.getReturnType()))
return factory;
FunctionName name = FunctionName.nativeFunction(CastFcts.getFunctionName(type));
Function fun = FunctionResolver.get(table.keyspace, name, args, table.keyspace, table.name, null);
if (fun == null)
{
throw new InvalidRequestException(String.format("%s cannot be cast to %s",
defs.get(0).name,
type));
}
return AbstractFunctionSelector.newFactory(fun, factories);
}
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
return type.getType();
}
@Override
public boolean selectColumns(Predicate<ColumnMetadata> predicate)
{
return arg.selectColumns(predicate);
}
public static class Raw implements Selectable.Raw
{
private final CQL3Type type;
private final Selectable.Raw arg;
public Raw(Selectable.Raw arg, CQL3Type type)
{
this.arg = arg;
this.type = type;
}
public WithCast prepare(TableMetadata table)
{
return new WithCast(arg.prepare(table), type);
}
}
}
/**
* Represents the selection of the field of a UDT (eg. t.f).
*/
public static class WithFieldSelection implements Selectable
{
public final Selectable selected;
public final FieldIdentifier field;
public WithFieldSelection(Selectable selected, FieldIdentifier field)
{
this.selected = selected;
this.field = field;
}
@Override
public String toString()
{
return String.format("%s.%s", selected, field);
}
public Selector.Factory newSelectorFactory(TableMetadata table, AbstractType<?> expectedType, List<ColumnMetadata> defs, VariableSpecifications boundNames)
{
AbstractType<?> expectedUdtType = null;
// If the UDT is between parentheses, we know that it is not a tuple with a single element.
if (selected instanceof BetweenParenthesesOrWithTuple)
{
BetweenParenthesesOrWithTuple betweenParentheses = (BetweenParenthesesOrWithTuple) selected;
expectedUdtType = betweenParentheses.selectables.get(0).getExactTypeIfKnown(table.keyspace);
}
Selector.Factory factory = selected.newSelectorFactory(table, expectedUdtType, defs, boundNames);
AbstractType<?> type = factory.getReturnType();
if (!type.isUDT())
{
throw new InvalidRequestException(
String.format("Invalid field selection: %s of type %s is not a user type",
selected,
type.asCQL3Type()));
}
UserType ut = (UserType) type;
int fieldIndex = ut.fieldPosition(field);
if (fieldIndex == -1)
{
throw new InvalidRequestException(String.format("%s of type %s has no field %s",
selected, type.asCQL3Type(), field));
}
return FieldSelector.newFactory(ut, fieldIndex, factory);
}
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
AbstractType<?> selectedType = selected.getExactTypeIfKnown(keyspace);
if (selectedType == null || !(selectedType instanceof UserType))
return null;
UserType ut = (UserType) selectedType;
int fieldIndex = ut.fieldPosition(field);
if (fieldIndex == -1)
return null;
return ut.fieldType(fieldIndex);
}
@Override
public boolean selectColumns(Predicate<ColumnMetadata> predicate)
{
return selected.selectColumns(predicate);
}
public static class Raw implements Selectable.Raw
{
private final Selectable.Raw selected;
private final FieldIdentifier field;
public Raw(Selectable.Raw selected, FieldIdentifier field)
{
this.selected = selected;
this.field = field;
}
public WithFieldSelection prepare(TableMetadata table)
{
return new WithFieldSelection(selected.prepare(table), field);
}
}
}
/**
* {@code Selectable} for {@code Selectable} between parentheses or tuples.
* <p>The parser cannot differentiate between a single element between parentheses or a single element tuple.
* By consequence, we are forced to wait until the type is known to be able to differentiate them.</p>
*/
public static class BetweenParenthesesOrWithTuple implements Selectable
{
/**
* The tuple elements or the element between the parentheses
*/
private final List<Selectable> selectables;
public BetweenParenthesesOrWithTuple(List<Selectable> selectables)
{
this.selectables = selectables;
}
@Override
public TestResult testAssignment(String keyspace, ColumnSpecification receiver)
{
if (selectables.size() == 1 && !receiver.type.isTuple())
return selectables.get(0).testAssignment(keyspace, receiver);
return Tuples.testTupleAssignment(receiver, selectables);
}
@Override
public Factory newSelectorFactory(TableMetadata cfm,
AbstractType<?> expectedType,
List<ColumnMetadata> defs,
VariableSpecifications boundNames)
{
AbstractType<?> type = getExactTypeIfKnown(cfm.keyspace);
if (type == null)
{
type = expectedType;
if (type == null)
throw invalidRequest("Cannot infer type for term %s in selection clause (try using a cast to force a type)",
this);
}
if (selectables.size() == 1 && !type.isTuple())
return newBetweenParenthesesSelectorFactory(cfm, expectedType, defs, boundNames);
return newTupleSelectorFactory(cfm, (TupleType) type, defs, boundNames);
}
private Factory newBetweenParenthesesSelectorFactory(TableMetadata cfm,
AbstractType<?> expectedType,
List<ColumnMetadata> defs,
VariableSpecifications boundNames)
{
Selectable selectable = selectables.get(0);
final Factory factory = selectable.newSelectorFactory(cfm, expectedType, defs, boundNames);
return new ForwardingFactory()
{
protected Factory delegate()
{
return factory;
}
protected String getColumnName()
{
return String.format("(%s)", factory.getColumnName());
}
};
}
private Factory newTupleSelectorFactory(TableMetadata cfm,
TupleType tupleType,
List<ColumnMetadata> defs,
VariableSpecifications boundNames)
{
SelectorFactories factories = createFactoriesAndCollectColumnDefinitions(selectables,
tupleType.allTypes(),
cfm,
defs,
boundNames);
return TupleSelector.newFactory(tupleType, factories);
}
@Override
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
// If there is only one element we cannot know if it is an element between parentheses or a tuple
// with only one element. By consequence, we need to force the user to specify the type.
if (selectables.size() == 1)
return null;
return Tuples.getExactTupleTypeIfKnown(selectables, p -> p.getExactTypeIfKnown(keyspace));
}
@Override
public AbstractType<?> getCompatibleTypeIfKnown(String keyspace)
{
// If there is only one element we cannot know if it is an element between parentheses or a tuple
// with only one element. By consequence, we need to force the user to specify the type.
if (selectables.size() == 1)
return null;
return Tuples.getExactTupleTypeIfKnown(selectables, p -> p.getCompatibleTypeIfKnown(keyspace));
}
@Override
public boolean selectColumns(Predicate<ColumnMetadata> predicate)
{
return Selectable.selectColumns(selectables, predicate);
}
@Override
public String toString()
{
return Tuples.tupleToString(selectables);
}
public static class Raw implements Selectable.Raw
{
private final List<Selectable.Raw> raws;
public Raw(List<Selectable.Raw> raws)
{
this.raws = raws;
}
@Override
public Selectable prepare(TableMetadata cfm)
{
return new BetweenParenthesesOrWithTuple(raws.stream().map(p -> p.prepare(cfm)).collect(Collectors.toList()));
}
}
}
/**
* <code>Selectable</code> for literal Lists.
*/
public static class WithList implements Selectable
{
/**
* The list elements
*/
private final List<Selectable> selectables;
public WithList(List<Selectable> selectables)
{
this.selectables = selectables;
}
@Override
public TestResult testAssignment(String keyspace, ColumnSpecification receiver)
{
return Lists.testListAssignment(receiver, selectables);
}
@Override
public Factory newSelectorFactory(TableMetadata cfm,
AbstractType<?> expectedType,
List<ColumnMetadata> defs,
VariableSpecifications boundNames)
{
AbstractType<?> type = getExactTypeIfKnown(cfm.keyspace);
if (type == null)
{
type = expectedType;
if (type == null)
throw invalidRequest("Cannot infer type for term %s in selection clause (try using a cast to force a type)",
this);
}
ListType<?> listType = (ListType<?>) type;
List<AbstractType<?>> expectedTypes = new ArrayList<>(selectables.size());
for (int i = 0, m = selectables.size(); i < m; i++)
expectedTypes.add(listType.getElementsType());
SelectorFactories factories = createFactoriesAndCollectColumnDefinitions(selectables,
expectedTypes,
cfm,
defs,
boundNames);
return ListSelector.newFactory(type, factories);
}
@Override
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
return Lists.getExactListTypeIfKnown(selectables, p -> p.getExactTypeIfKnown(keyspace));
}
@Override
public AbstractType<?> getCompatibleTypeIfKnown(String keyspace)
{
return Lists.getPreferredCompatibleType(selectables, p -> p.getCompatibleTypeIfKnown(keyspace));
}
@Override
public boolean selectColumns(Predicate<ColumnMetadata> predicate)
{
return Selectable.selectColumns(selectables, predicate);
}
@Override
public String toString()
{
return Lists.listToString(selectables);
}
public static class Raw implements Selectable.Raw
{
private final List<Selectable.Raw> raws;
public Raw(List<Selectable.Raw> raws)
{
this.raws = raws;
}
@Override
public Selectable prepare(TableMetadata cfm)
{
return new WithList(raws.stream().map(p -> p.prepare(cfm)).collect(Collectors.toList()));
}
}
}
/**
* <code>Selectable</code> for literal Sets.
*/
public static class WithSet implements Selectable
{
/**
* The set elements
*/
private final List<Selectable> selectables;
public WithSet(List<Selectable> selectables)
{
this.selectables = selectables;
}
@Override
public TestResult testAssignment(String keyspace, ColumnSpecification receiver)
{
return Sets.testSetAssignment(receiver, selectables);
}
@Override
public Factory newSelectorFactory(TableMetadata cfm,
AbstractType<?> expectedType,
List<ColumnMetadata> defs,
VariableSpecifications boundNames)
{
AbstractType<?> type = getExactTypeIfKnown(cfm.keyspace);
if (type == null)
{
type = expectedType;
if (type == null)
throw invalidRequest("Cannot infer type for term %s in selection clause (try using a cast to force a type)",
this);
}
// The parser treats empty Maps as Sets so if the type is a MapType we know that the Map is empty
if (type instanceof MapType)
return MapSelector.newFactory(type, Collections.emptyList());
SetType<?> setType = (SetType<?>) type;
if (setType.getElementsType() == DurationType.instance)
throw invalidRequest("Durations are not allowed inside sets: %s", setType.asCQL3Type());
List<AbstractType<?>> expectedTypes = new ArrayList<>(selectables.size());
for (int i = 0, m = selectables.size(); i < m; i++)
expectedTypes.add(setType.getElementsType());
SelectorFactories factories = createFactoriesAndCollectColumnDefinitions(selectables,
expectedTypes,
cfm,
defs,
boundNames);
return SetSelector.newFactory(type, factories);
}
@Override
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
return Sets.getExactSetTypeIfKnown(selectables, p -> p.getExactTypeIfKnown(keyspace));
}
@Override
public AbstractType<?> getCompatibleTypeIfKnown(String keyspace)
{
return Sets.getPreferredCompatibleType(selectables, p -> p.getCompatibleTypeIfKnown(keyspace));
}
@Override
public boolean selectColumns(Predicate<ColumnMetadata> predicate)
{
return Selectable.selectColumns(selectables, predicate);
}
@Override
public String toString()
{
return Sets.setToString(selectables);
}
public static class Raw implements Selectable.Raw
{
private final List<Selectable.Raw> raws;
public Raw(List<Selectable.Raw> raws)
{
this.raws = raws;
}
@Override
public Selectable prepare(TableMetadata cfm)
{
return new WithSet(raws.stream().map(p -> p.prepare(cfm)).collect(Collectors.toList()));
}
}
}
/**
* {@code Selectable} for literal Maps or UDTs.
* <p>The parser cannot differentiate between a Map or a UDT in the selection cause because a
* {@code ColumnIdentifier} is equivalent to a {@code FieldIdentifier} from a syntax point of view.
* By consequence, we are forced to wait until the type is known to be able to differentiate them.</p>
*/
public static class WithMapOrUdt implements Selectable
{
/**
* The column family metadata. We need to store them to be able to build the proper data once the type has been
* identified.
*/
private final TableMetadata cfm;
/**
* The Map or UDT raw elements.
*/
private final List<Pair<Selectable.Raw, Selectable.Raw>> raws;
public WithMapOrUdt(TableMetadata cfm, List<Pair<Selectable.Raw, Selectable.Raw>> raws)
{
this.cfm = cfm;
this.raws = raws;
}
@Override
public TestResult testAssignment(String keyspace, ColumnSpecification receiver)
{
return receiver.type.isUDT() ? UserTypes.testUserTypeAssignment(receiver, getUdtFields((UserType) receiver.type))
: Maps.testMapAssignment(receiver, getMapEntries(cfm));
}
@Override
public Factory newSelectorFactory(TableMetadata cfm,
AbstractType<?> expectedType,
List<ColumnMetadata> defs,
VariableSpecifications boundNames)
{
AbstractType<?> type = getExactTypeIfKnown(cfm.keyspace);
if (type == null)
{
type = expectedType;
if (type == null)
throw invalidRequest("Cannot infer type for term %s in selection clause (try using a cast to force a type)",
this);
}
if (type.isUDT())
return newUdtSelectorFactory(cfm, expectedType, defs, boundNames);
return newMapSelectorFactory(cfm, defs, boundNames, type);
}
private Factory newMapSelectorFactory(TableMetadata cfm,
List<ColumnMetadata> defs,
VariableSpecifications boundNames,
AbstractType<?> type)
{
MapType<?, ?> mapType = (MapType<?, ?>) type;
if (mapType.getKeysType() == DurationType.instance)
throw invalidRequest("Durations are not allowed as map keys: %s", mapType.asCQL3Type());
return MapSelector.newFactory(type, getMapEntries(cfm).stream()
.map(p -> Pair.create(p.left.newSelectorFactory(cfm, mapType.getKeysType(), defs, boundNames),
p.right.newSelectorFactory(cfm, mapType.getValuesType(), defs, boundNames)))
.collect(Collectors.toList()));
}
private Factory newUdtSelectorFactory(TableMetadata cfm,
AbstractType<?> expectedType,
List<ColumnMetadata> defs,
VariableSpecifications boundNames)
{
UserType ut = (UserType) expectedType;
Map<FieldIdentifier, Factory> factories = new LinkedHashMap<>(ut.size());
for (Pair<Selectable.Raw, Selectable.Raw> raw : raws)
{
if (!(raw.left instanceof RawIdentifier))
throw invalidRequest("%s is not a valid field identifier of type %s ",
raw.left,
ut.getNameAsString());
FieldIdentifier fieldName = ((RawIdentifier) raw.left).toFieldIdentifier();
int fieldPosition = ut.fieldPosition(fieldName);
if (fieldPosition == -1)
throw invalidRequest("Unknown field '%s' in value of user defined type %s",
fieldName,
ut.getNameAsString());
AbstractType<?> fieldType = ut.fieldType(fieldPosition);
factories.put(fieldName,
raw.right.prepare(cfm).newSelectorFactory(cfm, fieldType, defs, boundNames));
}
return UserTypeSelector.newFactory(expectedType, factories);
}
@Override
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
// Let's force the user to specify the type.
return null;
}
@Override
public AbstractType<?> getCompatibleTypeIfKnown(String keyspace)
{
// Let's force the user to specify the type.
return null;
}
@Override
public boolean selectColumns(Predicate<ColumnMetadata> predicate)
{
for (Pair<Selectable.Raw, Selectable.Raw> raw : raws)
{
if (!(raw.left instanceof RawIdentifier) && raw.left.prepare(cfm).selectColumns(predicate))
return true;
if (!raw.right.prepare(cfm).selectColumns(predicate))
return true;
}
return false;
}
@Override
public String toString()
{
return raws.stream()
.map(p -> String.format("%s: %s",
p.left instanceof RawIdentifier ? p.left : p.left.prepare(cfm),
p.right.prepare(cfm)))
.collect(Collectors.joining(", ", "{", "}"));
}
private List<Pair<Selectable, Selectable>> getMapEntries(TableMetadata cfm)
{
return raws.stream()
.map(p -> Pair.create(p.left.prepare(cfm), p.right.prepare(cfm)))
.collect(Collectors.toList());
}
private Map<FieldIdentifier, Selectable> getUdtFields(UserType ut)
{
Map<FieldIdentifier, Selectable> fields = new LinkedHashMap<>(ut.size());
for (Pair<Selectable.Raw, Selectable.Raw> raw : raws)
{
if (!(raw.left instanceof RawIdentifier))
throw invalidRequest("%s is not a valid field identifier of type %s ",
raw.left,
ut.getNameAsString());
FieldIdentifier fieldName = ((RawIdentifier) raw.left).toFieldIdentifier();
int fieldPosition = ut.fieldPosition(fieldName);
if (fieldPosition == -1)
throw invalidRequest("Unknown field '%s' in value of user defined type %s",
fieldName,
ut.getNameAsString());
fields.put(fieldName, raw.right.prepare(cfm));
}
return fields;
}
public static class Raw implements Selectable.Raw
{
private final List<Pair<Selectable.Raw, Selectable.Raw>> raws;
public Raw(List<Pair<Selectable.Raw, Selectable.Raw>> raws)
{
this.raws = raws;
}
@Override
public Selectable prepare(TableMetadata cfm)
{
return new WithMapOrUdt(cfm, raws);
}
}
}
/**
* <code>Selectable</code> for type hints (e.g. (int) ?).
*/
public static class WithTypeHint implements Selectable
{
/**
* The name of the type as specified in the query.
*/
private final String typeName;
/**
* The type specified by the hint.
*/
private final AbstractType<?> type;
/**
* The selectable to which the hint should be applied.
*/
private final Selectable selectable;
public WithTypeHint(String typeName, AbstractType<?> type, Selectable selectable)
{
this.typeName = typeName;
this.type = type;
this.selectable = selectable;
}
@Override
public TestResult testAssignment(String keyspace, ColumnSpecification receiver)
{
if (receiver.type.equals(type))
return AssignmentTestable.TestResult.EXACT_MATCH;
else if (receiver.type.isValueCompatibleWith(type))
return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE;
else
return AssignmentTestable.TestResult.NOT_ASSIGNABLE;
}
@Override
public Factory newSelectorFactory(TableMetadata cfm,
AbstractType<?> expectedType,
List<ColumnMetadata> defs,
VariableSpecifications boundNames)
{
final ColumnSpecification receiver = new ColumnSpecification(cfm.keyspace, cfm.name, new ColumnIdentifier(toString(), true), type);
if (!selectable.testAssignment(cfm.keyspace, receiver).isAssignable())
throw new InvalidRequestException(String.format("Cannot assign value %s to %s of type %s", this, receiver.name, receiver.type.asCQL3Type()));
final Factory factory = selectable.newSelectorFactory(cfm, type, defs, boundNames);
return new ForwardingFactory()
{
protected Factory delegate()
{
return factory;
}
protected AbstractType<?> getReturnType()
{
return type;
}
protected String getColumnName()
{
return String.format("(%s)%s", typeName, factory.getColumnName());
}
};
}
@Override
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
return type;
}
@Override
public boolean selectColumns(Predicate<ColumnMetadata> predicate)
{
return selectable.selectColumns(predicate);
}
@Override
public String toString()
{
return String.format("(%s)%s", typeName, selectable);
}
public static class Raw implements Selectable.Raw
{
private final CQL3Type.Raw typeRaw;
private final Selectable.Raw raw;
public Raw( CQL3Type.Raw typeRaw, Selectable.Raw raw)
{
this.typeRaw = typeRaw;
this.raw = raw;
}
public Selectable prepare(TableMetadata cfm)
{
Selectable selectable = raw.prepare(cfm);
AbstractType<?> type = this.typeRaw.prepare(cfm.keyspace).getType();
if (type.isFreezable())
type = type.freeze();
return new WithTypeHint(typeRaw.toString(), type, selectable);
}
}
}
/**
* In the selection clause, the parser cannot differentiate between Maps and UDTs as a column identifier and field
* identifier have the same syntax. By consequence, we need to wait until the type is known to create the proper
* Object: {@code ColumnMetadata} or {@code FieldIdentifier}.
*/
public static final class RawIdentifier implements Selectable.Raw
{
private final String text;
private final boolean quoted;
/**
* Creates a {@code RawIdentifier} from an unquoted identifier string.
*/
public static RawIdentifier forUnquoted(String text)
{
return new RawIdentifier(text, false);
}
/**
* Creates a {@code RawIdentifier} from a quoted identifier string.
*/
public static RawIdentifier forQuoted(String text)
{
return new RawIdentifier(text, true);
}
private RawIdentifier(String text, boolean quoted)
{
this.text = text;
this.quoted = quoted;
}
public ColumnMetadata columnMetadata(TableMetadata cfm)
{
return cfm.getExistingColumn(ColumnIdentifier.getInterned(text, quoted));
}
@Override
public Selectable prepare(TableMetadata cfm)
{
return columnMetadata(cfm);
}
public FieldIdentifier toFieldIdentifier()
{
return quoted ? FieldIdentifier.forQuoted(text)
: FieldIdentifier.forUnquoted(text);
}
@Override
public String toString()
{
return text;
}
}
/**
* Represents the selection of an element of a collection (eg. c[x]).
*/
public static class WithElementSelection implements Selectable
{
public final Selectable selected;
// Note that we can't yet prepare the Term.Raw yet as we need the ColumnSpecificiation corresponding to Selectable, which
// we'll only know in newSelectorFactory due to functions (which needs the defs passed to newSelectorFactory to resolve which
// function is called). Note that this doesn't really matter performance wise since the factories are still created during
// preparation of the corresponding SelectStatement.
public final Term.Raw element;
private WithElementSelection(Selectable selected, Term.Raw element)
{
assert element != null;
this.selected = selected;
this.element = element;
}
@Override
public String toString()
{
return String.format("%s[%s]", selected, element);
}
public Selector.Factory newSelectorFactory(TableMetadata cfm, AbstractType<?> expectedType, List<ColumnMetadata> defs, VariableSpecifications boundNames)
{
Selector.Factory factory = selected.newSelectorFactory(cfm, null, defs, boundNames);
ColumnSpecification receiver = factory.getColumnSpecification(cfm);
AbstractType<?> type = receiver.type;
if (receiver.isReversedType())
{
type = ((ReversedType<?>) type).baseType;
}
if (!(type instanceof CollectionType))
throw new InvalidRequestException(String.format("Invalid element selection: %s is of type %s is not a collection", selected, type.asCQL3Type()));
ColumnSpecification boundSpec = specForElementOrSlice(selected, receiver, ((CollectionType) type).kind, "Element");
Term elt = element.prepare(cfm.keyspace, boundSpec);
elt.collectMarkerSpecification(boundNames);
return ElementsSelector.newElementFactory(toString(), factory, (CollectionType)type, elt);
}
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
AbstractType<?> selectedType = selected.getExactTypeIfKnown(keyspace);
if (selectedType == null || !(selectedType instanceof CollectionType))
return null;
return ElementsSelector.valueType((CollectionType) selectedType);
}
@Override
public boolean selectColumns(Predicate<ColumnMetadata> predicate)
{
return selected.selectColumns(predicate);
}
public static class Raw implements Selectable.Raw
{
private final Selectable.Raw selected;
private final Term.Raw element;
public Raw(Selectable.Raw selected, Term.Raw element)
{
this.selected = selected;
this.element = element;
}
public WithElementSelection prepare(TableMetadata cfm)
{
return new WithElementSelection(selected.prepare(cfm), element);
}
@Override
public String toString()
{
return String.format("%s[%s]", selected, element);
}
}
}
/**
* Represents the selection of a slice of a collection (eg. c[x..y]).
*/
public static class WithSliceSelection implements Selectable
{
public final Selectable selected;
// Note that we can't yet prepare the Term.Raw yet as we need the ColumnSpecificiation corresponding to Selectable, which
// we'll only know in newSelectorFactory due to functions (which needs the defs passed to newSelectorFactory to resolve which
// function is called). Note that this doesn't really matter performance wise since the factories are still created during
// preparation of the corresponding SelectStatement.
// Both from and to can be null if they haven't been provided
public final Term.Raw from;
public final Term.Raw to;
private WithSliceSelection(Selectable selected, Term.Raw from, Term.Raw to)
{
this.selected = selected;
this.from = from;
this.to = to;
}
@Override
public String toString()
{
return String.format("%s[%s..%s]", selected, from == null ? "" : from, to == null ? "" : to);
}
public Selector.Factory newSelectorFactory(TableMetadata cfm, AbstractType<?> expectedType, List<ColumnMetadata> defs, VariableSpecifications boundNames)
{
// Note that a slice gives you the same type as the collection you applied it to, so we can pass expectedType for selected directly
Selector.Factory factory = selected.newSelectorFactory(cfm, expectedType, defs, boundNames);
ColumnSpecification receiver = factory.getColumnSpecification(cfm);
AbstractType<?> type = receiver.type;
if (receiver.isReversedType())
{
type = ((ReversedType<?>) type).baseType;
}
if (!(type instanceof CollectionType))
throw new InvalidRequestException(String.format("Invalid slice selection: %s of type %s is not a collection", selected, type.asCQL3Type()));
ColumnSpecification boundSpec = specForElementOrSlice(selected, receiver, ((CollectionType) type).kind, "Slice");
// If from or to are null, this means the user didn't provide on in the syntax (we had c[x..] or c[..x]).
// The equivalent of doing this when preparing values would be to use UNSET.
Term f = from == null ? Constants.UNSET_VALUE : from.prepare(cfm.keyspace, boundSpec);
Term t = to == null ? Constants.UNSET_VALUE : to.prepare(cfm.keyspace, boundSpec);
f.collectMarkerSpecification(boundNames);
t.collectMarkerSpecification(boundNames);
return ElementsSelector.newSliceFactory(toString(), factory, (CollectionType)type, f, t);
}
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
AbstractType<?> selectedType = selected.getExactTypeIfKnown(keyspace);
if (selectedType == null || !(selectedType instanceof CollectionType))
return null;
return selectedType;
}
@Override
public boolean selectColumns(Predicate<ColumnMetadata> predicate)
{
return selected.selectColumns(predicate);
}
public static class Raw implements Selectable.Raw
{
private final Selectable.Raw selected;
// Both from and to can be null if they haven't been provided
private final Term.Raw from;
private final Term.Raw to;
public Raw(Selectable.Raw selected, Term.Raw from, Term.Raw to)
{
this.selected = selected;
this.from = from;
this.to = to;
}
public WithSliceSelection prepare(TableMetadata cfm)
{
return new WithSliceSelection(selected.prepare(cfm), from, to);
}
@Override
public String toString()
{
return String.format("%s[%s..%s]", selected, from == null ? "" : from, to == null ? "" : to);
}
}
}
}