ReplacePartitionOp.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.nereids.trees.plans.commands.info;

import org.apache.doris.alter.AlterOpType;
import org.apache.doris.analysis.AlterTableClause;
import org.apache.doris.analysis.ReplacePartitionClause;
import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.UserException;
import org.apache.doris.common.util.PropertyAnalyzer;
import org.apache.doris.qe.ConnectContext;

import com.google.common.base.Joiner;
import com.google.common.collect.Maps;

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

/**
 * ReplacePartitionOp
 */
public class ReplacePartitionOp extends AlterTableOp {
    private PartitionNamesInfo partitionNames;
    private PartitionNamesInfo tempPartitionNames;
    private Map<String, String> properties = Maps.newHashMap();

    // "isStrictMode" is got from property "strict_range", and default is true.
    // If true, when replacing partition, the range of partitions must same as the range of temp partitions.
    private boolean isStrictRange;

    // "useTempPartitionName" is got from property "use_temp_partition_name", and default is false.
    // If false, after replacing, the replaced partition's name will remain unchanged.
    // Otherwise, the replaced partition's name will be the temp partitions name.
    // This parameter is valid only when the number of partitions is the same as the number of temp partitions.
    // For example:
    // 1. REPLACE PARTITION (p1, p2, p3) WITH TEMPORARY PARTITION(tp1, tp2)
    //    PROPERTIES("use_temp_partition_name" = "false");
    //      "use_temp_partition_name" will take no effect after replacing,
    //      and the partition names will be "tp1" and "tp2".
    //
    // 2. REPLACE PARTITION (p1, p2) WITH TEMPORARY PARTITION(tp1, tp2) PROPERTIES("use_temp_partition_name" = "false");
    //      alter replacing, the partition names will be "p1" and "p2".
    //      but if "use_temp_partition_name" is true, the partition names will be "tp1" and "tp2".
    private boolean useTempPartitionName;

    // The replaced partitions will be moved to recycle bin when "forceDropNormalPartition" is false,
    // and instead, these partitions will be deleted directly.
    private boolean forceDropOldPartition;

    /**
     * ReplacePartitionOp
     */
    public ReplacePartitionOp(PartitionNamesInfo partitionNames, PartitionNamesInfo tempPartitionNames,
            boolean isForce, Map<String, String> properties) {
        super(AlterOpType.REPLACE_PARTITION);
        this.partitionNames = partitionNames;
        this.tempPartitionNames = tempPartitionNames;
        this.needTableStable = false;
        this.forceDropOldPartition = isForce;
        this.properties = properties;

        // ATTN: During ReplacePartitionClause.analyze(), the default value of isStrictRange is true.
        // However, ReplacePartitionClause instances constructed by internal code do not call analyze(),
        // so their isStrictRange value is incorrect (e.g., INSERT INTO ... OVERWRITE).
        //
        // Considering this, we should handle the relevant properties when constructing.
        this.isStrictRange = getBoolProperty(properties, PropertyAnalyzer.PROPERTIES_STRICT_RANGE, true);
        this.useTempPartitionName = getBoolProperty(
                properties, PropertyAnalyzer.PROPERTIES_USE_TEMP_PARTITION_NAME, false);
    }

    public List<String> getPartitionNames() {
        return partitionNames.getPartitionNames();
    }

    public List<String> getTempPartitionNames() {
        return tempPartitionNames.getPartitionNames();
    }

    public boolean isStrictRange() {
        return isStrictRange;
    }

    public boolean useTempPartitionName() {
        return useTempPartitionName;
    }

    public boolean isForceDropOldPartition() {
        return forceDropOldPartition;
    }

    @SuppressWarnings("checkstyle:LineLength")
    @Override
    public void validate(ConnectContext ctx) throws UserException {
        if (partitionNames == null || tempPartitionNames == null) {
            throw new AnalysisException("No partition specified");
        }

        partitionNames.validate();
        tempPartitionNames.validate();

        if (partitionNames.isTemp() || !tempPartitionNames.isTemp()) {
            throw new AnalysisException("Only support replace partitions with temp partitions");
        }

        this.isStrictRange = PropertyAnalyzer.analyzeBooleanProp(
                properties, PropertyAnalyzer.PROPERTIES_STRICT_RANGE, true);
        this.useTempPartitionName = PropertyAnalyzer.analyzeBooleanProp(properties,
                PropertyAnalyzer.PROPERTIES_USE_TEMP_PARTITION_NAME, false);

        if (properties != null && !properties.isEmpty()) {
            throw new AnalysisException("Unknown properties: " + properties.keySet());
        }
    }

    @Override
    public AlterTableClause translateToLegacyAlterClause() {
        return new ReplacePartitionClause(partitionNames.translateToLegacyPartitionNames(),
                tempPartitionNames.translateToLegacyPartitionNames(),
                forceDropOldPartition, properties, isStrictRange, useTempPartitionName);
    }

    @Override
    public Map<String, String> getProperties() {
        return this.properties;
    }

    @Override
    public boolean allowOpMTMV() {
        return false;
    }

    @Override
    public boolean needChangeMTMVState() {
        return false;
    }

    @Override
    public String toSql() {
        StringBuilder sb = new StringBuilder();
        sb.append("REPLACE PARTITION(");
        sb.append(Joiner.on(", ").join(partitionNames.getPartitionNames())).append(")");
        sb.append(" WITH TEMPORARY PARTITION(");
        sb.append(Joiner.on(", ").join(tempPartitionNames.getPartitionNames())).append(")");
        return sb.toString();
    }

    @Override
    public String toString() {
        return toSql();
    }

    public static boolean getBoolProperty(Map<String, String> properties, String propKey, boolean defaultVal) {
        if (properties != null && properties.containsKey(propKey)) {
            String val = properties.get(propKey);
            return Boolean.parseBoolean(val);
        }
        return defaultVal;
    }
}