TableProperty.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.analysis.DataSortInfo;
import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.FeMetaVersion;
import org.apache.doris.common.io.Text;
import org.apache.doris.common.io.Writable;
import org.apache.doris.common.util.PropertyAnalyzer;
import org.apache.doris.persist.OperationType;
import org.apache.doris.persist.gson.GsonPostProcessable;
import org.apache.doris.persist.gson.GsonUtils;
import org.apache.doris.thrift.TCompressionType;
import org.apache.doris.thrift.TInvertedIndexFileStorageFormat;
import org.apache.doris.thrift.TStorageFormat;
import org.apache.doris.thrift.TStorageMedium;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gson.annotations.SerializedName;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * TableProperty contains additional information about OlapTable
 * TableProperty includes properties to persistent the additional information
 * Different properties is recognized by prefix such as dynamic_partition
 * If there is different type properties is added, write a method such as buildDynamicProperty to build it.
 */
public class TableProperty implements Writable, GsonPostProcessable {
    private static final Logger LOG = LogManager.getLogger(TableProperty.class);

    @SerializedName(value = "properties")
    private Map<String, String> properties;

    // the follower variables are built from "properties"
    private DynamicPartitionProperty dynamicPartitionProperty =
            EnvFactory.getInstance().createDynamicPartitionProperty(Maps.newHashMap());
    private ReplicaAllocation replicaAlloc = ReplicaAllocation.DEFAULT_ALLOCATION;
    private boolean isInMemory = false;
    private short minLoadReplicaNum = -1;
    private long ttlSeconds = 0L;
    private boolean isInAtomicRestore = false;

    private String storagePolicy = "";
    private Boolean isBeingSynced = null;
    private BinlogConfig binlogConfig;

    private TStorageMedium storageMedium = null;

    // which columns stored in RowStore column
    private List<String> rowStoreColumns;

    /*
     * the default storage format of this table.
     * DEFAULT: depends on BE's config 'default_rowset_type'
     * V1: alpha rowset
     * V2: beta rowset
     *
     * This property should be set when creating the table, and can only be changed to V2 using Alter Table stmt.
     */
    private TStorageFormat storageFormat = TStorageFormat.DEFAULT;

    private TInvertedIndexFileStorageFormat invertedIndexFileStorageFormat = TInvertedIndexFileStorageFormat.DEFAULT;

    private TCompressionType compressionType = TCompressionType.LZ4F;

    private boolean enableLightSchemaChange = false;

    private boolean disableAutoCompaction = false;

    private boolean variantEnableFlattenNested = false;

    private boolean enableSingleReplicaCompaction = false;

    private boolean storeRowColumn = false;

    private boolean skipWriteIndexOnLoad = false;

    private long rowStorePageSize = PropertyAnalyzer.ROW_STORE_PAGE_SIZE_DEFAULT_VALUE;

    private long storagePageSize = PropertyAnalyzer.STORAGE_PAGE_SIZE_DEFAULT_VALUE;

    private String compactionPolicy = PropertyAnalyzer.SIZE_BASED_COMPACTION_POLICY;

    private long timeSeriesCompactionGoalSizeMbytes
                                    = PropertyAnalyzer.TIME_SERIES_COMPACTION_GOAL_SIZE_MBYTES_DEFAULT_VALUE;

    private long timeSeriesCompactionFileCountThreshold
                                    = PropertyAnalyzer.TIME_SERIES_COMPACTION_FILE_COUNT_THRESHOLD_DEFAULT_VALUE;

    private long timeSeriesCompactionTimeThresholdSeconds
                                    = PropertyAnalyzer.TIME_SERIES_COMPACTION_TIME_THRESHOLD_SECONDS_DEFAULT_VALUE;

    private long timeSeriesCompactionEmptyRowsetsThreshold
                                    = PropertyAnalyzer.TIME_SERIES_COMPACTION_EMPTY_ROWSETS_THRESHOLD_DEFAULT_VALUE;

    private long timeSeriesCompactionLevelThreshold
                                    = PropertyAnalyzer.TIME_SERIES_COMPACTION_LEVEL_THRESHOLD_DEFAULT_VALUE;

    private String autoAnalyzePolicy = PropertyAnalyzer.ENABLE_AUTO_ANALYZE_POLICY;


    private DataSortInfo dataSortInfo = new DataSortInfo();

    public TableProperty(Map<String, String> properties) {
        this.properties = properties;
    }

    public static boolean isSamePrefixProperties(Map<String, String> properties, String prefix) {
        for (String value : properties.keySet()) {
            if (!value.startsWith(prefix)) {
                return false;
            }
        }
        return true;
    }

    public TableProperty buildProperty(short opCode) {
        switch (opCode) {
            case OperationType.OP_DYNAMIC_PARTITION:
                executeBuildDynamicProperty();
                break;
            case OperationType.OP_MODIFY_REPLICATION_NUM:
                buildReplicaAllocation();
                break;
            case OperationType.OP_MODIFY_TABLE_PROPERTIES:
                buildInMemory();
                buildMinLoadReplicaNum();
                buildStorageMedium();
                buildStoragePolicy();
                buildIsBeingSynced();
                buildCompactionPolicy();
                buildTimeSeriesCompactionGoalSizeMbytes();
                buildTimeSeriesCompactionFileCountThreshold();
                buildTimeSeriesCompactionTimeThresholdSeconds();
                buildSkipWriteIndexOnLoad();
                buildEnableSingleReplicaCompaction();
                buildDisableAutoCompaction();
                buildTimeSeriesCompactionEmptyRowsetsThreshold();
                buildTimeSeriesCompactionLevelThreshold();
                buildTTLSeconds();
                buildAutoAnalyzeProperty();
                break;
            default:
                break;
        }
        return this;
    }

    /**
     * Reset properties to correct values.
     *
     * @return this for chained
     */
    public TableProperty resetPropertiesForRestore(boolean reserveDynamicPartitionEnable, boolean reserveReplica,
                                                   ReplicaAllocation replicaAlloc) {
        // disable dynamic partition
        if (properties.containsKey(DynamicPartitionProperty.ENABLE)) {
            if (!reserveDynamicPartitionEnable) {
                properties.put(DynamicPartitionProperty.ENABLE, "false");
            }
            executeBuildDynamicProperty();
        }
        if (!reserveReplica) {
            setReplicaAlloc(replicaAlloc);
        }
        return this;
    }

    public TableProperty buildDynamicProperty() {
        executeBuildDynamicProperty();
        return this;
    }

    private TableProperty executeBuildDynamicProperty() {
        HashMap<String, String> dynamicPartitionProperties = new HashMap<>();
        for (Map.Entry<String, String> entry : properties.entrySet()) {
            if (entry.getKey().startsWith(DynamicPartitionProperty.DYNAMIC_PARTITION_PROPERTY_PREFIX)) {
                if (!DynamicPartitionProperty.DYNAMIC_PARTITION_PROPERTIES.contains(entry.getKey())) {
                    LOG.warn("Ignore invalid dynamic property key: {}: value: {}", entry.getKey(), entry.getValue());
                }
                dynamicPartitionProperties.put(entry.getKey(), entry.getValue());
            }
        }

        dynamicPartitionProperty = EnvFactory.getInstance().createDynamicPartitionProperty(dynamicPartitionProperties);
        return this;
    }

    public TableProperty buildInMemory() {
        isInMemory = Boolean.parseBoolean(properties.getOrDefault(PropertyAnalyzer.PROPERTIES_INMEMORY, "false"));
        return this;
    }

    public TableProperty buildInAtomicRestore() {
        isInAtomicRestore = Boolean.parseBoolean(properties.getOrDefault(
                PropertyAnalyzer.PROPERTIES_IN_ATOMIC_RESTORE, "false"));
        return this;
    }

    public boolean isInAtomicRestore() {
        return isInAtomicRestore;
    }

    public TableProperty setInAtomicRestore() {
        properties.put(PropertyAnalyzer.PROPERTIES_IN_ATOMIC_RESTORE, "true");
        return this;
    }

    public TableProperty clearInAtomicRestore() {
        properties.remove(PropertyAnalyzer.PROPERTIES_IN_ATOMIC_RESTORE);
        return this;
    }

    public TableProperty buildTTLSeconds() {
        ttlSeconds = Long.parseLong(properties.getOrDefault(PropertyAnalyzer.PROPERTIES_FILE_CACHE_TTL_SECONDS, "0"));
        return this;
    }

    public long getTTLSeconds() {
        return ttlSeconds;
    }

    public TableProperty buildEnableLightSchemaChange() {
        enableLightSchemaChange = Boolean.parseBoolean(
                properties.getOrDefault(PropertyAnalyzer.PROPERTIES_ENABLE_LIGHT_SCHEMA_CHANGE, "false"));
        return this;
    }

    public TableProperty buildDisableAutoCompaction() {
        disableAutoCompaction = Boolean.parseBoolean(
                properties.getOrDefault(PropertyAnalyzer.PROPERTIES_DISABLE_AUTO_COMPACTION, "false"));
        return this;
    }

    public TableProperty buildAutoAnalyzeProperty() {
        autoAnalyzePolicy = properties.getOrDefault(PropertyAnalyzer.PROPERTIES_AUTO_ANALYZE_POLICY,
                PropertyAnalyzer.ENABLE_AUTO_ANALYZE_POLICY);
        return this;
    }

    public boolean disableAutoCompaction() {
        return disableAutoCompaction;
    }


    public TableProperty buildVariantEnableFlattenNested() {
        variantEnableFlattenNested = Boolean.parseBoolean(
                properties.getOrDefault(PropertyAnalyzer.PROPERTIES_VARIANT_ENABLE_FLATTEN_NESTED, "false"));
        return this;
    }

    public boolean variantEnableFlattenNested() {
        return variantEnableFlattenNested;
    }

    public TableProperty buildEnableSingleReplicaCompaction() {
        enableSingleReplicaCompaction = Boolean.parseBoolean(
                properties.getOrDefault(PropertyAnalyzer.PROPERTIES_ENABLE_SINGLE_REPLICA_COMPACTION, "false"));
        return this;
    }

    public boolean enableSingleReplicaCompaction() {
        return enableSingleReplicaCompaction;
    }

    public TableProperty buildStoreRowColumn() {
        storeRowColumn = Boolean.parseBoolean(
                properties.getOrDefault(PropertyAnalyzer.PROPERTIES_STORE_ROW_COLUMN, "false"));
        return this;
    }

    public TableProperty buildRowStoreColumns() {
        String value = properties.get(PropertyAnalyzer.PROPERTIES_ROW_STORE_COLUMNS);
        // set empty row store columns by default
        if (null == value) {
            return this;
        }
        String[] rsColumnArr = value.split(PropertyAnalyzer.COMMA_SEPARATOR);
        rowStoreColumns = Lists.newArrayList();
        rowStoreColumns.addAll(Arrays.asList(rsColumnArr));
        return this;
    }

    public boolean storeRowColumn() {
        return storeRowColumn;
    }

    public TableProperty buildRowStorePageSize() {
        rowStorePageSize = Long.parseLong(
                properties.getOrDefault(PropertyAnalyzer.PROPERTIES_ROW_STORE_PAGE_SIZE,
                                        Long.toString(PropertyAnalyzer.ROW_STORE_PAGE_SIZE_DEFAULT_VALUE)));
        return this;
    }

    public long rowStorePageSize() {
        return rowStorePageSize;
    }

    public TableProperty buildStoragePageSize() {
        storagePageSize = Long.parseLong(
                properties.getOrDefault(PropertyAnalyzer.PROPERTIES_STORAGE_PAGE_SIZE,
                                        Long.toString(PropertyAnalyzer.STORAGE_PAGE_SIZE_DEFAULT_VALUE)));
        return this;
    }

    public long storagePageSize() {
        return storagePageSize;
    }

    public TableProperty buildSkipWriteIndexOnLoad() {
        skipWriteIndexOnLoad = Boolean.parseBoolean(
                properties.getOrDefault(PropertyAnalyzer.PROPERTIES_SKIP_WRITE_INDEX_ON_LOAD, "false"));
        return this;
    }

    public boolean skipWriteIndexOnLoad() {
        return skipWriteIndexOnLoad;
    }

    public TableProperty buildCompactionPolicy() {
        compactionPolicy = properties.getOrDefault(PropertyAnalyzer.PROPERTIES_COMPACTION_POLICY,
                                                                PropertyAnalyzer.SIZE_BASED_COMPACTION_POLICY);
        return this;
    }

    public String compactionPolicy() {
        return compactionPolicy;
    }

    public TableProperty buildTimeSeriesCompactionGoalSizeMbytes() {
        timeSeriesCompactionGoalSizeMbytes = Long.parseLong(properties
                    .getOrDefault(PropertyAnalyzer.PROPERTIES_TIME_SERIES_COMPACTION_GOAL_SIZE_MBYTES,
                    String.valueOf(PropertyAnalyzer.TIME_SERIES_COMPACTION_GOAL_SIZE_MBYTES_DEFAULT_VALUE)));
        return this;
    }

    public long timeSeriesCompactionGoalSizeMbytes() {
        return timeSeriesCompactionGoalSizeMbytes;
    }

    public TableProperty buildTimeSeriesCompactionFileCountThreshold() {
        timeSeriesCompactionFileCountThreshold = Long.parseLong(properties
                    .getOrDefault(PropertyAnalyzer.PROPERTIES_TIME_SERIES_COMPACTION_FILE_COUNT_THRESHOLD,
                    String.valueOf(PropertyAnalyzer.TIME_SERIES_COMPACTION_FILE_COUNT_THRESHOLD_DEFAULT_VALUE)));

        return this;
    }

    public long timeSeriesCompactionFileCountThreshold() {
        return timeSeriesCompactionFileCountThreshold;
    }

    public TableProperty buildTimeSeriesCompactionTimeThresholdSeconds() {
        timeSeriesCompactionTimeThresholdSeconds = Long.parseLong(properties
                    .getOrDefault(PropertyAnalyzer.PROPERTIES_TIME_SERIES_COMPACTION_TIME_THRESHOLD_SECONDS,
                    String.valueOf(PropertyAnalyzer.TIME_SERIES_COMPACTION_TIME_THRESHOLD_SECONDS_DEFAULT_VALUE)));

        return this;
    }

    public long timeSeriesCompactionTimeThresholdSeconds() {
        return timeSeriesCompactionTimeThresholdSeconds;
    }

    public TableProperty buildTimeSeriesCompactionEmptyRowsetsThreshold() {
        timeSeriesCompactionEmptyRowsetsThreshold = Long.parseLong(properties
                    .getOrDefault(PropertyAnalyzer.PROPERTIES_TIME_SERIES_COMPACTION_EMPTY_ROWSETS_THRESHOLD,
                    String.valueOf(PropertyAnalyzer.TIME_SERIES_COMPACTION_EMPTY_ROWSETS_THRESHOLD_DEFAULT_VALUE)));
        return this;
    }

    public long timeSeriesCompactionEmptyRowsetsThreshold() {
        return timeSeriesCompactionEmptyRowsetsThreshold;
    }

    public TableProperty buildTimeSeriesCompactionLevelThreshold() {
        timeSeriesCompactionLevelThreshold = Long.parseLong(properties
                    .getOrDefault(PropertyAnalyzer.PROPERTIES_TIME_SERIES_COMPACTION_LEVEL_THRESHOLD,
                    String.valueOf(PropertyAnalyzer.TIME_SERIES_COMPACTION_LEVEL_THRESHOLD_DEFAULT_VALUE)));
        return this;
    }

    public long timeSeriesCompactionLevelThreshold() {
        return timeSeriesCompactionLevelThreshold;
    }

    public TableProperty buildMinLoadReplicaNum() {
        minLoadReplicaNum = Short.parseShort(
                properties.getOrDefault(PropertyAnalyzer.PROPERTIES_MIN_LOAD_REPLICA_NUM, "-1"));
        return this;
    }

    public short getMinLoadReplicaNum() {
        return minLoadReplicaNum;
    }

    public TableProperty buildStorageMedium() {
        String storageMediumStr = properties.get(PropertyAnalyzer.PROPERTIES_STORAGE_MEDIUM);
        if (Strings.isNullOrEmpty(storageMediumStr)) {
            storageMedium = null;
        } else {
            storageMedium = TStorageMedium.valueOf(storageMediumStr);
        }
        return this;
    }

    public TStorageMedium getStorageMedium() {
        return storageMedium;
    }

    public TableProperty buildStoragePolicy() {
        storagePolicy = properties.getOrDefault(PropertyAnalyzer.PROPERTIES_STORAGE_POLICY, "");
        return this;
    }

    public String getStoragePolicy() {
        return storagePolicy;
    }

    public TableProperty buildIsBeingSynced() {
        isBeingSynced = Boolean.parseBoolean(properties.getOrDefault(
                PropertyAnalyzer.PROPERTIES_IS_BEING_SYNCED, "false"));
        return this;
    }

    public void setIsBeingSynced() {
        properties.put(PropertyAnalyzer.PROPERTIES_IS_BEING_SYNCED, "true");
        isBeingSynced = true;
    }

    public boolean isBeingSynced() {
        if (isBeingSynced == null) {
            buildIsBeingSynced();
        }
        return isBeingSynced;
    }

    public void removeInvalidProperties() {
        properties.remove(PropertyAnalyzer.PROPERTIES_STORAGE_POLICY);
        storagePolicy = "";
        properties.remove(PropertyAnalyzer.PROPERTIES_COLOCATE_WITH);
        properties.remove(DynamicPartitionProperty.STORAGE_POLICY);
        dynamicPartitionProperty.clearStoragePolicy();
    }

    public List<String> getCopiedRowStoreColumns() {
        if (rowStoreColumns == null) {
            return null;
        }
        return Lists.newArrayList(rowStoreColumns);
    }

    public TableProperty buildBinlogConfig() {
        BinlogConfig binlogConfig = new BinlogConfig();
        if (properties.containsKey(PropertyAnalyzer.PROPERTIES_BINLOG_ENABLE)) {
            binlogConfig.setEnable(Boolean.parseBoolean(properties.get(PropertyAnalyzer.PROPERTIES_BINLOG_ENABLE)));
        }
        if (properties.containsKey(PropertyAnalyzer.PROPERTIES_BINLOG_TTL_SECONDS)) {
            binlogConfig.setTtlSeconds(Long.parseLong(properties.get(PropertyAnalyzer.PROPERTIES_BINLOG_TTL_SECONDS)));
        }
        if (properties.containsKey(PropertyAnalyzer.PROPERTIES_BINLOG_MAX_BYTES)) {
            binlogConfig.setMaxBytes(Long.parseLong(properties.get(PropertyAnalyzer.PROPERTIES_BINLOG_MAX_BYTES)));
        }
        if (properties.containsKey(PropertyAnalyzer.PROPERTIES_BINLOG_MAX_HISTORY_NUMS)) {
            binlogConfig.setMaxHistoryNums(
                    Long.parseLong(properties.get(PropertyAnalyzer.PROPERTIES_BINLOG_MAX_HISTORY_NUMS)));
        }
        this.binlogConfig = binlogConfig;

        return this;
    }

    public BinlogConfig getBinlogConfig() {
        if (binlogConfig == null) {
            buildBinlogConfig();
        }
        return binlogConfig;
    }

    public void setBinlogConfig(BinlogConfig newBinlogConfig) {
        Map<String, String> binlogProperties = Maps.newHashMap();
        binlogProperties.put(PropertyAnalyzer.PROPERTIES_BINLOG_ENABLE, String.valueOf(newBinlogConfig.isEnable()));
        binlogProperties.put(PropertyAnalyzer.PROPERTIES_BINLOG_TTL_SECONDS,
                String.valueOf(newBinlogConfig.getTtlSeconds()));
        binlogProperties.put(PropertyAnalyzer.PROPERTIES_BINLOG_MAX_BYTES,
                String.valueOf(newBinlogConfig.getMaxBytes()));
        binlogProperties.put(PropertyAnalyzer.PROPERTIES_BINLOG_MAX_HISTORY_NUMS,
                String.valueOf(newBinlogConfig.getMaxHistoryNums()));
        modifyTableProperties(binlogProperties);
        this.binlogConfig = newBinlogConfig;
    }

    public TableProperty buildDataSortInfo() {
        HashMap<String, String> dataSortInfoProperties = new HashMap<>();
        for (Map.Entry<String, String> entry : properties.entrySet()) {
            if (entry.getKey().startsWith(DataSortInfo.DATA_SORT_PROPERTY_PREFIX)) {
                dataSortInfoProperties.put(entry.getKey(), entry.getValue());
            }
        }
        dataSortInfo = new DataSortInfo(dataSortInfoProperties);
        return this;
    }

    public TableProperty buildCompressionType() {
        compressionType = TCompressionType.valueOf(properties.getOrDefault(PropertyAnalyzer.PROPERTIES_COMPRESSION,
                TCompressionType.LZ4F.name()));
        return this;
    }

    public TableProperty buildStorageFormat() {
        storageFormat = TStorageFormat.valueOf(properties.getOrDefault(PropertyAnalyzer.PROPERTIES_STORAGE_FORMAT,
                TStorageFormat.DEFAULT.name()));
        return this;
    }

    public TableProperty buildInvertedIndexFileStorageFormat() {
        invertedIndexFileStorageFormat = TInvertedIndexFileStorageFormat.valueOf(properties.getOrDefault(
                PropertyAnalyzer.PROPERTIES_INVERTED_INDEX_STORAGE_FORMAT,
                TInvertedIndexFileStorageFormat.DEFAULT.name()));
        return this;
    }

    public void modifyTableProperties(Map<String, String> modifyProperties) {
        properties.putAll(modifyProperties);
        removeDuplicateReplicaNumProperty();
    }

    public void modifyDataSortInfoProperties(DataSortInfo dataSortInfo) {
        properties.put(DataSortInfo.DATA_SORT_TYPE, String.valueOf(dataSortInfo.getSortType()));
        properties.put(DataSortInfo.DATA_SORT_COL_NUM, String.valueOf(dataSortInfo.getColNum()));
    }

    public void setReplicaAlloc(ReplicaAllocation replicaAlloc) {
        this.replicaAlloc = replicaAlloc;
        // set it to "properties" so that this info can be persisted
        properties.put("default." + PropertyAnalyzer.PROPERTIES_REPLICATION_ALLOCATION,
                replicaAlloc.toCreateStmt());
    }

    public ReplicaAllocation getReplicaAllocation() {
        return replicaAlloc;
    }

    public void modifyTableProperties(String key, String value) {
        properties.put(key, value);
    }

    public Map<String, String> getProperties() {
        return properties;
    }

    public DynamicPartitionProperty getDynamicPartitionProperty() {
        return dynamicPartitionProperty;
    }

    public Map<String, String> getOriginDynamicPartitionProperty() {
        Map<String, String> origProp = Maps.newHashMap();
        for (Map.Entry<String, String> entry : properties.entrySet()) {
            if (DynamicPartitionProperty.DYNAMIC_PARTITION_PROPERTIES.contains(entry.getKey())) {
                origProp.put(entry.getKey(), entry.getValue());
            }
        }
        return origProp;
    }

    public boolean isInMemory() {
        return isInMemory;
    }

    public boolean isAutoBucket() {
        return Boolean.parseBoolean(properties.getOrDefault(PropertyAnalyzer.PROPERTIES_AUTO_BUCKET, "false"));
    }

    public String getEstimatePartitionSize() {
        return properties.getOrDefault(PropertyAnalyzer.PROPERTIES_ESTIMATE_PARTITION_SIZE, "");
    }

    public TStorageFormat getStorageFormat() {
        // Force convert all V1 table to V2 table
        if (TStorageFormat.V1 == storageFormat) {
            return TStorageFormat.V2;
        }
        return storageFormat;
    }

    public TInvertedIndexFileStorageFormat getInvertedIndexFileStorageFormat() {
        return invertedIndexFileStorageFormat;
    }

    public DataSortInfo getDataSortInfo() {
        return dataSortInfo;
    }

    public TCompressionType getCompressionType() {
        return compressionType;
    }

    public boolean getUseSchemaLightChange() {
        return enableLightSchemaChange;
    }

    public void setEnableUniqueKeyMergeOnWrite(boolean enable) {
        properties.put(PropertyAnalyzer.ENABLE_UNIQUE_KEY_MERGE_ON_WRITE, Boolean.toString(enable));
    }

    public boolean getEnableUniqueKeySkipBitmap() {
        return Boolean.parseBoolean(properties.getOrDefault(
            PropertyAnalyzer.ENABLE_UNIQUE_KEY_SKIP_BITMAP_COLUMN, "false"));
    }

    // In order to ensure that unique tables without the `enable_unique_key_merge_on_write` property specified
    // before version 2.1 still maintain the merge-on-read implementation after the upgrade, we will keep
    // the default value here as false.
    public boolean getEnableUniqueKeyMergeOnWrite() {
        return Boolean.parseBoolean(properties.getOrDefault(
                PropertyAnalyzer.ENABLE_UNIQUE_KEY_MERGE_ON_WRITE, "false"));
    }

    public void setEnableMowLightDelete(boolean enable) {
        properties.put(PropertyAnalyzer.PROPERTIES_ENABLE_MOW_LIGHT_DELETE, Boolean.toString(enable));
    }

    public boolean getEnableMowLightDelete() {
        return Boolean.parseBoolean(properties.getOrDefault(
                PropertyAnalyzer.PROPERTIES_ENABLE_MOW_LIGHT_DELETE,
                Boolean.toString(PropertyAnalyzer.PROPERTIES_ENABLE_MOW_LIGHT_DELETE_DEFAULT_VALUE)));
    }

    public void setSequenceMapCol(String colName) {
        properties.put(PropertyAnalyzer.PROPERTIES_FUNCTION_COLUMN + "."
                + PropertyAnalyzer.PROPERTIES_SEQUENCE_COL, colName);
    }

    public String getSequenceMapCol() {
        return properties.get(PropertyAnalyzer.PROPERTIES_FUNCTION_COLUMN + "."
                + PropertyAnalyzer.PROPERTIES_SEQUENCE_COL);
    }

    public void setGroupCommitIntervalMs(int groupCommitIntervalMs) {
        properties.put(PropertyAnalyzer.PROPERTIES_GROUP_COMMIT_INTERVAL_MS, Integer.toString(groupCommitIntervalMs));
    }

    public int getGroupCommitIntervalMs() {
        return Integer.parseInt(properties.getOrDefault(
                PropertyAnalyzer.PROPERTIES_GROUP_COMMIT_INTERVAL_MS,
                Integer.toString(PropertyAnalyzer.PROPERTIES_GROUP_COMMIT_INTERVAL_MS_DEFAULT_VALUE)));
    }

    public void setGroupCommitDataBytes(int groupCommitDataBytes) {
        properties.put(PropertyAnalyzer.PROPERTIES_GROUP_COMMIT_DATA_BYTES, Integer.toString(groupCommitDataBytes));
    }

    public int getGroupCommitDataBytes() {
        return Integer.parseInt(properties.getOrDefault(
            PropertyAnalyzer.PROPERTIES_GROUP_COMMIT_DATA_BYTES,
            Integer.toString(PropertyAnalyzer.PROPERTIES_GROUP_COMMIT_DATA_BYTES_DEFAULT_VALUE)));
    }

    public void setRowStoreColumns(List<String> rowStoreColumns) {
        if (rowStoreColumns != null && !rowStoreColumns.isEmpty()) {
            modifyTableProperties(PropertyAnalyzer.PROPERTIES_STORE_ROW_COLUMN, "true");
            buildStoreRowColumn();
            modifyTableProperties(PropertyAnalyzer.PROPERTIES_ROW_STORE_COLUMNS,
                    Joiner.on(",").join(rowStoreColumns));
            buildRowStoreColumns();
        } else {
            // clear row store columns
            this.rowStoreColumns = null;
        }
    }

    public void buildReplicaAllocation() {
        try {
            // Must copy the properties because "analyzeReplicaAllocation" will remove the property
            // from the properties.
            Map<String, String> copiedProperties = Maps.newHashMap(properties);
            this.replicaAlloc = PropertyAnalyzer.analyzeReplicaAllocationWithoutCheck(
                    copiedProperties, "default");
        } catch (AnalysisException e) {
            // should not happen
            LOG.error("should not happen when build replica allocation", e);
            this.replicaAlloc = ReplicaAllocation.DEFAULT_ALLOCATION;
        }
    }

    @Override
    public void write(DataOutput out) throws IOException {
        Text.writeString(out, GsonUtils.GSON.toJson(this));
    }

    public static TableProperty read(DataInput in) throws IOException {
        TableProperty tableProperty = GsonUtils.GSON.fromJson(Text.readString(in), TableProperty.class);
        return tableProperty;
    }

    public void gsonPostProcess() throws IOException {
        executeBuildDynamicProperty();
        buildInMemory();
        buildMinLoadReplicaNum();
        buildStorageMedium();
        buildStorageFormat();
        buildInvertedIndexFileStorageFormat();
        buildDataSortInfo();
        buildCompressionType();
        buildStoragePolicy();
        buildIsBeingSynced();
        buildBinlogConfig();
        buildEnableLightSchemaChange();
        buildStoreRowColumn();
        buildRowStoreColumns();
        buildRowStorePageSize();
        buildStoragePageSize();
        buildSkipWriteIndexOnLoad();
        buildCompactionPolicy();
        buildTimeSeriesCompactionGoalSizeMbytes();
        buildTimeSeriesCompactionFileCountThreshold();
        buildTimeSeriesCompactionTimeThresholdSeconds();
        buildDisableAutoCompaction();
        buildEnableSingleReplicaCompaction();
        buildTimeSeriesCompactionEmptyRowsetsThreshold();
        buildTimeSeriesCompactionLevelThreshold();
        buildTTLSeconds();
        buildVariantEnableFlattenNested();
        buildInAtomicRestore();

        if (Env.getCurrentEnvJournalVersion() < FeMetaVersion.VERSION_105) {
            // get replica num from property map and create replica allocation
            String repNum = properties.remove(PropertyAnalyzer.PROPERTIES_REPLICATION_NUM);
            if (!Strings.isNullOrEmpty(repNum)) {
                ReplicaAllocation replicaAlloc = new ReplicaAllocation(Short.valueOf(repNum));
                properties.put("default." + PropertyAnalyzer.PROPERTIES_REPLICATION_ALLOCATION,
                        replicaAlloc.toCreateStmt());
            } else {
                properties.put("default." + PropertyAnalyzer.PROPERTIES_REPLICATION_ALLOCATION,
                        ReplicaAllocation.DEFAULT_ALLOCATION.toCreateStmt());
            }
        }
        removeDuplicateReplicaNumProperty();
        buildReplicaAllocation();
    }

    // For some historical reason,
    // both "dynamic_partition.replication_num" and "dynamic_partition.replication_allocation"
    // may be exist in "properties". we need remove the "dynamic_partition.replication_num", or it will always replace
    // the "dynamic_partition.replication_allocation",
    // result in unable to set "dynamic_partition.replication_allocation".
    private void removeDuplicateReplicaNumProperty() {
        if (properties.containsKey(DynamicPartitionProperty.REPLICATION_NUM)
                && properties.containsKey(DynamicPartitionProperty.REPLICATION_ALLOCATION)) {
            properties.remove(DynamicPartitionProperty.REPLICATION_NUM);
        }
    }

    // Return null if storage vault has not been set
    public String getStorageVaultId() {
        return properties.getOrDefault(PropertyAnalyzer.PROPERTIES_STORAGE_VAULT_ID, "");
    }

    public void setStorageVaultId(String storageVaultId) {
        properties.put(PropertyAnalyzer.PROPERTIES_STORAGE_VAULT_ID, storageVaultId);
    }

    public String getStorageVaultName() {
        return properties.getOrDefault(PropertyAnalyzer.PROPERTIES_STORAGE_VAULT_NAME, "");
    }

    public String getPropertiesString() throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            return objectMapper.writeValueAsString(properties);
        } catch (JsonProcessingException e) {
            throw new IOException(e);
        }
    }
}