http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/cql3/functions/OperationFcts.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/OperationFcts.java b/src/java/org/apache/cassandra/cql3/functions/OperationFcts.java new file mode 100644 index 0000000..1f115a9 --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/functions/OperationFcts.java @@ -0,0 +1,380 @@ +/* + * 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.functions; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.cassandra.config.SchemaConstants; +import org.apache.cassandra.db.marshal.*; +import org.apache.cassandra.exceptions.OperationExecutionException; +import org.apache.cassandra.transport.ProtocolVersion; + +/** + * Operation functions (Mathematics). + * + */ +public final class OperationFcts +{ + private static enum OPERATION + { + ADDITION('+', "_add") + { + protected ByteBuffer execute(NumberType<?> resultType, + NumberType<?> leftType, + ByteBuffer left, + NumberType<?> rightType, + ByteBuffer right) + { + return resultType.add(leftType, left, rightType, right); + } + }, + SUBSTRACTION('-', "_substract") + { + protected ByteBuffer execute(NumberType<?> resultType, + NumberType<?> leftType, + ByteBuffer left, + NumberType<?> rightType, + ByteBuffer right) + { + return resultType.substract(leftType, left, rightType, right); + } + }, + MULTIPLICATION('*', "_multiply") + { + protected ByteBuffer execute(NumberType<?> resultType, + NumberType<?> leftType, + ByteBuffer left, + NumberType<?> rightType, + ByteBuffer right) + { + return resultType.multiply(leftType, left, rightType, right); + } + }, + DIVISION('/', "_divide") + { + protected ByteBuffer execute(NumberType<?> resultType, + NumberType<?> leftType, + ByteBuffer left, + NumberType<?> rightType, + ByteBuffer right) + { + return resultType.divide(leftType, left, rightType, right); + } + }, + MODULO('%', "_modulo") + { + protected ByteBuffer execute(NumberType<?> resultType, + NumberType<?> leftType, + ByteBuffer left, + NumberType<?> rightType, + ByteBuffer right) + { + return resultType.mod(leftType, left, rightType, right); + } + }; + + /** + * The operator symbol. + */ + private final char symbol; + + /** + * The name of the function associated to this operation + */ + private final String functionName; + + private OPERATION(char symbol, String functionName) + { + this.symbol = symbol; + this.functionName = functionName; + } + + /** + * Executes the operation between the specified operand. + * + * @param resultType the result ype of the operation + * @param leftType the type of the left operand + * @param left the left operand + * @param rightType the type of the right operand + * @param right the right operand + * @return the operation result + */ + protected abstract ByteBuffer execute(NumberType<?> resultType, + NumberType<?> leftType, + ByteBuffer left, + NumberType<?> rightType, + ByteBuffer right); + + /** + * Returns the {@code OPERATOR} associated to the specified function. + * @param functionName the function name + * @return the {@code OPERATOR} associated to the specified function + */ + public static OPERATION fromFunctionName(String functionName) + { + for (OPERATION operator : values()) + { + if (operator.functionName.equals(functionName)) + return operator; + } + return null; + } + + /** + * Returns the {@code OPERATOR} with the specified symbol. + * @param functionName the function name + * @return the {@code OPERATOR} with the specified symbol + */ + public static OPERATION fromSymbol(char symbol) + { + for (OPERATION operator : values()) + { + if (operator.symbol == symbol) + return operator; + } + return null; + } + } + + /** + * The name of the function used to perform negations + */ + public static final String NEGATION_FUNCTION_NAME = "_negate"; + + public static Collection<Function> all() + { + List<Function> functions = new ArrayList<>(); + + final NumberType<?>[] numericTypes = new NumberType[] { ByteType.instance, + ShortType.instance, + Int32Type.instance, + LongType.instance, + FloatType.instance, + DoubleType.instance, + DecimalType.instance, + IntegerType.instance, + CounterColumnType.instance}; + + for (NumberType<?> left : numericTypes) + { + for (NumberType<?> right : numericTypes) + { + NumberType<?> returnType = returnType(left, right); + functions.add(new OperationFunction(returnType, left, OPERATION.ADDITION, right)); + functions.add(new OperationFunction(returnType, left, OPERATION.SUBSTRACTION, right)); + functions.add(new OperationFunction(returnType, left, OPERATION.MULTIPLICATION, right)); + functions.add(new OperationFunction(returnType, left, OPERATION.DIVISION, right)); + functions.add(new OperationFunction(returnType, left, OPERATION.MODULO, right)); + } + functions.add(new NegationFunction(left)); + } + return functions; + } + + /** + * Checks if the function with the specified name is an operation. + * + * @param function the function name + * @return {@code true} if the function is an operation, {@code false} otherwise. + */ + public static boolean isOperation(FunctionName function) + { + return SchemaConstants.SYSTEM_KEYSPACE_NAME.equals(function.keyspace) + && OPERATION.fromFunctionName(function.name) != null; + } + + /** + * Checks if the function with the specified name is a negation. + * + * @param function the function name + * @return {@code true} if the function is an negation, {@code false} otherwise. + */ + public static boolean isNegation(FunctionName function) + { + return SchemaConstants.SYSTEM_KEYSPACE_NAME.equals(function.keyspace)&& NEGATION_FUNCTION_NAME.equals(function.name); + } + + /** + * Returns the operator associated to the specified function. + * + * @return the operator associated to the specified function. + */ + public static char getOperator(FunctionName function) + { + assert SchemaConstants.SYSTEM_KEYSPACE_NAME.equals(function.keyspace); + return OPERATION.fromFunctionName(function.name).symbol; + } + + /** + * Returns the name of the function associated to the specified operator. + * + * @param operator the operator + * @return the name of the function associated to the specified operator + */ + public static FunctionName getFunctionNameFromOperator(char operator) + { + return FunctionName.nativeFunction(OPERATION.fromSymbol(operator).functionName); + } + + /** + * Determine the return type for an operation between the specified types. + * + * @param left the type of the left operand + * @param right the type of the right operand + * @return the return type for an operation between the specified types + */ + private static NumberType<?> returnType(NumberType<?> left, NumberType<?> right) + { + boolean isFloatingPoint = left.isFloatingPoint() || right.isFloatingPoint(); + int size = Math.max(size(left), size(right)); + return isFloatingPoint + ? floatPointType(size) + : integerType(size); + } + + /** + * Returns the number of bytes used to represent a value of this type. + * @return the number of bytes used to represent a value of this type or {@code Integer.MAX} if the number of bytes + * is not limited. + */ + private static int size(NumberType<?> type) + { + int size = type.valueLengthIfFixed(); + + if (size > 0) + return size; + + if (type.isCounter()) + return LongType.instance.valueLengthIfFixed(); + + return Integer.MAX_VALUE; + } + + private static NumberType<?> floatPointType(int size) + { + switch (size) + { + case 4: return FloatType.instance; + case 8: return DoubleType.instance; + default: return DecimalType.instance; + } + } + + private static NumberType<?> integerType(int size) + { + switch (size) + { + case 1: return ByteType.instance; + case 2: return ShortType.instance; + case 4: return Int32Type.instance; + case 8: return LongType.instance; + default: return IntegerType.instance; + } + } + + /** + * The class must not be instantiated. + */ + private OperationFcts() + { + } + + /** + * Function that execute operations. + */ + private static class OperationFunction extends NativeScalarFunction + { + private final OPERATION operation; + + public OperationFunction(NumberType<?> returnType, + NumberType<?> left, + OPERATION operation, + NumberType<?> right) + { + super(operation.functionName, returnType, left, right); + this.operation = operation; + } + + @Override + public final String columnName(List<String> columnNames) + { + return String.format("%s %s %s", columnNames.get(0), getOperator(), columnNames.get(1)); + } + + public final ByteBuffer execute(ProtocolVersion protocolVersion, List<ByteBuffer> parameters) + { + ByteBuffer left = parameters.get(0); + ByteBuffer right = parameters.get(1); + if (left == null || !left.hasRemaining() || right == null || !right.hasRemaining()) + return null; + + NumberType<?> leftType = (NumberType<?>) argTypes().get(0); + NumberType<?> rightType = (NumberType<?>) argTypes().get(1); + NumberType<?> resultType = (NumberType<?>) returnType(); + + try + { + return operation.execute(resultType, leftType, left, rightType, right); + } + catch (Exception e) + { + throw OperationExecutionException.create(getOperator(), argTypes, e); + } + } + + /** + * Returns the operator symbol. + * @return the operator symbol + */ + private char getOperator() + { + return operation.symbol; + } + } + + /** + * Function that negate a number. + */ + private static class NegationFunction extends NativeScalarFunction + { + public NegationFunction(NumberType<?> inputType) + { + super(NEGATION_FUNCTION_NAME, inputType, inputType); + } + + @Override + public final String columnName(List<String> columnNames) + { + return String.format("-%s", columnNames.get(0)); + } + + public final ByteBuffer execute(ProtocolVersion protocolVersion, List<ByteBuffer> parameters) + { + ByteBuffer input = parameters.get(0); + if (input == null) + return null; + + NumberType<?> inputType = (NumberType<?>) argTypes().get(0); + + return inputType.negate(input); + } + } +}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/cql3/selection/CollectionFactory.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/selection/CollectionFactory.java b/src/java/org/apache/cassandra/cql3/selection/CollectionFactory.java new file mode 100644 index 0000000..de5fd93 --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/selection/CollectionFactory.java @@ -0,0 +1,91 @@ +/* + * 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.List; + +import org.apache.cassandra.config.ColumnDefinition; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.functions.Function; +import org.apache.cassandra.cql3.selection.Selector.Factory; +import org.apache.cassandra.db.marshal.AbstractType; + +/** + * A base <code>Selector.Factory</code> for collections or tuples. + */ +abstract class CollectionFactory extends Factory +{ + /** + * The collection or tuple type. + */ + private final AbstractType<?> type; + + /** + * The collection or tuple element factories. + */ + private final SelectorFactories factories; + + public CollectionFactory(AbstractType<?> type, SelectorFactories factories) + { + this.type = type; + this.factories = factories; + } + + protected final AbstractType<?> getReturnType() + { + return type; + } + + @Override + public final void addFunctionsTo(List<Function> functions) + { + factories.addFunctionsTo(functions); + } + + @Override + public final boolean isAggregateSelectorFactory() + { + return factories.doesAggregation(); + } + + @Override + public final boolean isWritetimeSelectorFactory() + { + return factories.containsWritetimeSelectorFactory(); + } + + @Override + public final boolean isTTLSelectorFactory() + { + return factories.containsTTLSelectorFactory(); + } + + protected final void addColumnMapping(SelectionColumnMapping mapping, ColumnSpecification resultsColumn) + { + SelectionColumnMapping tmpMapping = SelectionColumnMapping.newMapping(); + for (Factory factory : factories) + factory.addColumnMapping(tmpMapping, resultsColumn); + + if (tmpMapping.getMappings().get(resultsColumn).isEmpty()) + // add a null mapping for cases where the collection is empty + mapping.addMapping(resultsColumn, (ColumnDefinition)null); + else + // collate the mapped columns from the child factories & add those + mapping.addMapping(resultsColumn, tmpMapping.getMappings().values()); + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/cql3/selection/ForwardingFactory.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/selection/ForwardingFactory.java b/src/java/org/apache/cassandra/cql3/selection/ForwardingFactory.java new file mode 100644 index 0000000..50b3ded --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/selection/ForwardingFactory.java @@ -0,0 +1,90 @@ +/* + * 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.List; + +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.functions.Function; +import org.apache.cassandra.cql3.selection.Selector.Factory; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.exceptions.InvalidRequestException; + +/** + * A <code>Selector.Factory</code> which forwards all its method calls to another factory. + * Subclasses should override one or more methods to modify the behavior of the backing factory as desired per + * the decorator pattern. + */ +abstract class ForwardingFactory extends Factory +{ + /** + * Returns the backing delegate instance that methods are forwarded to. + */ + protected abstract Factory delegate(); + + public Selector newInstance(QueryOptions options) throws InvalidRequestException + { + return delegate().newInstance(options); + } + + protected String getColumnName() + { + return delegate().getColumnName(); + } + + protected AbstractType<?> getReturnType() + { + return delegate().getReturnType(); + } + + protected void addColumnMapping(SelectionColumnMapping mapping, ColumnSpecification resultsColumn) + { + delegate().addColumnMapping(mapping, resultsColumn); + } + + @Override + public void addFunctionsTo(List<Function> functions) + { + delegate().addFunctionsTo(functions); + } + + @Override + public boolean isAggregateSelectorFactory() + { + return delegate().isAggregateSelectorFactory(); + } + + @Override + public boolean isWritetimeSelectorFactory() + { + return delegate().isWritetimeSelectorFactory(); + } + + @Override + public boolean isTTLSelectorFactory() + { + return delegate().isTTLSelectorFactory(); + } + + @Override + public boolean isSimpleSelectorFactory(int index) + { + return delegate().isSimpleSelectorFactory(index); + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/cql3/selection/ListSelector.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/selection/ListSelector.java b/src/java/org/apache/cassandra/cql3/selection/ListSelector.java new file mode 100644 index 0000000..940bd9c --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/selection/ListSelector.java @@ -0,0 +1,104 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; + +import org.apache.cassandra.config.ColumnDefinition; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.Lists; +import org.apache.cassandra.cql3.selection.Selection.ResultSetBuilder; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.serializers.CollectionSerializer; +import org.apache.cassandra.transport.ProtocolVersion; + +/** + * <code>Selector</code> for literal list (e.g. [min(value), max(value), count(value)]). + * + */ +final class ListSelector extends Selector +{ + /** + * The list type. + */ + private final AbstractType<?> type; + + /** + * The list elements + */ + private final List<Selector> elements; + + public static Factory newFactory(final AbstractType<?> type, final SelectorFactories factories) + { + return new CollectionFactory(type, factories) + { + protected String getColumnName() + { + return Lists.listToString(factories, Factory::getColumnName); + } + + public Selector newInstance(final QueryOptions options) + { + return new ListSelector(type, factories.newInstances(options)); + } + }; + } + + public void addInput(ProtocolVersion protocolVersion, ResultSetBuilder rs) throws InvalidRequestException + { + for (int i = 0, m = elements.size(); i < m; i++) + elements.get(i).addInput(protocolVersion, rs); + } + + public ByteBuffer getOutput(ProtocolVersion protocolVersion) throws InvalidRequestException + { + List<ByteBuffer> buffers = new ArrayList<>(elements.size()); + for (int i = 0, m = elements.size(); i < m; i++) + { + buffers.add(elements.get(i).getOutput(protocolVersion)); + } + return CollectionSerializer.pack(buffers, buffers.size(), protocolVersion); + } + + public void reset() + { + for (int i = 0, m = elements.size(); i < m; i++) + elements.get(i).reset(); + } + + public AbstractType<?> getType() + { + return type; + } + + @Override + public String toString() + { + return Lists.listToString(elements); + } + + private ListSelector(AbstractType<?> type, List<Selector> elements) + { + this.type = type; + this.elements = elements; + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/cql3/selection/MapSelector.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/selection/MapSelector.java b/src/java/org/apache/cassandra/cql3/selection/MapSelector.java new file mode 100644 index 0000000..8bbae8c --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/selection/MapSelector.java @@ -0,0 +1,195 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import org.apache.cassandra.config.ColumnDefinition; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.Maps; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.functions.Function; +import org.apache.cassandra.cql3.selection.Selection.ResultSetBuilder; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.MapType; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.serializers.CollectionSerializer; +import org.apache.cassandra.transport.ProtocolVersion; +import org.apache.cassandra.utils.Pair; + +/** + * <code>Selector</code> for literal map (e.g. {'min' : min(value), 'max' : max(value), 'count' : count(value)}). + * + */ +final class MapSelector extends Selector +{ + /** + * The map type. + */ + private final MapType<?, ?> type; + + /** + * The map elements + */ + private final List<Pair<Selector, Selector>> elements; + + public static Factory newFactory(final AbstractType<?> type, final List<Pair<Factory, Factory>> factories) + { + return new Factory() + { + protected String getColumnName() + { + return Maps.mapToString(factories, Factory::getColumnName); + } + + protected AbstractType<?> getReturnType() + { + return type; + } + + protected final void addColumnMapping(SelectionColumnMapping mapping, ColumnSpecification resultsColumn) + { + SelectionColumnMapping tmpMapping = SelectionColumnMapping.newMapping(); + for (Pair<Factory, Factory> entry : factories) + { + entry.left.addColumnMapping(tmpMapping, resultsColumn); + entry.right.addColumnMapping(tmpMapping, resultsColumn); + } + + if (tmpMapping.getMappings().get(resultsColumn).isEmpty()) + // add a null mapping for cases where the collection is empty + mapping.addMapping(resultsColumn, (ColumnDefinition)null); + else + // collate the mapped columns from the child factories & add those + mapping.addMapping(resultsColumn, tmpMapping.getMappings().values()); + } + + public Selector newInstance(final QueryOptions options) + { + return new MapSelector(type, + factories.stream() + .map(p -> Pair.create(p.left.newInstance(options), + p.right.newInstance(options))) + .collect(Collectors.toList())); + } + + @Override + public boolean isAggregateSelectorFactory() + { + for (Pair<Factory, Factory> entry : factories) + { + if (entry.left.isAggregateSelectorFactory() || entry.right.isAggregateSelectorFactory()) + return true; + } + return false; + } + + @Override + public void addFunctionsTo(List<Function> functions) + { + for (Pair<Factory, Factory> entry : factories) + { + entry.left.addFunctionsTo(functions); + entry.right.addFunctionsTo(functions); + } + } + + @Override + public boolean isWritetimeSelectorFactory() + { + for (Pair<Factory, Factory> entry : factories) + { + if (entry.left.isWritetimeSelectorFactory() || entry.right.isWritetimeSelectorFactory()) + return true; + } + return false; + } + + @Override + public boolean isTTLSelectorFactory() + { + for (Pair<Factory, Factory> entry : factories) + { + if (entry.left.isTTLSelectorFactory() || entry.right.isTTLSelectorFactory()) + return true; + } + return false; + } + }; + } + + public void addInput(ProtocolVersion protocolVersion, ResultSetBuilder rs) throws InvalidRequestException + { + for (int i = 0, m = elements.size(); i < m; i++) + { + Pair<Selector, Selector> pair = elements.get(i); + pair.left.addInput(protocolVersion, rs); + pair.right.addInput(protocolVersion, rs); + } + } + + public ByteBuffer getOutput(ProtocolVersion protocolVersion) throws InvalidRequestException + { + Map<ByteBuffer, ByteBuffer> map = new TreeMap<>(type.getKeysType()); + for (int i = 0, m = elements.size(); i < m; i++) + { + Pair<Selector, Selector> pair = elements.get(i); + map.put(pair.left.getOutput(protocolVersion), pair.right.getOutput(protocolVersion)); + } + + List<ByteBuffer> buffers = new ArrayList<>(elements.size() * 2); + for (Map.Entry<ByteBuffer, ByteBuffer> entry : map.entrySet()) + { + buffers.add(entry.getKey()); + buffers.add(entry.getValue()); + } + return CollectionSerializer.pack(buffers, elements.size(), protocolVersion); + } + + public void reset() + { + for (int i = 0, m = elements.size(); i < m; i++) + { + Pair<Selector, Selector> pair = elements.get(i); + pair.left.reset(); + pair.right.reset(); + } + } + + public AbstractType<?> getType() + { + return type; + } + + @Override + public String toString() + { + return Maps.mapToString(elements); + } + + private MapSelector(AbstractType<?> type, List<Pair<Selector, Selector>> elements) + { + this.type = (MapType<?, ?>) type; + this.elements = elements; + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/cql3/selection/ScalarFunctionSelector.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/selection/ScalarFunctionSelector.java b/src/java/org/apache/cassandra/cql3/selection/ScalarFunctionSelector.java index c05cdaa..15e0210 100644 --- a/src/java/org/apache/cassandra/cql3/selection/ScalarFunctionSelector.java +++ b/src/java/org/apache/cassandra/cql3/selection/ScalarFunctionSelector.java @@ -28,15 +28,6 @@ import org.apache.cassandra.transport.ProtocolVersion; final class ScalarFunctionSelector extends AbstractFunctionSelector<ScalarFunction> { - public boolean isAggregate() - { - // We cannot just return true as it is possible to have a scalar function wrapping an aggregation function - if (argSelectors.isEmpty()) - return false; - - return argSelectors.get(0).isAggregate(); - } - public void addInput(ProtocolVersion protocolVersion, ResultSetBuilder rs) throws InvalidRequestException { for (int i = 0, m = argSelectors.size(); i < m; i++) http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/cql3/selection/Selectable.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/selection/Selectable.java b/src/java/org/apache/cassandra/cql3/selection/Selectable.java index 80e2ae8..20719ea 100644 --- a/src/java/org/apache/cassandra/cql3/selection/Selectable.java +++ b/src/java/org/apache/cassandra/cql3/selection/Selectable.java @@ -18,17 +18,22 @@ */ package org.apache.cassandra.cql3.selection; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; 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.cql3.selection.Selector.Factory; import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.exceptions.InvalidRequestException; +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 { @@ -154,7 +159,7 @@ public interface Selectable extends AssignmentTestable @Override public String toString() { - return rawTerm.toString(); + return rawTerm.getText(); } public static class Raw extends Selectable.Raw @@ -244,11 +249,7 @@ public interface Selectable extends AssignmentTestable @Override public String toString() { - return new StrBuilder().append(function.name()) - .append("(") - .appendWithSeparators(args, ", ") - .append(")") - .toString(); + return function.columnName(args.stream().map(Object::toString).collect(Collectors.toList())); } public Selector.Factory newSelectorFactory(CFMetaData cfm, AbstractType<?> expectedType, List<ColumnDefinition> defs, VariableSpecifications boundNames) @@ -279,6 +280,18 @@ public interface Selectable extends AssignmentTestable 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)); + } + public Selectable prepare(CFMetaData cfm) { List<Selectable> preparedArgs = new ArrayList<>(args.size()); @@ -433,7 +446,16 @@ public interface Selectable extends AssignmentTestable public Selector.Factory newSelectorFactory(CFMetaData cfm, AbstractType<?> expectedType, List<ColumnDefinition> defs, VariableSpecifications boundNames) { - Selector.Factory factory = selected.newSelectorFactory(cfm, null, defs, 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(cfm.ksName); + } + + Selector.Factory factory = selected.newSelectorFactory(cfm, expectedUdtType, defs, boundNames); AbstractType<?> type = factory.getColumnSpecification(cfm).type; if (!type.isUDT()) { @@ -485,4 +507,609 @@ public interface Selectable extends AssignmentTestable } } } + + /** + * {@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(CFMetaData cfm, + AbstractType<?> expectedType, + List<ColumnDefinition> defs, + VariableSpecifications boundNames) + { + AbstractType<?> type = getExactTypeIfKnown(cfm.ksName); + 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(CFMetaData cfm, + AbstractType<?> expectedType, + List<ColumnDefinition> 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(CFMetaData cfm, + TupleType tupleType, + List<ColumnDefinition> 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 String toString() + { + return Tuples.tupleToString(selectables); + } + + public static class Raw extends Selectable.Raw + { + private final List<Selectable.Raw> raws; + + public Raw(List<Selectable.Raw> raws) + { + this.raws = raws; + } + + public Selectable prepare(CFMetaData 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(CFMetaData cfm, + AbstractType<?> expectedType, + List<ColumnDefinition> defs, + VariableSpecifications boundNames) + { + AbstractType<?> type = getExactTypeIfKnown(cfm.ksName); + 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 String toString() + { + return Lists.listToString(selectables); + } + + public static class Raw extends Selectable.Raw + { + private final List<Selectable.Raw> raws; + + public Raw(List<Selectable.Raw> raws) + { + this.raws = raws; + } + + public Selectable prepare(CFMetaData 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(CFMetaData cfm, + AbstractType<?> expectedType, + List<ColumnDefinition> defs, + VariableSpecifications boundNames) + { + AbstractType<?> type = getExactTypeIfKnown(cfm.ksName); + 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 String toString() + { + return Sets.setToString(selectables); + } + + public static class Raw extends Selectable.Raw + { + private final List<Selectable.Raw> raws; + + public Raw(List<Selectable.Raw> raws) + { + this.raws = raws; + } + + public Selectable prepare(CFMetaData 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 ColumnDefinition} 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 CFMetaData cfm; + + /** + * The Map or UDT raw elements. + */ + private final List<Pair<Selectable.Raw, Selectable.Raw>> raws; + + public WithMapOrUdt(CFMetaData 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(CFMetaData cfm, + AbstractType<?> expectedType, + List<ColumnDefinition> defs, + VariableSpecifications boundNames) + { + AbstractType<?> type = getExactTypeIfKnown(cfm.ksName); + 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(CFMetaData cfm, + List<ColumnDefinition> 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(CFMetaData cfm, + AbstractType<?> expectedType, + List<ColumnDefinition> 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) + { + // Lets force the user to specify the type. + return null; + } + + @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(CFMetaData 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 extends Selectable.Raw + { + private final List<Pair<Selectable.Raw, Selectable.Raw>> raws; + + public Raw(List<Pair<Selectable.Raw, Selectable.Raw>> raws) + { + this.raws = raws; + } + + public Selectable prepare(CFMetaData 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(CFMetaData cfm, + AbstractType<?> expectedType, + List<ColumnDefinition> defs, + VariableSpecifications boundNames) + { + final ColumnSpecification receiver = new ColumnSpecification(cfm.ksName, cfm.cfName, new ColumnIdentifier(toString(), true), type); + + if (!selectable.testAssignment(cfm.ksName, 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 String toString() + { + return String.format("(%s)%s", typeName, selectable); + } + + public static class Raw extends 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(CFMetaData cfm) + { + Selectable selectable = raw.prepare(cfm); + AbstractType<?> type = this.typeRaw.prepare(cfm.ksName).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 ColumnDefinition} or {@code FieldIdentifier}. + */ + public static final class RawIdentifier extends Selectable.Raw + { + private final String text; + + private final boolean quoted; + + /** + * Creates a {@code RawIdentifier} from an unquoted identifier string. + */ + public static Raw forUnquoted(String text) + { + return new RawIdentifier(text, false); + } + + /** + * Creates a {@code RawIdentifier} from a quoted identifier string. + */ + public static Raw forQuoted(String text) + { + return new RawIdentifier(text, true); + } + + private RawIdentifier(String text, boolean quoted) + { + this.text = text; + this.quoted = quoted; + } + + @Override + public Selectable prepare(CFMetaData cfm) + { + ColumnDefinition.Raw raw = quoted ? ColumnDefinition.Raw.forQuoted(text) + : ColumnDefinition.Raw.forUnquoted(text); + return raw.prepare(cfm); + } + + public FieldIdentifier toFieldIdentifier() + { + return quoted ? FieldIdentifier.forQuoted(text) + : FieldIdentifier.forUnquoted(text); + } + + @Override + public String toString() + { + return text; + } + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/cql3/selection/Selector.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/selection/Selector.java b/src/java/org/apache/cassandra/cql3/selection/Selector.java index 922b57f..6f83dfc 100644 --- a/src/java/org/apache/cassandra/cql3/selection/Selector.java +++ b/src/java/org/apache/cassandra/cql3/selection/Selector.java @@ -172,17 +172,6 @@ public abstract class Selector public abstract AbstractType<?> getType(); /** - * Checks if this <code>Selector</code> is creating aggregates. - * - * @return <code>true</code> if this <code>Selector</code> is creating aggregates <code>false</code> - * otherwise. - */ - public boolean isAggregate() - { - return false; - } - - /** * Reset the internal state of this <code>Selector</code>. */ public abstract void reset(); http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/cql3/selection/SetSelector.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/selection/SetSelector.java b/src/java/org/apache/cassandra/cql3/selection/SetSelector.java new file mode 100644 index 0000000..34de078 --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/selection/SetSelector.java @@ -0,0 +1,106 @@ +/* + * 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 java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.cassandra.config.ColumnDefinition; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.Sets; +import org.apache.cassandra.cql3.selection.Selection.ResultSetBuilder; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.SetType; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.serializers.CollectionSerializer; +import org.apache.cassandra.transport.ProtocolVersion; + +/** + * <code>Selector</code> for literal set (e.g. {min(value), max(value), count(value)}). + * + */ +final class SetSelector extends Selector +{ + /** + * The set type. + */ + private final SetType<?> type; + + /** + * The set elements + */ + private final List<Selector> elements; + + public static Factory newFactory(final AbstractType<?> type, final SelectorFactories factories) + { + return new CollectionFactory(type, factories) + { + protected String getColumnName() + { + return Sets.setToString(factories, Factory::getColumnName); + } + + public Selector newInstance(final QueryOptions options) + { + return new SetSelector(type, factories.newInstances(options)); + } + }; + } + + public void addInput(ProtocolVersion protocolVersion, ResultSetBuilder rs) throws InvalidRequestException + { + for (int i = 0, m = elements.size(); i < m; i++) + elements.get(i).addInput(protocolVersion, rs); + } + + public ByteBuffer getOutput(ProtocolVersion protocolVersion) throws InvalidRequestException + { + Set<ByteBuffer> buffers = new TreeSet<>(type.getElementsType()); + for (int i = 0, m = elements.size(); i < m; i++) + { + buffers.add(elements.get(i).getOutput(protocolVersion)); + } + return CollectionSerializer.pack(buffers, buffers.size(), protocolVersion); + } + + public void reset() + { + for (int i = 0, m = elements.size(); i < m; i++) + elements.get(i).reset(); + } + + public AbstractType<?> getType() + { + return type; + } + + @Override + public String toString() + { + return Sets.setToString(elements); + } + + private SetSelector(AbstractType<?> type, List<Selector> elements) + { + this.type = (SetType<?>) type; + this.elements = elements; + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/cql3/selection/TupleSelector.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/selection/TupleSelector.java b/src/java/org/apache/cassandra/cql3/selection/TupleSelector.java new file mode 100644 index 0000000..9f4c381 --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/selection/TupleSelector.java @@ -0,0 +1,101 @@ +/* + * 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 java.util.List; + +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.Tuples; +import org.apache.cassandra.cql3.selection.Selection.ResultSetBuilder; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.TupleType; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.transport.ProtocolVersion; + +/** + * <code>Selector</code> for literal tuples (e.g. (min(value), max(value), count(value))). + * + */ +final class TupleSelector extends Selector +{ + /** + * The tuple type. + */ + private final AbstractType<?> type; + + /** + * The tuple elements + */ + private final List<Selector> elements; + + public static Factory newFactory(final AbstractType<?> type, final SelectorFactories factories) + { + return new CollectionFactory(type, factories) + { + protected String getColumnName() + { + return Tuples.tupleToString(factories, Factory::getColumnName); + } + + public Selector newInstance(final QueryOptions options) + { + return new TupleSelector(type, factories.newInstances(options)); + } + }; + } + + public void addInput(ProtocolVersion protocolVersion, ResultSetBuilder rs) throws InvalidRequestException + { + for (int i = 0, m = elements.size(); i < m; i++) + elements.get(i).addInput(protocolVersion, rs); + } + + public ByteBuffer getOutput(ProtocolVersion protocolVersion) throws InvalidRequestException + { + ByteBuffer[] buffers = new ByteBuffer[elements.size()]; + for (int i = 0, m = elements.size(); i < m; i++) + { + buffers[i] = elements.get(i).getOutput(protocolVersion); + } + return TupleType.buildValue(buffers); + } + + public void reset() + { + for (int i = 0, m = elements.size(); i < m; i++) + elements.get(i).reset(); + } + + public AbstractType<?> getType() + { + return type; + } + + @Override + public String toString() + { + return Tuples.tupleToString(elements); + } + + private TupleSelector(AbstractType<?> type, List<Selector> elements) + { + this.type = type; + this.elements = elements; + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/cql3/selection/UserTypeSelector.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/selection/UserTypeSelector.java b/src/java/org/apache/cassandra/cql3/selection/UserTypeSelector.java new file mode 100644 index 0000000..3c298b5 --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/selection/UserTypeSelector.java @@ -0,0 +1,177 @@ +/* + * 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 java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.cassandra.config.ColumnDefinition; +import org.apache.cassandra.cql3.ColumnSpecification; +import org.apache.cassandra.cql3.FieldIdentifier; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.UserTypes; +import org.apache.cassandra.cql3.functions.Function; +import org.apache.cassandra.cql3.selection.Selection.ResultSetBuilder; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.TupleType; +import org.apache.cassandra.db.marshal.UserType; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.transport.ProtocolVersion; + +/** + * <code>Selector</code> for literal map (e.g. {'min' : min(value), 'max' : max(value), 'count' : count(value)}). + * + */ +final class UserTypeSelector extends Selector +{ + /** + * The map type. + */ + private final AbstractType<?> type; + + /** + * The user type fields + */ + private final Map<FieldIdentifier, Selector> fields; + + public static Factory newFactory(final AbstractType<?> type, final Map<FieldIdentifier, Factory> factories) + { + return new Factory() + { + protected String getColumnName() + { + return UserTypes.userTypeToString(factories, Factory::getColumnName); + } + + protected AbstractType<?> getReturnType() + { + return type; + } + + protected final void addColumnMapping(SelectionColumnMapping mapping, ColumnSpecification resultsColumn) + { + SelectionColumnMapping tmpMapping = SelectionColumnMapping.newMapping(); + for (Factory factory : factories.values()) + { + factory.addColumnMapping(tmpMapping, resultsColumn); + } + + if (tmpMapping.getMappings().get(resultsColumn).isEmpty()) + // add a null mapping for cases where the collection is empty + mapping.addMapping(resultsColumn, (ColumnDefinition)null); + else + // collate the mapped columns from the child factories & add those + mapping.addMapping(resultsColumn, tmpMapping.getMappings().values()); + } + + public Selector newInstance(final QueryOptions options) + { + Map<FieldIdentifier, Selector> fields = new HashMap<>(factories.size()); + for (Entry<FieldIdentifier, Factory> factory : factories.entrySet()) + fields.put(factory.getKey(), factory.getValue().newInstance(options)); + + return new UserTypeSelector(type, fields); + } + + @Override + public boolean isAggregateSelectorFactory() + { + for (Factory factory : factories.values()) + { + if (factory.isAggregateSelectorFactory()) + return true; + } + return false; + } + + @Override + public void addFunctionsTo(List<Function> functions) + { + for (Factory factory : factories.values()) + factory.addFunctionsTo(functions); + } + + @Override + public boolean isWritetimeSelectorFactory() + { + for (Factory factory : factories.values()) + { + if (factory.isWritetimeSelectorFactory()) + return true; + } + return false; + } + + @Override + public boolean isTTLSelectorFactory() + { + for (Factory factory : factories.values()) + { + if (factory.isTTLSelectorFactory()) + return true; + } + return false; + } + }; + } + + public void addInput(ProtocolVersion protocolVersion, ResultSetBuilder rs) throws InvalidRequestException + { + for (Selector field : fields.values()) + field.addInput(protocolVersion, rs); + } + + public ByteBuffer getOutput(ProtocolVersion protocolVersion) throws InvalidRequestException + { + UserType userType = (UserType) type; + ByteBuffer[] buffers = new ByteBuffer[userType.size()]; + for (int i = 0, m = userType.size(); i < m; i++) + { + Selector selector = fields.get(userType.fieldName(i)); + if (selector != null) + buffers[i] = selector.getOutput(protocolVersion); + } + return TupleType.buildValue(buffers); + } + + public void reset() + { + for (Selector field : fields.values()) + field.reset(); + } + + public AbstractType<?> getType() + { + return type; + } + + @Override + public String toString() + { + return UserTypes.userTypeToString(fields); + } + + private UserTypeSelector(AbstractType<?> type, Map<FieldIdentifier, Selector> fields) + { + this.type = type; + this.fields = fields; + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/db/SystemKeyspace.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/SystemKeyspace.java b/src/java/org/apache/cassandra/db/SystemKeyspace.java index 7f6b6ab..31a461b 100644 --- a/src/java/org/apache/cassandra/db/SystemKeyspace.java +++ b/src/java/org/apache/cassandra/db/SystemKeyspace.java @@ -477,6 +477,7 @@ public final class SystemKeyspace .add(BytesConversionFcts.all()) .add(AggregateFcts.all()) .add(CastFcts.all()) + .add(OperationFcts.all()) .build(); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/db/marshal/AbstractType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/AbstractType.java b/src/java/org/apache/cassandra/db/marshal/AbstractType.java index 37a1959..99df8a2 100644 --- a/src/java/org/apache/cassandra/db/marshal/AbstractType.java +++ b/src/java/org/apache/cassandra/db/marshal/AbstractType.java @@ -319,6 +319,11 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm return false; } + public boolean isTuple() + { + return false; + } + public boolean isMultiCell() { return false; @@ -384,7 +389,7 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm /** * The length of values for this type if all values are of fixed length, -1 otherwise. */ - protected int valueLengthIfFixed() + public int valueLengthIfFixed() { return -1; } @@ -487,9 +492,13 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm public final AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) { + return testAssignment(receiver.type); + } + + public final AssignmentTestable.TestResult testAssignment(AbstractType<?> receiverType) + { // We should ignore the fact that the output type is frozen in our comparison as functions do not support // frozen types for arguments - AbstractType<?> receiverType = receiver.type; if (isFreezable() && !isMultiCell()) receiverType = receiverType.freeze(); http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/db/marshal/BooleanType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/BooleanType.java b/src/java/org/apache/cassandra/db/marshal/BooleanType.java index 1dbd1af..475cae6 100644 --- a/src/java/org/apache/cassandra/db/marshal/BooleanType.java +++ b/src/java/org/apache/cassandra/db/marshal/BooleanType.java @@ -97,7 +97,7 @@ public class BooleanType extends AbstractType<Boolean> } @Override - protected int valueLengthIfFixed() + public int valueLengthIfFixed() { return 1; } http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/db/marshal/ByteType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/ByteType.java b/src/java/org/apache/cassandra/db/marshal/ByteType.java index 55aea8f..517e734 100644 --- a/src/java/org/apache/cassandra/db/marshal/ByteType.java +++ b/src/java/org/apache/cassandra/db/marshal/ByteType.java @@ -28,7 +28,7 @@ import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.ByteBufferUtil; -public class ByteType extends AbstractType<Byte> +public class ByteType extends NumberType<Byte> { public static final ByteType instance = new ByteType(); @@ -84,8 +84,62 @@ public class ByteType extends AbstractType<Byte> } @Override + public int valueLengthIfFixed() + { + return 1; + } + + @Override public TypeSerializer<Byte> getSerializer() { return ByteSerializer.instance; } + + @Override + public byte toByte(ByteBuffer value) + { + return ByteBufferUtil.toByte(value); + } + + @Override + public short toShort(ByteBuffer value) + { + return toByte(value); + } + + @Override + protected int toInt(ByteBuffer value) + { + return toByte(value); + } + + public ByteBuffer add(NumberType<?> leftType, ByteBuffer left, NumberType<?> rightType, ByteBuffer right) + { + return ByteBufferUtil.bytes((byte) (leftType.toByte(left) + rightType.toByte(right))); + } + + public ByteBuffer substract(NumberType<?> leftType, ByteBuffer left, NumberType<?> rightType, ByteBuffer right) + { + return ByteBufferUtil.bytes((byte) (leftType.toByte(left) - rightType.toByte(right))); + } + + public ByteBuffer multiply(NumberType<?> leftType, ByteBuffer left, NumberType<?> rightType, ByteBuffer right) + { + return ByteBufferUtil.bytes((byte) (leftType.toByte(left) * rightType.toByte(right))); + } + + public ByteBuffer divide(NumberType<?> leftType, ByteBuffer left, NumberType<?> rightType, ByteBuffer right) + { + return ByteBufferUtil.bytes((byte) (leftType.toByte(left) / rightType.toByte(right))); + } + + public ByteBuffer mod(NumberType<?> leftType, ByteBuffer left, NumberType<?> rightType, ByteBuffer right) + { + return ByteBufferUtil.bytes((byte) (leftType.toByte(left) % rightType.toByte(right))); + } + + public ByteBuffer negate(ByteBuffer input) + { + return ByteBufferUtil.bytes((byte) -toByte(input)); + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java b/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java index 8bb1a25..8777e0e 100644 --- a/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java +++ b/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java @@ -22,13 +22,13 @@ import java.nio.ByteBuffer; import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.cql3.Term; import org.apache.cassandra.db.context.CounterContext; -import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.serializers.CounterSerializer; import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.transport.ProtocolVersion; +import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.utils.ByteBufferUtil; -public class CounterColumnType extends AbstractType<Long> +public class CounterColumnType extends NumberType<Long> { public static final CounterColumnType instance = new CounterColumnType(); @@ -93,4 +93,40 @@ public class CounterColumnType extends AbstractType<Long> { return CounterSerializer.instance; } + + @Override + protected long toLong(ByteBuffer value) + { + return ByteBufferUtil.toLong(value); + } + + public ByteBuffer add(NumberType<?> leftType, ByteBuffer left, NumberType<?> rightType, ByteBuffer right) + { + return ByteBufferUtil.bytes(leftType.toLong(left) + rightType.toLong(right)); + } + + public ByteBuffer substract(NumberType<?> leftType, ByteBuffer left, NumberType<?> rightType, ByteBuffer right) + { + return ByteBufferUtil.bytes(leftType.toLong(left) - rightType.toLong(right)); + } + + public ByteBuffer multiply(NumberType<?> leftType, ByteBuffer left, NumberType<?> rightType, ByteBuffer right) + { + return ByteBufferUtil.bytes(leftType.toLong(left) * rightType.toLong(right)); + } + + public ByteBuffer divide(NumberType<?> leftType, ByteBuffer left, NumberType<?> rightType, ByteBuffer right) + { + return ByteBufferUtil.bytes(leftType.toLong(left) / rightType.toLong(right)); + } + + public ByteBuffer mod(NumberType<?> leftType, ByteBuffer left, NumberType<?> rightType, ByteBuffer right) + { + return ByteBufferUtil.bytes(leftType.toLong(left) % rightType.toLong(right)); + } + + public ByteBuffer negate(ByteBuffer input) + { + return ByteBufferUtil.bytes(-toLong(input)); + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/8b3de2f4/src/java/org/apache/cassandra/db/marshal/DateType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/DateType.java b/src/java/org/apache/cassandra/db/marshal/DateType.java index 87b2cad..473cedf 100644 --- a/src/java/org/apache/cassandra/db/marshal/DateType.java +++ b/src/java/org/apache/cassandra/db/marshal/DateType.java @@ -119,7 +119,7 @@ public class DateType extends AbstractType<Date> } @Override - protected int valueLengthIfFixed() + public int valueLengthIfFixed() { return 8; }