PartitionKeyDesc.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.analysis;

import org.apache.doris.common.AnalysisException;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;

import java.util.List;

// Describe the partition key values in create table or add partition clause
public class PartitionKeyDesc {
    public static final PartitionKeyDesc MAX_VALUE = new PartitionKeyDesc();
    // UNPARTITIONED table not have real keyDesc,therefore, provide this DUMMY_KEY_DESC
    public static final PartitionKeyDesc DUMMY_KEY_DESC = new PartitionKeyDesc();

    public enum PartitionKeyValueType {
        INVALID,
        LESS_THAN,
        FIXED,
        IN
    }

    private List<PartitionValue> lowerValues;
    private List<PartitionValue> upperValues;
    private List<List<PartitionValue>> inValues;
    private PartitionKeyValueType partitionKeyValueType;

    private Long timeInterval;
    private String timeType;

    public static PartitionKeyDesc createMaxKeyDesc() {
        return MAX_VALUE;
    }

    private PartitionKeyDesc() {
        partitionKeyValueType = PartitionKeyValueType.LESS_THAN; // LESS_THAN is default type.
    }

    public static PartitionKeyDesc createLessThan(List<PartitionValue> upperValues) {
        PartitionKeyDesc desc = new PartitionKeyDesc();
        desc.upperValues = upperValues;
        desc.partitionKeyValueType = PartitionKeyValueType.LESS_THAN;
        return desc;
    }

    public static PartitionKeyDesc createIn(List<List<PartitionValue>> inValues) {
        PartitionKeyDesc desc = new PartitionKeyDesc();
        desc.inValues = inValues;
        desc.partitionKeyValueType = PartitionKeyValueType.IN;
        return desc;
    }

    public static PartitionKeyDesc createFixed(List<PartitionValue> lowerValues, List<PartitionValue> upperValues) {
        PartitionKeyDesc desc = new PartitionKeyDesc();
        desc.lowerValues = lowerValues;
        desc.upperValues = upperValues;
        desc.partitionKeyValueType = PartitionKeyValueType.FIXED;
        return desc;
    }

    public static PartitionKeyDesc createMultiFixed(
            List<PartitionValue> lowerValues,
            List<PartitionValue> upperValues,
            Long timeInterval,
            String timeType) {
        PartitionKeyDesc desc = new PartitionKeyDesc();
        desc.lowerValues = lowerValues;
        desc.upperValues = upperValues;
        desc.timeInterval = timeInterval;
        desc.timeType = timeType;
        desc.partitionKeyValueType = PartitionKeyValueType.FIXED;
        return desc;
    }

    public static PartitionKeyDesc createMultiFixed(
            List<PartitionValue> lowerValues,
            List<PartitionValue> upperValues,
            Long interval) {
        PartitionKeyDesc desc = new PartitionKeyDesc();
        desc.lowerValues = lowerValues;
        desc.upperValues = upperValues;
        desc.timeInterval = interval;
        desc.timeType = "";
        desc.partitionKeyValueType = PartitionKeyValueType.FIXED;
        return desc;
    }

    public Long getTimeInterval() {
        return timeInterval;
    }

    public String getTimeType() {
        return timeType;
    }

    public List<PartitionValue> getLowerValues() {
        return lowerValues;
    }

    public List<PartitionValue> getUpperValues() {
        return upperValues;
    }

    public List<List<PartitionValue>> getInValues() {
        return inValues;
    }

    public boolean isMax() {
        return this == MAX_VALUE;
    }

    public boolean isDummy() {
        return this == DUMMY_KEY_DESC;
    }

    public boolean hasLowerValues() {
        return lowerValues != null;
    }

    public boolean hasUpperValues() {
        return upperValues != null;
    }

    public PartitionKeyValueType getPartitionType() {
        return partitionKeyValueType;
    }

    public boolean hasInValues() {
        return inValues != null;
    }

    public void analyze(int partColNum) throws AnalysisException {
        if (isDummy()) {
            return;
        }
        if (!isMax()) {
            if ((upperValues != null && (upperValues.isEmpty() || upperValues.size() > partColNum))) {
                throw new AnalysisException("Partition values number is more than partition column number: " + toSql());
            }
        }

        // Currently, we do not support MAXVALUE in multi partition range values. eg: ("100", "200", MAXVALUE);
        // Because we still don’t support expressing such values on the BE side.
        // Maybe support later.
        // But we can support MAXVALUE in single partition values.
        if (lowerValues != null && lowerValues.size() > 1) {
            for (PartitionValue lowerVal : lowerValues) {
                if (lowerVal.isMax()) {
                    throw new AnalysisException("Not support MAXVALUE in multi partition range values.");
                }
            }
        }

        if (upperValues != null && upperValues.size() > 1) {
            for (PartitionValue upperVal : upperValues) {
                if (upperVal.isMax()) {
                    throw new AnalysisException("Not support MAXVALUE in multi partition range values.");
                }
            }
        }
    }

    // returns:
    // 1: MAXVALUE
    // 2: ("100", "200", MAXVALUE)
    // 3: [("100", "200"), ("300", "200"))
    public String toSql() {
        if (isMax()) {
            return "MAXVALUE";
        }

        if (partitionKeyValueType == PartitionKeyValueType.LESS_THAN) {
            return getPartitionValuesStr(upperValues);
        } else if (partitionKeyValueType == PartitionKeyValueType.FIXED) {
            StringBuilder sb = new StringBuilder("[");
            sb.append(getPartitionValuesStr(lowerValues)).append(", ").append(getPartitionValuesStr(upperValues));
            sb.append(")");
            return sb.toString();
        } else if (partitionKeyValueType == PartitionKeyValueType.IN) {
            StringBuilder sb = new StringBuilder("(");
            for (int i = 0; i < inValues.size(); i++) {
                String valueStr = getPartitionValuesStr(inValues.get(i));
                if (inValues.get(i).size() == 1) {
                    valueStr = valueStr.substring(1, valueStr.length() - 1);
                }
                sb.append(valueStr);
                if (i < inValues.size() - 1) {
                    sb.append(",");
                }
            }
            sb.append(")");
            return sb.toString();
        } else {
            return "INVALID";
        }
    }

    private String getPartitionValuesStr(List<PartitionValue> values) {
        StringBuilder sb = new StringBuilder("(");
        Joiner.on(", ").appendTo(sb, Lists.transform(values, new Function<PartitionValue, String>() {
            @Override
            public String apply(PartitionValue v) {
                if (v.isMax()) {
                    return v.getStringValue();
                } else {
                    return "'" + v.getStringValue() + "'";
                }
            }
        })).append(")");
        return sb.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        PartitionKeyDesc that = (PartitionKeyDesc) o;
        return Objects.equal(lowerValues, that.lowerValues)
                && Objects.equal(upperValues, that.upperValues)
                && Objects.equal(inValues, that.inValues)
                && partitionKeyValueType == that.partitionKeyValueType
                && Objects.equal(timeInterval, that.timeInterval)
                && Objects.equal(timeType, that.timeType);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(lowerValues, upperValues, inValues, partitionKeyValueType, timeInterval, timeType);
    }
}