org.apache.calcite.adapter.druid.DruidSqlCastConverter.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.calcite.adapter.druid.DruidSqlCastConverter.java

Source

/*
 * 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.calcite.adapter.druid;

import org.apache.calcite.avatica.util.DateTimeUtils;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.type.SqlTypeName;

import com.google.common.collect.ImmutableList;

import org.joda.time.Period;

import java.util.TimeZone;

/**
 * Druid cast converter operator used to translates calcite casts to Druid expression cast
 */
public class DruidSqlCastConverter implements DruidSqlOperatorConverter {

    @Override
    public SqlOperator calciteOperator() {
        return SqlStdOperatorTable.CAST;
    }

    @Override
    public String toDruidExpression(RexNode rexNode, RelDataType topRel, DruidQuery druidQuery) {

        final RexNode operand = ((RexCall) rexNode).getOperands().get(0);
        final String operandExpression = DruidExpressions.toDruidExpression(operand, topRel, druidQuery);

        if (operandExpression == null) {
            return null;
        }

        final SqlTypeName fromType = operand.getType().getSqlTypeName();
        final SqlTypeName toType = rexNode.getType().getSqlTypeName();
        final String timeZoneConf = druidQuery.getConnectionConfig().timeZone();
        final TimeZone timeZone = TimeZone.getTimeZone(timeZoneConf == null ? "UTC" : timeZoneConf);
        final boolean nullEqualToEmpty = druidQuery.getConnectionConfig().nullEqualToEmpty();

        if (SqlTypeName.CHAR_TYPES.contains(fromType) && SqlTypeName.DATETIME_TYPES.contains(toType)) {
            //case chars to dates
            return castCharToDateTime(
                    toType == SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE ? timeZone : DateTimeUtils.UTC_ZONE,
                    operandExpression, toType, nullEqualToEmpty ? "" : null);
        } else if (SqlTypeName.DATETIME_TYPES.contains(fromType) && SqlTypeName.CHAR_TYPES.contains(toType)) {
            //case dates to chars
            return castDateTimeToChar(
                    fromType == SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE ? timeZone : DateTimeUtils.UTC_ZONE,
                    operandExpression, fromType);
        } else if (SqlTypeName.DATETIME_TYPES.contains(fromType)
                && toType == SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE) {
            if (timeZone.equals(DateTimeUtils.UTC_ZONE)) {
                // bail out, internal representation is the same,
                // we do not need to do anything
                return operandExpression;
            }
            // to timestamp with local time zone
            return castCharToDateTime(timeZone,
                    castDateTimeToChar(DateTimeUtils.UTC_ZONE, operandExpression, fromType), toType,
                    nullEqualToEmpty ? "" : null);
        } else if (fromType == SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE
                && SqlTypeName.DATETIME_TYPES.contains(toType)) {
            if (toType != SqlTypeName.DATE && timeZone.equals(DateTimeUtils.UTC_ZONE)) {
                // bail out, internal representation is the same,
                // we do not need to do anything
                return operandExpression;
            }
            // timestamp with local time zone to other types
            return castCharToDateTime(DateTimeUtils.UTC_ZONE,
                    castDateTimeToChar(timeZone, operandExpression, fromType), toType,
                    nullEqualToEmpty ? "" : null);
        } else {
            // Handle other casts.
            final DruidType fromExprType = DruidExpressions.EXPRESSION_TYPES.get(fromType);
            final DruidType toExprType = DruidExpressions.EXPRESSION_TYPES.get(toType);

            if (fromExprType == null || toExprType == null) {
                // Unknown types bail out.
                return null;
            }
            final String typeCastExpression;
            if (fromExprType != toExprType) {
                typeCastExpression = DruidQuery.format("CAST(%s, '%s')", operandExpression, toExprType.toString());
            } else {
                // case it is the same type it is ok to skip CAST
                typeCastExpression = operandExpression;
            }

            if (toType == SqlTypeName.DATE) {
                // Floor to day when casting to DATE.
                return DruidExpressions.applyTimestampFloor(typeCastExpression, Period.days(1).toString(), "",
                        TimeZone.getTimeZone(druidQuery.getConnectionConfig().timeZone()));
            } else {
                return typeCastExpression;
            }

        }
    }

    private static String castCharToDateTime(TimeZone timeZone, String operand, final SqlTypeName toType,
            String format) {
        // Cast strings to date times by parsing them from SQL format.
        final String timestampExpression = DruidExpressions.functionCall("timestamp_parse", ImmutableList.of(
                operand, DruidExpressions.stringLiteral(format), DruidExpressions.stringLiteral(timeZone.getID())));

        if (toType == SqlTypeName.DATE) {
            // case to date we need to floor to day first
            return DruidExpressions.applyTimestampFloor(timestampExpression, Period.days(1).toString(), "",
                    timeZone);
        } else if (toType == SqlTypeName.TIMESTAMP || toType == SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE) {
            return timestampExpression;
        } else {
            throw new IllegalStateException(DruidQuery.format("Unsupported DateTime type[%s]", toType));
        }
    }

    private static String castDateTimeToChar(final TimeZone timeZone, final String operand,
            final SqlTypeName fromType) {
        return DruidExpressions.functionCall("timestamp_format",
                ImmutableList.of(operand, DruidExpressions.stringLiteral(dateTimeFormatString(fromType)),
                        DruidExpressions.stringLiteral(timeZone.getID())));
    }

    public static String dateTimeFormatString(final SqlTypeName sqlTypeName) {
        if (sqlTypeName == SqlTypeName.DATE) {
            return "yyyy-MM-dd";
        } else if (sqlTypeName == SqlTypeName.TIMESTAMP) {
            return "yyyy-MM-dd HH:mm:ss";
        } else if (sqlTypeName == SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE) {
            return "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
        } else {
            return null;
        }
    }
}

// End DruidSqlCastConverter.java