| /* |
| * 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.schema.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 executeOnNumerics(NumberType<?> resultType, |
| NumberType<?> leftType, |
| ByteBuffer left, |
| NumberType<?> rightType, |
| ByteBuffer right) |
| { |
| return resultType.add(leftType, left, rightType, right); |
| } |
| |
| @Override |
| protected ByteBuffer executeOnTemporals(TemporalType<?> type, |
| ByteBuffer temporal, |
| ByteBuffer duration) |
| { |
| return type.addDuration(temporal, duration); |
| } |
| }, |
| SUBSTRACTION('-', "_substract") |
| { |
| protected ByteBuffer executeOnNumerics(NumberType<?> resultType, |
| NumberType<?> leftType, |
| ByteBuffer left, |
| NumberType<?> rightType, |
| ByteBuffer right) |
| { |
| return resultType.substract(leftType, left, rightType, right); |
| } |
| |
| @Override |
| protected ByteBuffer executeOnTemporals(TemporalType<?> type, |
| ByteBuffer temporal, |
| ByteBuffer duration) |
| { |
| return type.substractDuration(temporal, duration); |
| } |
| }, |
| MULTIPLICATION('*', "_multiply") |
| { |
| protected ByteBuffer executeOnNumerics(NumberType<?> resultType, |
| NumberType<?> leftType, |
| ByteBuffer left, |
| NumberType<?> rightType, |
| ByteBuffer right) |
| { |
| return resultType.multiply(leftType, left, rightType, right); |
| } |
| }, |
| DIVISION('/', "_divide") |
| { |
| protected ByteBuffer executeOnNumerics(NumberType<?> resultType, |
| NumberType<?> leftType, |
| ByteBuffer left, |
| NumberType<?> rightType, |
| ByteBuffer right) |
| { |
| return resultType.divide(leftType, left, rightType, right); |
| } |
| }, |
| MODULO('%', "_modulo") |
| { |
| protected ByteBuffer executeOnNumerics(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 numeric 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 executeOnNumerics(NumberType<?> resultType, |
| NumberType<?> leftType, |
| ByteBuffer left, |
| NumberType<?> rightType, |
| ByteBuffer right); |
| |
| /** |
| * Executes the operation on the specified temporal operand. |
| * |
| * @param type the temporal type |
| * @param temporal the temporal value |
| * @param duration the duration |
| * @return the operation result |
| */ |
| protected ByteBuffer executeOnTemporals(TemporalType<?> type, |
| ByteBuffer temporal, |
| ByteBuffer duration) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * 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); |
| for (OPERATION operation : OPERATION.values()) |
| functions.add(new NumericOperationFunction(returnType, left, operation, right)); |
| } |
| functions.add(new NumericNegationFunction(left)); |
| } |
| |
| for (OPERATION operation : new OPERATION[] {OPERATION.ADDITION, OPERATION.SUBSTRACTION}) |
| { |
| functions.add(new TemporalOperationFunction(TimestampType.instance, operation)); |
| functions.add(new TemporalOperationFunction(SimpleDateType.instance, operation)); |
| } |
| |
| 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; |
| |
| // tinyint and smallint type are not fixed length types even if they should be. |
| // So we need to handle them in a special way. |
| if (type == ByteType.instance) |
| return 1; |
| |
| if (type == ShortType.instance) |
| return 2; |
| |
| 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() |
| { |
| } |
| |
| /** |
| * Base class for functions that execute operations. |
| */ |
| private static abstract class OperationFunction extends NativeScalarFunction |
| { |
| private final OPERATION operation; |
| |
| public OperationFunction(AbstractType<?> returnType, |
| AbstractType<?> left, |
| OPERATION operation, |
| AbstractType<?> 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; |
| |
| try |
| { |
| return doExecute(left, operation, right); |
| } |
| catch (Exception e) |
| { |
| throw OperationExecutionException.create(getOperator(), argTypes, e); |
| } |
| } |
| |
| protected abstract ByteBuffer doExecute(ByteBuffer left, OPERATION operation, ByteBuffer right); |
| |
| /** |
| * Returns the operator symbol. |
| * @return the operator symbol |
| */ |
| private final char getOperator() |
| { |
| return operation.symbol; |
| } |
| } |
| |
| /** |
| * Function that execute operations on numbers. |
| */ |
| private static class NumericOperationFunction extends OperationFunction |
| { |
| public NumericOperationFunction(NumberType<?> returnType, |
| NumberType<?> left, |
| OPERATION operation, |
| NumberType<?> right) |
| { |
| super(returnType, left, operation, right); |
| } |
| |
| @Override |
| protected ByteBuffer doExecute(ByteBuffer left, OPERATION operation, ByteBuffer right) |
| { |
| NumberType<?> leftType = (NumberType<?>) argTypes().get(0); |
| NumberType<?> rightType = (NumberType<?>) argTypes().get(1); |
| NumberType<?> resultType = (NumberType<?>) returnType(); |
| |
| return operation.executeOnNumerics(resultType, leftType, left, rightType, right); |
| } |
| } |
| |
| /** |
| * Function that execute operations on temporals (timestamp, date, ...). |
| */ |
| private static class TemporalOperationFunction extends OperationFunction |
| { |
| public TemporalOperationFunction(TemporalType<?> type, |
| OPERATION operation) |
| { |
| super(type, type, operation, DurationType.instance); |
| } |
| |
| @Override |
| protected ByteBuffer doExecute(ByteBuffer left, OPERATION operation, ByteBuffer right) |
| { |
| TemporalType<?> resultType = (TemporalType<?>) returnType(); |
| return operation.executeOnTemporals(resultType, left, right); |
| } |
| } |
| |
| /** |
| * Function that negate a number. |
| */ |
| private static class NumericNegationFunction extends NativeScalarFunction |
| { |
| public NumericNegationFunction(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); |
| } |
| } |
| } |