blob: 80e2ae8778b8c076c3530b10226a7c4b178dbded [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.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang3.text.StrBuilder;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.functions.*;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.exceptions.InvalidRequestException;
public interface Selectable extends AssignmentTestable
{
public Selector.Factory newSelectorFactory(CFMetaData cfm, AbstractType<?> expectedType, List<ColumnDefinition> 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);
// 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);
}
default int addAndGetIndex(ColumnDefinition def, List<ColumnDefinition> l)
{
int idx = l.indexOf(def);
if (idx < 0)
{
idx = l.size();
l.add(def);
}
return idx;
}
public static abstract class Raw
{
public abstract Selectable prepare(CFMetaData cfm);
/**
* Returns true if any processing is performed on the selected column.
**/
public boolean processesSelection()
{
// ColumnIdentifier is the only case that returns false and override this
return true;
}
}
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(CFMetaData cfm, AbstractType<?> expectedType, List<ColumnDefinition> 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 bigintAsBlob(?) 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(cfm.ksName);
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(cfm.ksName, new ColumnSpecification(cfm.ksName, cfm.cfName, bindMarkerNameInSelection, type));
term.collectMarkerSpecification(boundNames);
return TermSelector.newFactory(rawTerm.getText(), term, type);
}
@Override
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
return rawTerm.getExactTypeIfKnown(keyspace);
}
@Override
public String toString()
{
return rawTerm.toString();
}
public static class Raw extends Selectable.Raw
{
private final Term.Raw term;
public Raw(Term.Raw term)
{
this.term = term;
}
public Selectable prepare(CFMetaData cfm)
{
return new WithTerm(term);
}
}
}
public static class WritetimeOrTTL implements Selectable
{
public final ColumnDefinition column;
public final boolean isWritetime;
public WritetimeOrTTL(ColumnDefinition column, boolean isWritetime)
{
this.column = column;
this.isWritetime = isWritetime;
}
@Override
public String toString()
{
return (isWritetime ? "writetime" : "ttl") + "(" + column.name + ")";
}
public Selector.Factory newSelectorFactory(CFMetaData cfm,
AbstractType<?> expectedType,
List<ColumnDefinition> defs,
VariableSpecifications boundNames)
{
if (column.isPrimaryKeyColumn())
throw new InvalidRequestException(
String.format("Cannot use selection function %s on PRIMARY KEY part %s",
isWritetime ? "writeTime" : "ttl",
column.name));
if (column.type.isCollection())
throw new InvalidRequestException(String.format("Cannot use selection function %s on collections",
isWritetime ? "writeTime" : "ttl"));
return WritetimeOrTTLSelector.newFactory(column, addAndGetIndex(column, defs), isWritetime);
}
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
return isWritetime ? LongType.instance : Int32Type.instance;
}
public static class Raw extends Selectable.Raw
{
private final ColumnDefinition.Raw id;
private final boolean isWritetime;
public Raw(ColumnDefinition.Raw id, boolean isWritetime)
{
this.id = id;
this.isWritetime = isWritetime;
}
public WritetimeOrTTL prepare(CFMetaData cfm)
{
return new WritetimeOrTTL(id.prepare(cfm), isWritetime);
}
}
}
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 new StrBuilder().append(function.name())
.append("(")
.appendWithSeparators(args, ", ")
.append(")")
.toString();
}
public Selector.Factory newSelectorFactory(CFMetaData cfm, AbstractType<?> expectedType, List<ColumnDefinition> defs, VariableSpecifications boundNames)
{
SelectorFactories factories = SelectorFactories.createFactoriesAndCollectColumnDefinitions(args, function.argTypes(), cfm, defs, boundNames);
return AbstractFunctionSelector.newFactory(function, factories);
}
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
return function.returnType();
}
public static class Raw extends 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 Selectable prepare(CFMetaData cfm)
{
List<Selectable> preparedArgs = new ArrayList<>(args.size());
for (Selectable.Raw arg : args)
preparedArgs.add(arg.prepare(cfm));
FunctionName name = functionName;
// We need to circumvent the normal function lookup process for toJson() because instances of the function
// are not pre-declared (because it can accept any type of argument). We also have to wait until we have the
// selector factories of the argument so we can access their final type.
if (functionName.equalsNativeFunction(ToJsonFct.NAME))
{
return new WithToJSonFunction(preparedArgs);
}
// Also, COUNT(x) is equivalent to COUNT(*) for any non-null term x (since count(x) don't care about it's 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 have to special case COUNT.
else 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(cfm.ksName, name, preparedArgs, cfm.ksName, cfm.cfName, 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 WithToJSonFunction implements Selectable
{
public final List<Selectable> args;
private WithToJSonFunction(List<Selectable> args)
{
this.args = args;
}
@Override
public String toString()
{
return new StrBuilder().append(ToJsonFct.NAME)
.append("(")
.appendWithSeparators(args, ", ")
.append(")")
.toString();
}
public Selector.Factory newSelectorFactory(CFMetaData cfm, AbstractType<?> expectedType, List<ColumnDefinition> defs, VariableSpecifications boundNames)
{
SelectorFactories factories = SelectorFactories.createFactoriesAndCollectColumnDefinitions(args, null, cfm, defs, boundNames);
Function fun = ToJsonFct.getInstance(factories.getReturnTypes());
return AbstractFunctionSelector.newFactory(fun, factories);
}
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
return UTF8Type.instance;
}
}
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(CFMetaData cfm, AbstractType<?> expectedType, List<ColumnDefinition> defs, VariableSpecifications boundNames)
{
List<Selectable> args = Collections.singletonList(arg);
SelectorFactories factories = SelectorFactories.createFactoriesAndCollectColumnDefinitions(args, null, cfm, 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(cfm.ksName, name, args, cfm.ksName, cfm.cfName, 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();
}
public static class Raw extends 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(CFMetaData cfm)
{
return new WithCast(arg.prepare(cfm), type);
}
}
}
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(CFMetaData cfm, AbstractType<?> expectedType, List<ColumnDefinition> defs, VariableSpecifications boundNames)
{
Selector.Factory factory = selected.newSelectorFactory(cfm, null, defs, boundNames);
AbstractType<?> type = factory.getColumnSpecification(cfm).type;
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);
}
public static class Raw extends 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(CFMetaData cfm)
{
return new WithFieldSelection(selected.prepare(cfm), field);
}
}
}
}