All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.cassandra.cql3.functions.CastFcts Maven / Gradle / Ivy

Go to download

The Apache Cassandra Project develops a highly scalable second-generation distributed database, bringing together Dynamo's fully distributed design and Bigtable's ColumnFamily-based data model.

There is a newer version: 5.0.0
Show newest version
/*
 * 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.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.List;

import org.apache.cassandra.cql3.CQL3Type;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.AsciiType;
import org.apache.cassandra.db.marshal.BooleanType;
import org.apache.cassandra.db.marshal.ByteType;
import org.apache.cassandra.db.marshal.CounterColumnType;
import org.apache.cassandra.db.marshal.DecimalType;
import org.apache.cassandra.db.marshal.DoubleType;
import org.apache.cassandra.db.marshal.FloatType;
import org.apache.cassandra.db.marshal.InetAddressType;
import org.apache.cassandra.db.marshal.Int32Type;
import org.apache.cassandra.db.marshal.IntegerType;
import org.apache.cassandra.db.marshal.LongType;
import org.apache.cassandra.db.marshal.ShortType;
import org.apache.cassandra.db.marshal.SimpleDateType;
import org.apache.cassandra.db.marshal.TimeType;
import org.apache.cassandra.db.marshal.TimeUUIDType;
import org.apache.cassandra.db.marshal.TimestampType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.db.marshal.UUIDType;
import org.apache.cassandra.transport.ProtocolVersion;

import static org.apache.cassandra.cql3.functions.TimeFcts.*;

import org.apache.commons.lang3.StringUtils;

/**
 * Casting functions
 *
 */
public final class CastFcts
{
    private static final String FUNCTION_NAME_PREFIX = "cast_as_";

    // Until 5.0 we have used camel cased names for cast functions. That changed with CASSANDRA-18037, where we decided
    // to adopt snake case for all native function names. However, we should still support the old came case names.
    private static final String LEGACY_FUNCTION_NAME_PREFIX = "castAs";

    public static void addFunctionsTo(NativeFunctions functions)
    {
        @SuppressWarnings("unchecked")
        final AbstractType[] numericTypes = new AbstractType[] {ByteType.instance,
                                                                                  ShortType.instance,
                                                                                  Int32Type.instance,
                                                                                  LongType.instance,
                                                                                  FloatType.instance,
                                                                                  DoubleType.instance,
                                                                                  DecimalType.instance,
                                                                                  CounterColumnType.instance,
                                                                                  IntegerType.instance};

        for (AbstractType inputType : numericTypes)
        {
            addFunctionIfNeeded(functions, inputType, ByteType.instance, Number::byteValue);
            addFunctionIfNeeded(functions, inputType, ShortType.instance, Number::shortValue);
            addFunctionIfNeeded(functions, inputType, Int32Type.instance, Number::intValue);
            addFunctionIfNeeded(functions, inputType, LongType.instance, Number::longValue);
            addFunctionIfNeeded(functions, inputType, FloatType.instance, Number::floatValue);
            addFunctionIfNeeded(functions, inputType, DoubleType.instance, Number::doubleValue);
            addFunctionIfNeeded(functions, inputType, DecimalType.instance, getDecimalConversionFunction(inputType));
            addFunctionIfNeeded(functions, inputType, IntegerType.instance, p -> BigInteger.valueOf(p.longValue()));
            functions.add(CastAsTextFunction.create(inputType, AsciiType.instance));
            functions.add(CastAsTextFunction.create(inputType, UTF8Type.instance));
        }

        functions.add(JavaFunctionWrapper.create(AsciiType.instance, UTF8Type.instance, p -> p));

        functions.add(CastAsTextFunction.create(InetAddressType.instance, AsciiType.instance));
        functions.add(CastAsTextFunction.create(InetAddressType.instance, UTF8Type.instance));

        functions.add(CastAsTextFunction.create(BooleanType.instance, AsciiType.instance));
        functions.add(CastAsTextFunction.create(BooleanType.instance, UTF8Type.instance));

        functions.add(CassandraFunctionWrapper.create(TimeUUIDType.instance, SimpleDateType.instance, toDate(TimeUUIDType.instance)));
        functions.add(CassandraFunctionWrapper.create(TimeUUIDType.instance, TimestampType.instance, toTimestamp(TimeUUIDType.instance)));
        functions.add(CastAsTextFunction.create(TimeUUIDType.instance, AsciiType.instance));
        functions.add(CastAsTextFunction.create(TimeUUIDType.instance, UTF8Type.instance));
        functions.add(CassandraFunctionWrapper.create(TimestampType.instance, SimpleDateType.instance, toDate(TimestampType.instance)));
        functions.add(CastAsTextFunction.create(TimestampType.instance, AsciiType.instance));
        functions.add(CastAsTextFunction.create(TimestampType.instance, UTF8Type.instance));
        functions.add(CassandraFunctionWrapper.create(SimpleDateType.instance, TimestampType.instance, toTimestamp(SimpleDateType.instance)));
        functions.add(CastAsTextFunction.create(SimpleDateType.instance, AsciiType.instance));
        functions.add(CastAsTextFunction.create(SimpleDateType.instance, UTF8Type.instance));
        functions.add(CastAsTextFunction.create(TimeType.instance, AsciiType.instance));
        functions.add(CastAsTextFunction.create(TimeType.instance, UTF8Type.instance));

        functions.add(CastAsTextFunction.create(UUIDType.instance, AsciiType.instance));
        functions.add(CastAsTextFunction.create(UUIDType.instance, UTF8Type.instance));
    }

    /**
     * Returns the conversion function to convert the specified type into a Decimal type
     *
     * @param inputType the input type
     * @return the conversion function to convert the specified type into a Decimal type
     */
    private static  java.util.function.Function getDecimalConversionFunction(AbstractType inputType)
    {
        if (inputType == FloatType.instance)
            return p -> new BigDecimal(Float.toString(p.floatValue()));

        if (inputType == DoubleType.instance)
            return p -> BigDecimal.valueOf(p.doubleValue());

        if (inputType == IntegerType.instance)
            return p -> new BigDecimal((BigInteger) p);

        return p -> BigDecimal.valueOf(p.longValue());
    }

    /**
     * Creates the snake-cased name of the cast function used to cast to the specified type.
     *
     * @param outputType the output type
     * @return the name of the cast function used to cast to the specified type
     */
    public static String getFunctionName(CQL3Type outputType)
    {
        return FUNCTION_NAME_PREFIX + toLowerCaseString(outputType);
    }

    /**
     * Creates the legacy camel-cased name of the cast function used to cast to the specified type.
     *
     * @param outputType the output type
     * @return the legacy camel-cased name of the cast function used to cast to the specified type
     */
    private static String getLegacyFunctionName(CQL3Type outputType)
    {
        return LEGACY_FUNCTION_NAME_PREFIX + StringUtils.capitalize(toLowerCaseString(outputType));
    }

    /**
     * Creates the name of the cast function used to cast to the specified type.
     *
     * @param outputType the output type
     * @param legacy whether to use the old cameCase names, instead of the new snake_case names
     * @return the name of the cast function used to cast to the specified type
     */
    private static String getFunctionName(AbstractType outputType, boolean legacy)
    {
        CQL3Type type = outputType.asCQL3Type();
        return legacy ? getLegacyFunctionName(type) : getFunctionName(type);
    }

    /**
     * Adds to the list a function converting the input type in to the output type if they are not the same.
     *
     * @param functions the list to add to
     * @param inputType the input type
     * @param outputType the output type
     * @param converter the function use to convert the input type into the output type
     */
    private static  void addFunctionIfNeeded(NativeFunctions functions,
                                                   AbstractType inputType,
                                                   AbstractType outputType,
                                                   java.util.function.Function converter)
    {
        if (!inputType.equals(outputType))
            functions.add(wrapJavaFunction(inputType, outputType, converter));
    }

    @SuppressWarnings("unchecked")
    private static  CastFunction wrapJavaFunction(AbstractType inputType,
                                                              AbstractType outputType,
                                                              java.util.function.Function converter)
    {
        return inputType.equals(CounterColumnType.instance)
                ? JavaCounterFunctionWrapper.create(outputType, (java.util.function.Function) converter)
                : JavaFunctionWrapper.create(inputType, outputType, converter);
    }

    private static String toLowerCaseString(CQL3Type type)
    {
        return type.toString().toLowerCase();
    }

    /**
     * Base class for the CAST functions.
     *
     * @param  the input type
     * @param  the output type
     */
    private static abstract class CastFunction extends NativeScalarFunction
    {
        public CastFunction(AbstractType inputType, AbstractType outputType, boolean useLegacyName)
        {
            super(getFunctionName(outputType, useLegacyName), outputType, inputType);
        }

        @Override
        public String columnName(List columnNames)
        {
            return String.format("cast(%s as %s)", columnNames.get(0), toLowerCaseString(outputType().asCQL3Type()));
        }

        @SuppressWarnings("unchecked")
        protected AbstractType outputType()
        {
            return (AbstractType) returnType;
        }

        @SuppressWarnings("unchecked")
        protected AbstractType inputType()
        {
            return (AbstractType) argTypes.get(0);
        }
    }

    /**
     * CastFunction that implements casting by wrapping a java Function.
     *
     * @param  the input parameter
     * @param  the output parameter
     */
    private static class JavaFunctionWrapper extends CastFunction
    {
        /**
         * The java function used to convert the input type into the output one.
         */
        protected final java.util.function.Function converter;

        public static  JavaFunctionWrapper create(AbstractType inputType,
                                                              AbstractType outputType,
                                                              java.util.function.Function converter)
        {
            return new JavaFunctionWrapper<>(inputType, outputType, converter, false);
        }

        protected JavaFunctionWrapper(AbstractType inputType,
                                      AbstractType outputType,
                                      java.util.function.Function converter,
                                      boolean useLegacyName)
        {
            super(inputType, outputType, useLegacyName);
            this.converter = converter;
        }

        @Override
        public JavaFunctionWrapper withLegacyName()
        {
            return new JavaFunctionWrapper<>(inputType(), outputType(), converter, true);
        }

        @Override
        public final ByteBuffer execute(Arguments arguments)
        {
            if (arguments.containsNulls())
                return null;

            return outputType().decompose(converter.apply(arguments.get(0)));
        }

        protected I compose(ByteBuffer bb)
        {
            return inputType().compose(bb);
        }
    }

    /**
     * JavaFunctionWrapper for counter columns.
     *
     * 

Counter columns need to be handled in a special way because their binary representation is converted into * the one of a BIGINT before functions are applied.

* * @param the output parameter */ private static class JavaCounterFunctionWrapper extends JavaFunctionWrapper { public static JavaFunctionWrapper create(AbstractType outputType, java.util.function.Function converter) { return new JavaCounterFunctionWrapper<>(outputType, converter, false); } protected JavaCounterFunctionWrapper(AbstractType outputType, java.util.function.Function converter, boolean useLegacyName) { super(CounterColumnType.instance, outputType, converter, useLegacyName); } @Override public JavaCounterFunctionWrapper withLegacyName() { return new JavaCounterFunctionWrapper<>(outputType(), converter, true); } protected Long compose(ByteBuffer bb) { return LongType.instance.compose(bb); } } /** * CastFunction that implements casting by wrapping an existing NativeScalarFunction. * * @param the input parameter * @param the output parameter */ private static final class CassandraFunctionWrapper extends CastFunction { /** * The native scalar function used to perform the conversion. */ private final NativeScalarFunction delegate; public static CassandraFunctionWrapper create(AbstractType inputType, AbstractType outputType, NativeScalarFunction delegate) { return new CassandraFunctionWrapper<>(inputType, outputType, delegate, false); } private CassandraFunctionWrapper(AbstractType inputType, AbstractType outputType, NativeScalarFunction delegate, boolean useLegacyName) { super(inputType, outputType, useLegacyName); assert delegate.argTypes().size() == 1 && inputType.equals(delegate.argTypes().get(0)); assert outputType.equals(delegate.returnType()); this.delegate = delegate; } @Override public CassandraFunctionWrapper withLegacyName() { return new CassandraFunctionWrapper<>(inputType(), outputType(), delegate, true); } @Override public ByteBuffer execute(Arguments arguments) { return delegate.execute(arguments); } } /** * CastFunction that can be used to cast a type into ascii or text types. * * @param the input parameter */ private static final class CastAsTextFunction extends CastFunction { public static CastAsTextFunction create(AbstractType inputType, AbstractType outputType) { return new CastAsTextFunction<>(inputType, outputType, false); } private CastAsTextFunction(AbstractType inputType, AbstractType outputType, boolean useLegacyName) { super(inputType, outputType, useLegacyName); } @Override public CastAsTextFunction withLegacyName() { return new CastAsTextFunction<>(inputType(), outputType(), true); } @Override public Arguments newArguments(ProtocolVersion version) { return new FunctionArguments(version, (protocolVersion, buffer) -> { AbstractType argType = argTypes.get(0); if (buffer == null || (!buffer.hasRemaining() && argType.isEmptyValueMeaningless())) return null; return argType.getSerializer().toCQLLiteralNoQuote(buffer); }); } @Override public ByteBuffer execute(Arguments arguments) { if (arguments.containsNulls()) return null; return outputType().decompose(arguments.get(0)); } } /** * The class must not be instantiated as it contains only static variables. */ private CastFcts() { } }