JdbcNameUtil.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.datasource.jdbc;

import org.apache.doris.thrift.TOdbcTableType;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Utility class for formatting database, table, and column names according to
 * the target JDBC/ODBC database type conventions (quoting, case rules).
 *
 * <p>Extracted from {@code JdbcTable} so that both {@code JdbcExternalTable}
 * and any remaining ODBC code paths can share the same logic.
 */
public final class JdbcNameUtil {

    private JdbcNameUtil() {
    }

    // ========= Core formatting =========

    /**
     * Formats a name (database, table, or schema) by wrapping each component
     * with the specified characters, optionally converting case.
     *
     * <p>The name is expected in {@code "schemaName.tableName"} format.
     * If there is no '.', the entire string is treated as one component.
     */
    public static String formatName(String name, String wrapStart, String wrapEnd,
            boolean toUpperCase, boolean toLowerCase) {
        int index = name.indexOf(".");
        if (index == -1) {
            String newName = toUpperCase ? name.toUpperCase() : name;
            newName = toLowerCase ? newName.toLowerCase() : newName;
            return wrapStart + newName + wrapEnd;
        } else {
            String schemaName = toUpperCase ? name.substring(0, index).toUpperCase() : name.substring(0, index);
            schemaName = toLowerCase ? schemaName.toLowerCase() : schemaName;
            String tableName = toUpperCase ? name.substring(index + 1).toUpperCase() : name.substring(index + 1);
            tableName = toLowerCase ? tableName.toLowerCase() : tableName;
            return wrapStart + schemaName + wrapEnd + "." + wrapStart + tableName + wrapEnd;
        }
    }

    /**
     * Formats a database/table/column name according to the database type quoting rules.
     */
    public static String databaseProperName(TOdbcTableType tableType, String name) {
        switch (tableType) {
            case MYSQL:
            case OCEANBASE:
            case GBASE:
                return formatName(name, "`", "`", false, false);
            case SQLSERVER:
                return formatName(name, "[", "]", false, false);
            case POSTGRESQL:
            case CLICKHOUSE:
            case TRINO:
            case PRESTO:
            case OCEANBASE_ORACLE:
            case SAP_HANA:
                return formatName(name, "\"", "\"", false, false);
            case ORACLE:
            case DB2:
                return formatName(name, "\"", "\"", true, false);
            default:
                return name;
        }
    }

    /**
     * Wraps a remote name (already in the correct case) with the appropriate quotes for the database type.
     */
    public static String properNameWithRemoteName(TOdbcTableType tableType, String remoteName) {
        switch (tableType) {
            case MYSQL:
            case OCEANBASE:
            case GBASE:
                return formatNameWithRemoteName(remoteName, "`", "`");
            case SQLSERVER:
                return formatNameWithRemoteName(remoteName, "[", "]");
            case POSTGRESQL:
            case CLICKHOUSE:
            case TRINO:
            case PRESTO:
            case OCEANBASE_ORACLE:
            case ORACLE:
            case SAP_HANA:
            case DB2:
                return formatNameWithRemoteName(remoteName, "\"", "\"");
            default:
                return remoteName;
        }
    }

    public static String formatNameWithRemoteName(String remoteName, String wrapStart, String wrapEnd) {
        return wrapStart + remoteName + wrapEnd;
    }

    // ========= Composite name builders =========

    /**
     * Build the properly quoted full table name (database.table) using remote names.
     *
     * @param tableType       the target database type
     * @param remoteDatabaseName remote database name (may be null for legacy internal tables)
     * @param remoteTableName    remote table name (may be null for legacy internal tables)
     * @param externalTableName  fallback name when remote names are not available
     */
    public static String getProperRemoteFullTableName(TOdbcTableType tableType, String remoteDatabaseName,
            String remoteTableName, String externalTableName) {
        if (remoteDatabaseName == null || remoteTableName == null) {
            return databaseProperName(tableType, externalTableName);
        } else {
            return properNameWithRemoteName(tableType, remoteDatabaseName) + "."
                    + properNameWithRemoteName(tableType, remoteTableName);
        }
    }

    /**
     * Build the properly quoted column name, looking up the remote name from the mapping.
     *
     * @param tableType         the target database type
     * @param columnName        the local column name
     * @param remoteColumnNames mapping from local column name to remote column name (may be null)
     */
    public static String getProperRemoteColumnName(TOdbcTableType tableType, String columnName,
            Map<String, String> remoteColumnNames) {
        if (remoteColumnNames == null || remoteColumnNames.isEmpty() || !remoteColumnNames.containsKey(columnName)) {
            return databaseProperName(tableType, columnName);
        } else {
            return properNameWithRemoteName(tableType, remoteColumnNames.get(columnName));
        }
    }

    // ========= SQL builders =========

    /**
     * Build a parameterized INSERT SQL for the given table and columns.
     */
    public static String getInsertSql(TOdbcTableType tableType, String remoteDatabaseName, String remoteTableName,
            String externalTableName, Map<String, String> remoteColumnNames, List<String> insertCols) {
        StringBuilder sb = new StringBuilder("INSERT INTO ");
        sb.append(getProperRemoteFullTableName(tableType, remoteDatabaseName, remoteTableName, externalTableName));
        sb.append("(");
        List<String> transformedCols = insertCols.stream()
                .map(col -> getProperRemoteColumnName(tableType, col, remoteColumnNames))
                .collect(Collectors.toList());
        sb.append(String.join(",", transformedCols));
        sb.append(")");
        sb.append(" VALUES (");
        sb.append(String.join(", ", Collections.nCopies(insertCols.size(), "?")));
        sb.append(")");
        return sb.toString();
    }
}