package com.lanyuanxiaoyao.service.common.utils;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.lanyuanxiaoyao.service.common.entity.TableMeta;
import com.lanyuanxiaoyao.service.common.exception.ConfigException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Table Meta 工具类
 *
 * @author ZhangJiacheng
 * @version 0.0.1
 * @date 2021-12-01
 */
public class TableMetaHelper {
    //private static final AES AES = new AES(Mode.CBC, Padding.NoPadding, "6fa22c779ec14b98".getBytes(), "6fa22c779ec14b98".getBytes());

    public static String tableMetaSql(String database) {
        return tableMetaSql(database, true, false);
    }

    /*
    SqlBuilder.select(
                                DataSource.DS_NAME,
                                DataSource.SCHEMA_NAME,
                                DataSourceTable.TABLE_NAME,
                                DataSourceTable.TABLE_TYPE,
                                DataSourceTableField.FIELD_NAME,
                                DataSourceTableField.FIELD_SEQ,
                                DataSourceTableField.FIELD_TYPE,
                                DataSourceTableField.PRIMARY_KEY,
                                DataSourceTableField.PARTITION_KEY,
                                DataSourceTableField.LENGTH,
                                TbAppCollectTableInfo.TGT_DB,
                                TbAppCollectTableInfo.TGT_TABLE,
                                TbAppCollectTableInfo.TGT_TABLE_TYPE,
                                TbAppCollectTableInfo.TGT_HDFS_PATH,
                                TbAppHudiJobConfig.WRITE_TASKS,
                                TbAppHudiJobConfig.WRITE_OPERATION,
                                TbAppHudiJobConfig.WRITE_TASK_MAX_MEMORY,
                                TbAppHudiJobConfig.WRITE_BATCH_SIZE,
                                TbAppHudiJobConfig.WRITE_RATE_LIMIT,
                                TbAppCollectTableInfo.BUCKET_NUMBER,
                                TbAppHudiJobConfig.COMPACTION_STRATEGY,
                                TbAppHudiJobConfig.COMPACTION_TASKS,
                                TbAppHudiJobConfig.COMPACTION_DELTA_COMMITS,
                                TbAppHudiJobConfig.COMPACTION_DELTA_SECONDS,
                                TbAppHudiJobConfig.COMPACTION_ASYNC_ENABLED,
                                TbAppHudiJobConfig.COMPACTION_MAX_MEMORY,
                                TbAppHudiJobConfig.CONFIGS,
                                TbAppCollectTableInfo.FILTER_FIELD,
                                TbAppCollectTableInfo.FILTER_VALUES,
                                TbAppCollectTableInfo.FILTER_TYPE,
                                TbAppCollectTableInfo.SRC_TOPIC,
                                TbAppCollectTableInfo.SRC_PULSAR_ADDR,
                                Alias.of(TbAppYarnJobConfigSync.JOB_MANAGER_MEMORY, "sync_job_manager_memory"),
                                Alias.of(TbAppYarnJobConfigSync.TASK_MANAGER_MEMORY, "sync_task_manager_memory"),
                                Alias.of(TbAppYarnJobConfigCompaction.JOB_MANAGER_MEMORY, "compaction_job_manager_memory"),
                                Alias.of(TbAppYarnJobConfigCompaction.TASK_MANAGER_MEMORY, "compaction_task_manger_momory"),
                                TbAppCollectTableInfo.PARTITION_FIELD,
                                TbAppHudiSyncState.MESSAGE_ID,
                                TbAppGlobalConfig.METRIC_PUBLISH_URL,
                                TbAppGlobalConfig.METRIC_PROMETHEUS_URL,
                                TbAppGlobalConfig.METRIC_API_URL,
                                TbAppGlobalConfig.METRIC_PUBLISH_DELAY,
                                TbAppGlobalConfig.METRIC_PUBLISH_PERIOD,
                                TbAppGlobalConfig.METRIC_PUBLISH_TIMEOUT,
                                TbAppGlobalConfig.METRIC_PUBLISH_BATCH,
                                Alias.of(TbAppFlinkJobConfig.ID, "job_id"),
                                Alias.of(TbAppFlinkJobConfig.NAME, "job_name"),
                                TbAppGlobalConfig.CHECKPOINT_ROOT_PATH,
                                TbAppHudiJobConfig.SOURCE_TASKS,
                                TbAppCollectTableInfo.ALIAS,
                                DataSource.CONNECTION,
                                TbAppCollectTableInfo.PRIORITY,
                                DataSource.DS_TYPE,
                                TbAppHudiJobConfig.KEEP_FILE_VERSION,
                                TbAppHudiJobConfig.KEEP_COMMIT_VERSION,
                                TbAppCollectTableInfo.TAGS,
                                TbAppGlobalConfig.ZK_URL
                        )
                        .from(
                                DataSource._alias_,
                                DataSourceTable._alias_,
                                DataSourceTableField._alias_,
                                TbAppFlinkJobConfig._alias_,
                                TbAppHudiJobConfig._alias_,
                                TbAppYarnJobConfigSync._alias_,
                                TbAppYarnJobConfigCompaction._alias_,
                                TbAppGlobalConfig._alias_,
                                TbAppCollectTableInfo._alias_
                        )
                        .leftJoin(TbAppHudiSyncState._alias_)
                        .onEq(TbAppHudiSyncState.ID, Column.as(StrUtil.format("concat({}, '-', {})", TbAppCollectTableInfo.FLINK_JOB_ID, TbAppCollectTableInfo.ALIAS)))
                        .whereEq(DataSource.DS_ROLE, "src")
                        .andEq(DataSource.DS_STATE, "y")
                        .andEq(DataSource.RECORD_STATE, "y")
                        .andEq(DataSourceTable.DS_ID, Column.as(DataSource.DS_ID))
                        .andEq(DataSourceTable.RECORD_STATE, "y")
                        .andEq(DataSourceTableField.TABLE_ID, Column.as(DataSourceTable.TABLE_ID))
                        .andEq(DataSourceTableField.RECORD_STATE, "y")
                        .andIn(DataSource.DS_TYPE, "udal", "telepg")
                        .andEq(DataSource.DS_NAME, Column.as(TbAppCollectTableInfo.SRC_DB))
                        .andEq(DataSource.SCHEMA_NAME, Column.as(TbAppCollectTableInfo.SRC_SCHEMA))
                        .andEq(DataSourceTable.TABLE_NAME, Column.as(TbAppCollectTableInfo.SRC_TABLE))
                        .andEq(TbAppCollectTableInfo.FLINK_JOB_ID, Column.as(TbAppFlinkJobConfig.ID))
                        .andEq(TbAppCollectTableInfo.HUDI_JOB_ID, Column.as(TbAppHudiJobConfig.ID))
                        .andEq(TbAppCollectTableInfo.SYNC_YARN_JOB_ID, Column.as(TbAppYarnJobConfigSync.ID))
                        .andEq(TbAppCollectTableInfo.COMPACTION_YARN_JOB_ID, Column.as(TbAppYarnJobConfigCompaction.ID))
                        .andEq(TbAppCollectTableInfo.CONFIG_ID, Column.as(TbAppGlobalConfig.ID))
                        .andEq(TbAppFlinkJobConfig.ID, 1542097984132706304L)
                        .andEq(TbAppCollectTableInfo.ALIAS, "crm_cfguse_channel")
                        .andEq(TbAppCollectTableInfo.STATUS, "y")
                        .andEq(TbAppFlinkJobConfig.STATUS, "y")
                        .andEq(TbAppHudiJobConfig.STATUS, "y")
                        .andEq(TbAppYarnJobConfigSync.STATUS, "y")
                        .andEq(TbAppYarnJobConfigCompaction.STATUS, "y")
                        .orderBy(DataSourceTableField.FIELD_SEQ)
                        .build()
     */
    public static String tableMetaSql(String database, Boolean filterByFlinkJobId, Boolean filterByAlias) {
        // language=MySQL
        return "select dst.ds_name,\n" +
                "       dst.schema_name,\n" +
                "       dst.table_name,\n" +
                "       dst.table_type,\n" +
                "       dstf.field_name,\n" +
                "       dstf.field_seq,\n" +
                "       dstf.field_type,\n" +
                "       dstf.primary_key,\n" +
                "       dstf.partition_key,\n" +
                "       dstf.length,\n" +
                "       tacti.tgt_db,\n" +
                "       tacti.tgt_table,\n" +
                "       tacti.tgt_table_type,\n" +
                "       tacti.tgt_hdfs_path,\n" +
                "       tajhc.write_tasks,\n" +
                "       tajhc.write_operation,\n" +
                "       tajhc.write_task_max_memory,\n" +
                "       tajhc.write_batch_size,\n" +
                "       tajhc.write_rate_limit,\n" +
                "       tacti.bucket_number,\n" +
                "       tajhc.compaction_strategy,\n" +
                "       tajhc.compaction_tasks,\n" +
                "       tajhc.compaction_delta_commits,\n" +
                "       tajhc.compaction_delta_seconds,\n" +
                "       tajhc.compaction_async_enabled,\n" +
                "       tajhc.compaction_max_memory,\n" +
                "       tajhc.configs,\n" +
                "       tacti.filter_field,\n" +
                "       tacti.filter_values,\n" +
                "       tacti.filter_type,\n" +
                "       tacti.src_topic,\n" +
                "       tacti.src_pulsar_addr,\n" +
                "       tayjc_sync.job_manager_memory        as sync_job_manager_memory,\n" +
                "       tayjc_sync.task_manager_memory       as sync_task_manager_memory,\n" +
                "       tayjc_compaction.job_manager_memory  as compaction_job_manager_memory,\n" +
                "       tayjc_compaction.task_manager_memory as compaction_task_manger_momory,\n" +
                "       tacti.partition_field,\n" +
                "       tahss.message_id,\n" +
                "       tagc.metric_publish_url,\n" +
                "       tagc.metric_prometheus_url,\n" +
                "       tagc.metric_api_url,\n" +
                "       tagc.metric_publish_delay,\n" +
                "       tagc.metric_publish_period,\n" +
                "       tagc.metric_publish_timeout,\n" +
                "       tagc.metric_publish_batch,\n" +
                "       tafjc.id                             as job_id,\n" +
                "       tafjc.name                           as job_name,\n" +
                "       tagc.checkpoint_root_path,\n" +
                "       tajhc.source_tasks,\n" +
                "       tacti.alias,\n" +
                "       dst.connection,\n" +
                "       tacti.priority,\n" +
                "       dst.ds_type,\n" +
                "       tajhc.keep_file_version,\n" +
                "       tajhc.keep_commit_version,\n" +
                "       tacti.tags,\n" +
                "       tagc.zk_url,\n" +
                "       tacti.version,\n" +
                "       dstf.scale\n" +
                "from `" + database + "`.tb_app_collect_table_info tacti\n" +
                "         left join `" + database + "`.tb_app_hudi_sync_state tahss\n" +
                "                   on tahss.id = concat(tacti.flink_job_id, '-', tacti.alias),\n" +
                "     `" + database + "`.tb_app_flink_job_config tafjc,\n" +
                "     `" + database + "`.tb_app_hudi_job_config tajhc,\n" +
                "     `" + database + "`.tb_app_yarn_job_config tayjc_sync,\n" +
                "     `" + database + "`.tb_app_yarn_job_config tayjc_compaction,\n" +
                "     `" + database + "`.tb_app_global_config tagc,\n" +
                "     `" + database + "`.tb_app_hudi_compaction_schedule tahcs,\n" +
                "     `iap-datahub`.data_source_table_field dstf,\n" +
                "     (select ds.*, dst.table_id, dst.table_name, dst.table_type\n" +
                "      from `iap-datahub`.data_source_table dst,\n" +
                "           (select ds.ds_id, ds.ds_name, ds.ds_type, ds.schema_name, ds.connection\n" +
                "            from `iap-datahub`.data_source ds\n" +
                "            where ds.ds_role = 'src'\n" +
                "              and ds.ds_state = 'y'\n" +
                "              and ds.record_state = 'y') ds\n" +
                "      where dst.ds_id = ds.ds_id\n" +
                "        and dst.record_state = 'y') dst\n" +
                "where dstf.table_id = dst.table_id\n" +
                "  and dstf.record_state = 'y'\n" +
                "  and dst.ds_type in ('udal', 'telepg')\n" +
                "  and dst.ds_name = tacti.src_db\n" +
                "  and dst.schema_name = tacti.src_schema\n" +
                "  and dst.table_name = tacti.src_table\n" +
                "  and tacti.flink_job_id = tafjc.id\n" +
                "  and tacti.hudi_job_id = tajhc.id\n" +
                "  and tacti.sync_yarn_job_id = tayjc_sync.id\n" +
                "  and tacti.compaction_yarn_job_id = tayjc_compaction.id\n" +
                "  and tacti.config_id = tagc.id\n" +
                "  and tacti.schedule_id = tahcs.id\n" +
                (filterByFlinkJobId ? "  and tafjc.id = ?\n" : "") +
                (filterByAlias ? "  and tacti.alias = ?\n" : "") +
                " and tacti.status = 'y'\n" +
                "  and tafjc.status = 'y'\n" +
                "  and tajhc.status = 'y'\n" +
                "  and tayjc_sync.status = 'y'\n" +
                "  and tayjc_compaction.status = 'y'\n" +
                "order by dstf.field_seq;";
    }

    public static List<TableMeta> from(ResultSet rs) throws SQLException {
        List<TableMeta> results = new ArrayList<>();
        List<TableMeta.RowMeta> metaList = new ArrayList<>();
        while (rs.next( )) {
            metaList.add(
                    TableMeta.RowMeta.builder()
                            .dsName(rs.getString(1))
                            .schemaName(rs.getString(2))
                            .tableName(rs.getString(3))
                            .tableType(rs.getString(4))
                            .fieldName(rs.getString(5))
                            .fieldSeq(rs.getInt(6))
                            .fieldType(rs.getString(7))
                            .primaryKey(rs.getString(8))
                            .partitionKey(rs.getString(9))
                            .length(rs.getLong(10))
                            .tgtDb(rs.getString(11))
                            .tgtTable(rs.getString(12))
                            .tgtTableType(rs.getString(13))
                            .tgtHdfsPath(rs.getString(14))
                            .writeTasks(rs.getInt(15))
                            .writeOperation(rs.getString(16))
                            .writeTaskMaxMemory(rs.getInt(17))
                            .writeBatchSize(rs.getInt(18))
                            .writeRateLimit(rs.getInt(19))
                            .bucketIndexNumber(rs.getInt(20))
                            .compactionStrategy(rs.getString(21))
                            .compactionTasks(rs.getInt(22))
                            .compactionDeltaCommits(rs.getInt(23))
                            .compactionDeltaSeconds(rs.getInt(24))
                            .compactionAsyncEnabled(rs.getString(25))
                            .compactionMaxMemory(rs.getInt(26))
                            .configs(rs.getString(27))
                            .filterField(rs.getString(28))
                            .filterValues(rs.getString(29))
                            .filterType(rs.getString(30))
                            .topic(rs.getString(31))
                            .pulsarAddress(rs.getString(32))
                            .syncJobManagerMemory(rs.getInt(33))
                            .syncTaskManagerMemory(rs.getInt(34))
                            .compactionJobManagerMemory(rs.getInt(35))
                            .compactionTaskManagerMemory(rs.getInt(36))
                            .partitionField(rs.getString(37))
                            .messageId(rs.getString(38))
                            .metricPublishUrl(rs.getString(39))
                            .metricPrometheusUrl(rs.getString(40))
                            .metricApiUrl(rs.getString(41))
                            .metricPublishDelay(rs.getInt(42))
                            .metricPublishPeriod(rs.getInt(43))
                            .metricPublishTimeout(rs.getInt(44))
                            .metricPublishBatch(rs.getInt(45))
                            .jobId(rs.getLong(46))
                            .jobName(rs.getString(47))
                            .checkpointRootPath(rs.getString(48))
                            .sourceTasks(rs.getInt(49))
                            .alias(rs.getString(50))
                            .connection(rs.getString(51))
                            .priority(rs.getInt(52))
                            .sourceType(rs.getString(53))
                            .keepFileVersion(rs.getInt(54))
                            .keepCommitVersion(rs.getInt(55))
                            .tags(rs.getString(56))
                            .zookeeperUrl(rs.getString(57))
                            .version(rs.getInt(58))
                            .scala(rs.getInt(59))
                            .build()
            );
        }
        metaList.stream()
                .collect(Collectors.groupingBy(TableMeta.RowMeta::getAlias))
                .values()
                .stream()
                .flatMap(schemaRowMetas -> schemaRowMetas
                        .stream()
                        .collect(Collectors.groupingBy(TableMeta.RowMeta::getTableName))
                        .values()
                        .stream()
                        .map(tableRowMetas -> {
                            try {
                                return fromRowMetas(tableRowMetas);
                            } catch (Exception e) {
                                throw new RuntimeException(e);
                            }
                        }))
                .forEach(results::add);
        return results;
    }

    private static void checkMoreThanOne(String fieldName, Collection<?> collection) throws ConfigException {
        ConfigException.check(fieldName + " cannot be more than 1", () -> collection.size() > 1);
    }

    private static void checkEmpty(String fieldName, Collection<?> collection) throws ConfigException {
        ConfigException.check(fieldName + " cannot be empty", collection::isEmpty);
    }

    private static void checkEmptyOrMoreThanOne(String fieldName, Collection<?> collection) throws ConfigException {
        checkEmpty(fieldName, collection);
        checkMoreThanOne(fieldName, collection);
    }

    public static TableMeta fromRowMetas(List<TableMeta.RowMeta> metaList) throws Exception {
        List<String> aliasList = metaList.stream()
                .map(TableMeta.RowMeta::getAlias)
                .distinct()
                .collect(Collectors.toList());
        checkEmptyOrMoreThanOne("alias", aliasList);
        String alias = aliasList.get(0);
        List<String> sourceTypeList = metaList.stream()
                .map(TableMeta.RowMeta::getSourceType)
                .distinct()
                .collect(Collectors.toList());
        checkEmptyOrMoreThanOne("source_type", sourceTypeList);
        String sourceTypeText = sourceTypeList.get(0).toUpperCase();
        TableMeta.SourceType sourceType;
        try {
            sourceType = TableMeta.SourceType.valueOf(sourceTypeText);
        } catch (IllegalArgumentException e) {
            throw new Exception("Cannot parse source type " + sourceTypeText);
        }
        List<String> dsNames = metaList.stream()
                .map(TableMeta.RowMeta::getDsName)
                .distinct()
                .collect(Collectors.toList());
        checkEmptyOrMoreThanOne("ds_name", dsNames);
        String dataSource = dsNames.get(0);
        List<String> schemaNames = metaList.stream()
                .map(TableMeta.RowMeta::getSchemaName)
                .distinct()
                .collect(Collectors.toList());
        checkEmptyOrMoreThanOne("schema_name", schemaNames);
        String schema = schemaNames.get(0);
        List<String> tableNames = metaList.stream()
                .map(TableMeta.RowMeta::getTableName)
                .distinct()
                .collect(Collectors.toList());
        // 每次只能获取 1 张表的元信息
        checkMoreThanOne("table_name", tableNames);
        checkEmpty("table_name", tableNames);
        String table = tableNames.get(0);
        List<String> tableTypes = metaList.stream()
                .map(TableMeta.RowMeta::getTableType)
                .distinct()
                .collect(Collectors.toList());
        checkEmptyOrMoreThanOne("table_type", tableTypes);
        String type = tableTypes.get(0);
        List<String> filterFields = metaList.stream()
                .map(TableMeta.RowMeta::getFilterField)
                .distinct()
                .collect(Collectors.toList());
        checkEmptyOrMoreThanOne("filter_field", filterFields);
        String filterField = filterFields.get(0);
        List<String> filterValueList = metaList.stream()
                .map(TableMeta.RowMeta::getFilterValues)
                .distinct()
                .collect(Collectors.toList());
        checkEmptyOrMoreThanOne("filter_values", filterValueList);
        String filterValuesText = filterValueList.get(0);
        List<String> filterValues = (filterValuesText == null || filterValuesText.isEmpty())
                ? Collections.emptyList()
                : Arrays.asList(filterValuesText.split(","));
        List<String> filterTypes = metaList.stream()
                .map(TableMeta.RowMeta::getFilterType)
                .distinct()
                .collect(Collectors.toList());
        checkEmptyOrMoreThanOne("filter_field", filterFields);
        TableMeta.FilterType filterType;
        try {
            filterType = TableMeta.FilterType.valueOf(filterTypes.get(0));
        } catch (IllegalArgumentException e) {
            filterType = TableMeta.FilterType.NONE;
        }
        List<String> topics = metaList.stream()
                .map(TableMeta.RowMeta::getTopic)
                .distinct()
                .collect(Collectors.toList());
        checkEmptyOrMoreThanOne("topic", topics);
        String topic = topics.get(0);
        List<String> pulsarAddresses = metaList.stream()
                .map(TableMeta.RowMeta::getPulsarAddress)
                .distinct()
                .collect(Collectors.toList());
        checkEmptyOrMoreThanOne("pulsar address", pulsarAddresses);
        String pulsarAddress = pulsarAddresses.get(0);
        List<Integer> priorities = metaList.stream()
                .map(TableMeta.RowMeta::getPriority)
                .distinct()
                .collect(Collectors.toList());
        checkEmptyOrMoreThanOne("priority", priorities);
        Integer priority = priorities.get(0);
        List<String> tagTexts = metaList.stream()
                .map(TableMeta.RowMeta::getTags)
                .distinct()
                .collect(Collectors.toList());
        checkEmptyOrMoreThanOne("tags", tagTexts);
        String tagText = tagTexts.get(0) == null ? "" : tagTexts.get(0);
        List<String> tags = Arrays.asList(tagText.split(","));
        List<Integer> versions = metaList.stream()
                .map(TableMeta.RowMeta::getVersion)
                .distinct()
                .collect(Collectors.toList());
        checkEmptyOrMoreThanOne("version", versions);
        Integer version = versions.get(0);

        // 获取 Hudi 配置, 因为查出来同一张表的配置都相同, 所以直接取第一条即可
        TableMeta.RowMeta example = metaList.get(0);
        TableMeta.HudiMeta hudiMeta = TableMeta.HudiMeta.builder()
                .targetDataSource(example.getTgtDb())
                .targetTable(example.getTgtTable())
                .targetTableType(example.getTgtTableType())
                .targetHdfsPath(example.getTgtHdfsPath())
                .sourceTasks(example.getSourceTasks())
                .writeTasks(example.getWriteTasks())
                .writeOperation(example.getWriteOperation())
                .writeTaskMaxMemory(example.getWriteTaskMaxMemory())
                .writeBatchSize(example.getWriteBatchSize())
                .writeRateLimit(example.getWriteRateLimit())
                .bucketIndexNumber(example.getBucketIndexNumber())
                .compactionStrategy(example.getCompactionStrategy())
                .compactionTasks(example.getCompactionTasks())
                .compactionDeltaCommits(example.getCompactionDeltaCommits())
                .compactionDeltaSeconds(example.getCompactionDeltaSeconds())
                .compactionAsyncEnabled(example.getCompactionAsyncEnabled())
                .compactionMaxMemory(example.getCompactionMaxMemory())
                .configs(example.getConfigs())
                .keepFileVersion(example.getKeepFileVersion())
                .keepCommitVersion(example.getKeepCommitVersion())
                .build();
        TableMeta.YarnMeta syncYarnMeta = TableMeta.YarnMeta.builder()
                .jobManagerMemory(example.getSyncJobManagerMemory())
                .taskManagerMemory(example.getSyncTaskManagerMemory())
                .build();
        TableMeta.YarnMeta compactionYarnMeta = TableMeta.YarnMeta.builder()
                .jobManagerMemory(example.getCompactionJobManagerMemory())
                .taskManagerMemory(example.getCompactionTaskManagerMemory())
                .build();
        TableMeta.ConfigMeta configMeta = TableMeta.ConfigMeta.builder()
                .messageId(example.getMessageId())
                .metricPublishUrl(example.getMetricPublishUrl())
                .metricPrometheusUrl(example.getMetricPrometheusUrl())
                .metricApiUrl(example.getMetricApiUrl())
                .metricPublishDelay(example.getMetricPublishDelay())
                .metricPublishPeriod(example.getMetricPublishPeriod())
                .metricPublishTimeout(example.getMetricPublishTimeout())
                .metricPublishBatch(example.getMetricPublishBatch())
                .checkpointRootPath(example.getCheckpointRootPath())
                .zookeeperUrl(example.getZookeeperUrl())
                .build();
        TableMeta.JobMeta jobMeta = TableMeta.JobMeta.builder()
                .id(example.getJobId())
                .name(example.getJobName())
                .build();

        TableMeta.ConnectionMeta connectionMeta = null;
        String connectionText = example.getConnection();
        if (connectionText != null && !connectionText.isEmpty()) {
            JSONObject connectionObj = JSONUtil.parseObj(connectionText);
            connectionMeta = TableMeta.ConnectionMeta.builder()
                    .url(connectionObj.getStr("jdbc_url"))
                    .user(connectionObj.getStr("jdbc_user"))
                    .password(connectionObj.getStr("jdbc_password"))
                    .driver(connectionObj.getStr("jdbc_driver"))
                    .build();
        }

        List<String> partitionFields = metaList.stream()
                .map(TableMeta.RowMeta::getPartitionField)
                .distinct()
                .collect(Collectors.toList());
        checkEmptyOrMoreThanOne("partition_field", filterFields);
        String partitionField = partitionFields.get(0);

        List<TableMeta.FieldMeta> primaryKeys = new ArrayList<>(), partitionKeys = new ArrayList<>();
        List<TableMeta.FieldMeta> fieldMetaList = new ArrayList<>(metaList.size());
        for (TableMeta.RowMeta rowMeta : metaList) {
            boolean isPrimaryKey = "y".equals(rowMeta.getPrimaryKey());
            boolean isPartitionKey = "y".equals(rowMeta.getPartitionKey());
            TableMeta.FieldMeta fieldMeta = TableMeta.FieldMeta.builder()
                    .name(rowMeta.getFieldName().toUpperCase(Locale.ROOT))
                    .sequence(rowMeta.getFieldSeq())
                    .type(rowMeta.getFieldType())
                    .isPrimaryKey(isPrimaryKey)
                    .partitionKey(isPartitionKey)
                    .length(rowMeta.getLength())
                    .scala(rowMeta.getScala())
                    .build();
            if (isPrimaryKey) {
                primaryKeys.add(fieldMeta);
            }
            if (isPartitionKey) {
                partitionKeys.add(fieldMeta);
            }
            fieldMetaList.add(fieldMeta);
        }
        return TableMeta.builder()
                .alias(alias)
                .source(dataSource)
                .schema(schema)
                .table(table)
                .type(type)
                .primaryKeys(primaryKeys)
                .partitionKeys(partitionKeys)
                .hudi(hudiMeta)
                .fields(fieldMetaList)
                .filterField(filterField)
                .filterValues(filterValues)
                .filterType(filterType)
                .topic(topic)
                .pulsarAddress(pulsarAddress)
                .syncYarn(syncYarnMeta)
                .compactionYarn(compactionYarnMeta)
                .partitionField(partitionField)
                .config(configMeta)
                .job(jobMeta)
                .connection(connectionMeta)
                .priority(priority)
                .sourceType(sourceType)
                .tags(tags)
                .version(version)
                .build();
    }

    public static Optional<String> getPartitionField(TableMeta meta) {
        if (meta.getPartitionField() == null || "".equals(meta.getPartitionField())) {
            return Optional.empty();
        } else {
            return meta.getFields()
                    .stream()
                    .map(TableMeta.FieldMeta::getName)
                    .filter(name -> meta.getPartitionField().equalsIgnoreCase(name))
                    .findFirst();
        }
    }

    public static Optional<String> getFilterField(TableMeta meta) {
        if (meta.getFilterField() == null || "".equals(meta.getFilterField())) {
            return Optional.empty();
        } else {
            return meta.getFields()
                    .stream()
                    .map(TableMeta.FieldMeta::getName)
                    .filter(name -> meta.getFilterField().equalsIgnoreCase(name))
                    .findFirst();
        }
    }

    public static boolean existsTag(TableMeta meta, String tag) {
        return meta.getTags() != null && meta.getTags().contains(tag);
    }
}
