MysqlTable.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.catalog;

import org.apache.doris.common.DdlException;
import org.apache.doris.common.io.Text;
import org.apache.doris.mysql.privilege.PrivPredicate;
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.thrift.TMySQLTable;
import org.apache.doris.thrift.TTableDescriptor;
import org.apache.doris.thrift.TTableType;

import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.gson.annotations.SerializedName;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.DataInput;
import java.io.IOException;
import java.util.List;
import java.util.Map;

public class MysqlTable extends Table {
    private static final Logger LOG = LogManager.getLogger(MysqlTable.class);

    private static final String ODBC_CATALOG_RESOURCE = "odbc_catalog_resource";
    private static final String MYSQL_HOST = "host";
    private static final String MYSQL_PORT = "port";
    private static final String MYSQL_USER = "user";
    private static final String MYSQL_PASSWORD = "password";
    private static final String MYSQL_DATABASE = "database";
    private static final String MYSQL_TABLE = "table";
    private static final String MYSQL_CHARSET = "charset";

    @SerializedName("ocrn")
    private String odbcCatalogResourceName;
    @SerializedName("h")
    private String host;
    @SerializedName("p")
    private String port;
    @SerializedName("un")
    private String userName;
    @SerializedName("pwd")
    private String passwd;
    @SerializedName("mdn")
    private String mysqlDatabaseName;
    @SerializedName("mtn")
    private String mysqlTableName;
    @SerializedName("c")
    private String charset;

    public MysqlTable() {
        super(TableType.MYSQL);
    }

    public MysqlTable(long id, String name, List<Column> schema, Map<String, String> properties)
            throws DdlException {
        super(id, name, TableType.MYSQL, schema);
        validate(properties);
    }

    private void validate(Map<String, String> properties) throws DdlException {
        if (properties == null) {
            throw new DdlException("Please set properties of mysql table, "
                    + "they are: odbc_catalog_resource or [host, port, user, password] and database and table");
        }

        if (properties.containsKey(ODBC_CATALOG_RESOURCE)) {
            odbcCatalogResourceName = properties.get(ODBC_CATALOG_RESOURCE);

            // 1. check whether resource exist
            Resource oriResource = Env.getCurrentEnv().getResourceMgr().getResource(odbcCatalogResourceName);
            if (oriResource == null) {
                throw new DdlException("Resource does not exist. name: " + odbcCatalogResourceName);
            }

            // 2. check resource usage privilege
            if (!Env.getCurrentEnv().getAccessManager().checkResourcePriv(ConnectContext.get(),
                    odbcCatalogResourceName,
                    PrivPredicate.USAGE)) {
                throw new DdlException("USAGE denied to user '" + ConnectContext.get().getQualifiedUser()
                        + "'@'" + ConnectContext.get().getRemoteIP()
                        + "' for resource '" + odbcCatalogResourceName + "'");
            }
        } else {
            // Set up
            host = properties.get(MYSQL_HOST);
            if (Strings.isNullOrEmpty(host)) {
                throw new DdlException("Host of MySQL table is null. "
                        + "Please set proper resource or add properties('host'='xxx.xxx.xxx.xxx') when create table");
            }

            port = properties.get(MYSQL_PORT);
            if (Strings.isNullOrEmpty(port)) {
                // Maybe null pointer or number convert
                throw new DdlException("Port of MySQL table is null. "
                        + "Please set proper resource or add properties('port'='3306') when create table");
            } else {
                try {
                    Integer.valueOf(port);
                } catch (Exception e) {
                    throw new DdlException("Port of MySQL table must be a number."
                            + "Please set proper resource or add properties('port'='3306') when create table");

                }
            }

            userName = properties.get(MYSQL_USER);
            if (Strings.isNullOrEmpty(userName)) {
                throw new DdlException("User of MySQL table is null. "
                        + "Please set proper resource or add properties('user'='root') when create table");
            }

            passwd = properties.get(MYSQL_PASSWORD);
            if (passwd == null) {
                throw new DdlException("Password of MySQL table is null. "
                        + "Please set proper resource or add properties('password'='xxxx') when create table");
            }

            charset = properties.get(MYSQL_CHARSET);
            if (charset == null) {
                charset = "utf8";
            }
            if (!charset.equalsIgnoreCase("utf8") && !charset.equalsIgnoreCase("utf8mb4")) {
                throw new DdlException("Unknown character set of MySQL table. "
                        + "Please set charset 'utf8' or 'utf8mb4', other charsets not be unsupported now.");
            }
        }

        mysqlDatabaseName = properties.get(MYSQL_DATABASE);
        if (Strings.isNullOrEmpty(mysqlDatabaseName)) {
            throw new DdlException("Database of MySQL table is null. "
                    + "Please add properties('database'='xxxx') when create table");
        }

        mysqlTableName = properties.get(MYSQL_TABLE);
        if (Strings.isNullOrEmpty(mysqlTableName)) {
            throw new DdlException("Database of MySQL table is null. "
                    + "Please add properties('table'='xxxx') when create table");
        }
    }

    private String getPropertyFromResource(String propertyName) {
        OdbcCatalogResource odbcCatalogResource = (OdbcCatalogResource)
                (Env.getCurrentEnv().getResourceMgr().getResource(odbcCatalogResourceName));
        if (odbcCatalogResource == null) {
            throw new RuntimeException("Resource does not exist. name: " + odbcCatalogResourceName);
        }

        String property = odbcCatalogResource.getProperty(propertyName);
        if (property == null) {
            throw new RuntimeException("The property:" + propertyName
                    + " do not set in resource " + odbcCatalogResourceName);
        }
        return property;
    }

    public String getOdbcCatalogResourceName() {
        return odbcCatalogResourceName;
    }

    public String getHost() {
        if (host != null) {
            return host;
        }
        return getPropertyFromResource(MYSQL_HOST);
    }

    public String getPort() {
        if (port != null) {
            return port;
        }
        return getPropertyFromResource(MYSQL_PORT);
    }

    public String getUserName() {
        if (userName != null) {
            return userName;
        }
        return getPropertyFromResource(MYSQL_USER);
    }

    public String getPasswd() {
        if (passwd != null) {
            return passwd;
        }
        return getPropertyFromResource(MYSQL_PASSWORD);
    }

    public String getMysqlDatabaseName() {
        return mysqlDatabaseName;
    }

    public String getMysqlTableName() {
        return mysqlTableName;
    }

    public String getCharset() {
        if (charset != null) {
            return charset;
        }
        return "utf8";
    }

    public TTableDescriptor toThrift() {
        TMySQLTable tMySQLTable = new TMySQLTable(getHost(), getPort(), getUserName(), getPasswd(),
                mysqlDatabaseName, mysqlTableName, getCharset());
        TTableDescriptor tTableDescriptor = new TTableDescriptor(getId(), TTableType.MYSQL_TABLE,
                fullSchema.size(), 0, getName(), "");
        tTableDescriptor.setMysqlTable(tMySQLTable);
        return tTableDescriptor;
    }

    @Override
    public String getSignature(int signatureVersion) {
        StringBuilder sb = new StringBuilder(signatureVersion);
        sb.append(name);
        sb.append(type.name());
        sb.append(getHost());
        sb.append(getPort());
        sb.append(getUserName());
        sb.append(getPasswd());
        sb.append(mysqlDatabaseName);
        sb.append(mysqlTableName);
        sb.append(getCharset());
        String md5 = DigestUtils.md5Hex(sb.toString());
        if (LOG.isDebugEnabled()) {
            LOG.debug("get signature of mysql table {}: {}. signature string: {}", name, md5, sb.toString());
        }
        return md5;
    }

    @Deprecated
    public void readFields(DataInput in) throws IOException {
        super.readFields(in);

        // Read MySQL meta
        int size = in.readInt();
        Map<String, String> serializeMap = Maps.newHashMap();
        for (int i = 0; i < size; i++) {
            String key = Text.readString(in);
            String value = Text.readString(in);
            serializeMap.put(key, value);
        }

        odbcCatalogResourceName = serializeMap.get(ODBC_CATALOG_RESOURCE);
        host = serializeMap.get(MYSQL_HOST);
        port = serializeMap.get(MYSQL_PORT);
        userName = serializeMap.get(MYSQL_USER);
        passwd = serializeMap.get(MYSQL_PASSWORD);
        mysqlDatabaseName = serializeMap.get(MYSQL_DATABASE);
        mysqlTableName = serializeMap.get(MYSQL_TABLE);
        charset = serializeMap.get(MYSQL_CHARSET);
    }
}