SqlModeHelper.java

// 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.doris.qe;


import org.apache.doris.common.DdlException;
import org.apache.doris.common.ErrorCode;
import org.apache.doris.common.ErrorReport;

import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class SqlModeHelper {
    private static final Logger LOG = LogManager.getLogger(SqlModeHelper.class);

    // TODO(xuyang): these mode types are copy from MYSQL mode types, which are example
    //  of how they works and to be compatible with MySQL, so for now they are not
    //  really meaningful.
    /* Bits for different SQL MODE modes, you can add custom SQL MODE here */
    /* When a new session is created, its sql mode is set to MODE_DEFAULT */
    public static final long MODE_DEFAULT = 1L;
    public static final long MODE_PIPES_AS_CONCAT = 2L;
    public static final long MODE_ANSI_QUOTES = 4L;
    public static final long MODE_IGNORE_SPACE = 8L;
    public static final long MODE_NOT_USED = 16L;
    public static final long MODE_ONLY_FULL_GROUP_BY = 32L;
    public static final long MODE_NO_UNSIGNED_SUBTRACTION = 64L;
    public static final long MODE_NO_DIR_IN_CREATE = 128L;
    public static final long MODE_NO_AUTO_VALUE_ON_ZERO = 1L << 19;
    public static final long MODE_NO_BACKSLASH_ESCAPES = 1L << 20;
    public static final long MODE_STRICT_TRANS_TABLES = 1L << 21;
    public static final long MODE_STRICT_ALL_TABLES = 1L << 22;
    // NO_ZERO_IN_DATE and NO_ZERO_DATE are removed in mysql 5.7 and merged into STRICT MODE.
    // However, for backward compatibility during upgrade, these modes are kept.
    @Deprecated
    public static final long MODE_NO_ZERO_IN_DATE = 1L << 23;
    @Deprecated
    public static final long MODE_NO_ZERO_DATE = 1L << 24;
    public static final long MODE_INVALID_DATES = 1L << 25;
    public static final long MODE_ERROR_FOR_DIVISION_BY_ZERO = 1L << 26;
    public static final long MODE_HIGH_NOT_PRECEDENCE = 1L << 29;
    public static final long MODE_NO_ENGINE_SUBSTITUTION = 1L << 30;
    public static final long MODE_PAD_CHAR_TO_FULL_LENGTH = 1L << 31;
    public static final long MODE_TIME_TRUNCATE_FRACTIONAL = 1L << 32;

    /* Bits for different COMBINE MODE modes, you can add custom COMBINE MODE here */
    public static final long MODE_ANSI = 1L << 18;
    public static final long MODE_TRADITIONAL = 1L << 27;

    public static final long MODE_LAST = 1L << 33;
    public static final long MODE_REAL_AS_FLOAT = 1L << 34;


    public static final long MODE_ALLOWED_MASK =
            (MODE_REAL_AS_FLOAT | MODE_PIPES_AS_CONCAT | MODE_ANSI_QUOTES | MODE_IGNORE_SPACE | MODE_NOT_USED
                    | MODE_ONLY_FULL_GROUP_BY | MODE_NO_UNSIGNED_SUBTRACTION | MODE_NO_DIR_IN_CREATE
                    | MODE_NO_AUTO_VALUE_ON_ZERO | MODE_NO_BACKSLASH_ESCAPES | MODE_STRICT_TRANS_TABLES
                    | MODE_STRICT_ALL_TABLES | MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE | MODE_INVALID_DATES
                    | MODE_ERROR_FOR_DIVISION_BY_ZERO | MODE_HIGH_NOT_PRECEDENCE | MODE_NO_ENGINE_SUBSTITUTION
                    | MODE_PAD_CHAR_TO_FULL_LENGTH | MODE_TRADITIONAL | MODE_ANSI | MODE_TIME_TRUNCATE_FRACTIONAL
                    | MODE_DEFAULT);

    public static final long MODE_COMBINE_MASK = (MODE_ANSI | MODE_TRADITIONAL);

    private static final Map<String, Long> sqlModeSet = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);

    private static final Map<String, Long> combineModeSet = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);

    static {
        sqlModeSet.put("DEFAULT", MODE_DEFAULT);
        sqlModeSet.put("REAL_AS_FLOAT", MODE_REAL_AS_FLOAT);
        sqlModeSet.put("PIPES_AS_CONCAT", MODE_PIPES_AS_CONCAT);
        sqlModeSet.put("ANSI_QUOTES", MODE_ANSI_QUOTES);
        sqlModeSet.put("IGNORE_SPACE", MODE_IGNORE_SPACE);
        sqlModeSet.put("NOT_USED", MODE_NOT_USED);
        sqlModeSet.put("ONLY_FULL_GROUP_BY", MODE_ONLY_FULL_GROUP_BY);
        sqlModeSet.put("NO_UNSIGNED_SUBTRACTION", MODE_NO_UNSIGNED_SUBTRACTION);
        sqlModeSet.put("NO_DIR_IN_CREATE", MODE_NO_DIR_IN_CREATE);
        sqlModeSet.put("ANSI", MODE_ANSI);
        sqlModeSet.put("NO_AUTO_VALUE_ON_ZERO", MODE_NO_AUTO_VALUE_ON_ZERO);
        sqlModeSet.put("NO_BACKSLASH_ESCAPES", MODE_NO_BACKSLASH_ESCAPES);
        sqlModeSet.put("STRICT_TRANS_TABLES", MODE_STRICT_TRANS_TABLES);
        sqlModeSet.put("STRICT_ALL_TABLES", MODE_STRICT_ALL_TABLES);
        sqlModeSet.put("NO_ZERO_IN_DATE", MODE_NO_ZERO_IN_DATE);
        sqlModeSet.put("NO_ZERO_DATE", MODE_NO_ZERO_DATE);
        sqlModeSet.put("INVALID_DATES", MODE_INVALID_DATES);
        sqlModeSet.put("ERROR_FOR_DIVISION_BY_ZERO", MODE_ERROR_FOR_DIVISION_BY_ZERO);
        sqlModeSet.put("TRADITIONAL", MODE_TRADITIONAL);
        sqlModeSet.put("HIGH_NOT_PRECEDENCE", MODE_HIGH_NOT_PRECEDENCE);
        sqlModeSet.put("NO_ENGINE_SUBSTITUTION", MODE_NO_ENGINE_SUBSTITUTION);
        sqlModeSet.put("PAD_CHAR_TO_FULL_LENGTH", MODE_PAD_CHAR_TO_FULL_LENGTH);
        sqlModeSet.put("TIME_TRUNCATE_FRACTIONAL", MODE_TIME_TRUNCATE_FRACTIONAL);

        combineModeSet.put("ANSI", (MODE_REAL_AS_FLOAT | MODE_PIPES_AS_CONCAT
                | MODE_ANSI_QUOTES | MODE_IGNORE_SPACE | MODE_ONLY_FULL_GROUP_BY));
        combineModeSet.put("TRADITIONAL", (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES
                | MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE | MODE_ERROR_FOR_DIVISION_BY_ZERO
                | MODE_NO_ENGINE_SUBSTITUTION));
    }

    // convert long type SQL MODE to string type that user can read
    public static String decode(Long sqlMode) throws DdlException {
        if (sqlMode == MODE_DEFAULT) {
            //For compatibility with older versions, return empty string
            return "";
        }
        if ((sqlMode & ~MODE_ALLOWED_MASK) != 0) {
            ErrorReport.reportDdlException(ErrorCode.ERR_WRONG_VALUE_FOR_VAR, SessionVariable.SQL_MODE, sqlMode);
        }

        List<String> names = new ArrayList<String>();
        for (Map.Entry<String, Long> mode : getSupportedSqlMode().entrySet()) {
            if ((sqlMode & mode.getValue()) != 0) {
                names.add(mode.getKey());
            }
        }

        return Joiner.on(',').join(names);
    }

    // convert string type SQL MODE to long type that session can store
    public static Long encode(String sqlMode) throws DdlException {
        List<String> names =
                Splitter.on(',').trimResults().omitEmptyStrings().splitToList(sqlMode);

        // empty string parse to 0
        long resultCode = 0L;
        for (String key : names) {
            long code = 0L;
            if (StringUtils.isNumeric(key)) {
                code |= expand(Long.valueOf(key));
            } else {
                code = getCodeFromString(key);
                if (code == 0) {
                    ErrorReport.reportDdlException(ErrorCode.ERR_WRONG_VALUE_FOR_VAR, SessionVariable.SQL_MODE, key);
                }
            }
            resultCode |= code;
            if ((resultCode & ~MODE_ALLOWED_MASK) != 0) {
                ErrorReport.reportDdlException(ErrorCode.ERR_WRONG_VALUE_FOR_VAR, SessionVariable.SQL_MODE, key);
            }
        }
        return resultCode;
    }

    // expand the combine mode if exists
    public static long expand(long sqlMode) throws DdlException {
        for (String key : getCombineMode().keySet()) {
            if ((sqlMode & getSupportedSqlMode().get(key)) != 0) {
                sqlMode |= getCombineMode().get(key);
            }
        }
        return sqlMode;
    }

    // check if this SQL MODE is supported
    public static boolean isSupportedSqlMode(String sqlMode) {
        if (sqlMode == null || !getSupportedSqlMode().containsKey(sqlMode)) {
            return false;
        }
        return true;
    }

    // encode sqlMode from string to long
    private static long getCodeFromString(String sqlMode) {
        long code = 0L;
        if (isSupportedSqlMode(sqlMode)) {
            if (isCombineMode(sqlMode)) {
                code |= getCombineMode().get(sqlMode);
            }
            code |= getSupportedSqlMode().get(sqlMode);
        }
        return code;
    }

    // check if this SQL MODE is combine mode
    public static boolean isCombineMode(String key) {
        return combineModeSet.containsKey(key);
    }

    public static Map<String, Long> getSupportedSqlMode() {
        return sqlModeSet;
    }

    public static Map<String, Long> getCombineMode() {
        return combineModeSet;
    }

    public static boolean hasNoBackSlashEscapes() {
        SessionVariable sessionVariable = ConnectContext.get() == null
                ? VariableMgr.newSessionVariable()
                : ConnectContext.get().getSessionVariable();
        return ((sessionVariable.getSqlMode() & MODE_ALLOWED_MASK)
                & MODE_NO_BACKSLASH_ESCAPES) != 0;
    }

    public static boolean hasPipeAsConcat() {
        SessionVariable sessionVariable = ConnectContext.get() == null
                ? VariableMgr.newSessionVariable()
                : ConnectContext.get().getSessionVariable();
        return ((sessionVariable.getSqlMode() & MODE_ALLOWED_MASK)
                & MODE_PIPES_AS_CONCAT) != 0;
    }

    public static boolean hasOnlyFullGroupBy() {
        SessionVariable sessionVariable = ConnectContext.get() == null
                ? VariableMgr.newSessionVariable()
                : ConnectContext.get().getSessionVariable();
        return ((sessionVariable.getSqlMode() & MODE_ALLOWED_MASK)
                & MODE_ONLY_FULL_GROUP_BY) != 0;
    }

}