/* * 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); } } }