ChangelogConfig.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.AnalysisException;
import org.apache.doris.common.util.PropertyAnalyzer;
import org.apache.doris.persist.gson.GsonUtils;
import com.google.gson.annotations.SerializedName;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* The changelog configuration of a table, or the default a database gives to the tables created in it.
*
* <p>It is stored as the "changelog.*" properties in the property map of the table or the database and
* rebuilt from that map, like {@link BinlogConfig}. Only a table with changelog enabled stores them.
* Changelog can only be enabled when a table is created, and so can the before images
* ("changelog.need_historical_value"); the retention ("changelog.ttl_seconds") can be altered.
*/
public class ChangelogConfig {
// Internal persistence property, not a user-settable table property. It is the generation of the
// retention published to BE and Meta Service, which reject a configuration older than the one they have.
// ATTN(changelog-rename): formerly "__binlog_config_version"
public static final String CONFIG_VERSION = "__changelog_config_version";
public static final long TTL_SECONDS = 86400L; // 1 day
@SerializedName("enable")
private boolean enable;
@SerializedName("ttlSeconds")
private long ttlSeconds;
@SerializedName("needHistoricalValue")
private boolean needHistoricalValue;
@SerializedName("configVersion")
private long configVersion;
public ChangelogConfig(boolean enable, long ttlSeconds, boolean needHistoricalValue) {
this.enable = enable;
this.ttlSeconds = ttlSeconds;
this.needHistoricalValue = needHistoricalValue;
}
public ChangelogConfig(ChangelogConfig config) {
this(config.enable, config.ttlSeconds, config.needHistoricalValue);
configVersion = config.configVersion;
}
public ChangelogConfig() {
this(false, TTL_SECONDS, false);
}
/**
* Overrides this configuration with the "changelog.*" properties. The properties come from a statement or
* a database, so the internal generation is never read from them.
*/
public void mergeFromProperties(Map<String, String> properties) {
if (properties == null) {
return;
}
if (properties.containsKey(PropertyAnalyzer.PROPERTIES_CHANGELOG_ENABLE)) {
enable = Boolean.parseBoolean(properties.get(PropertyAnalyzer.PROPERTIES_CHANGELOG_ENABLE));
}
if (properties.containsKey(PropertyAnalyzer.PROPERTIES_CHANGELOG_TTL_SECONDS)) {
ttlSeconds = Long.parseLong(properties.get(PropertyAnalyzer.PROPERTIES_CHANGELOG_TTL_SECONDS));
}
if (properties.containsKey(PropertyAnalyzer.PROPERTIES_CHANGELOG_NEED_HISTORICAL_VALUE)) {
needHistoricalValue = Boolean.parseBoolean(
properties.get(PropertyAnalyzer.PROPERTIES_CHANGELOG_NEED_HISTORICAL_VALUE));
}
}
/**
* Applies the "changelog.*" properties of an ALTER TABLE. Changelog and its before images can only be
* chosen when the table is created.
*/
public void alterFromProperties(Map<String, String> properties) throws AnalysisException {
if (properties.containsKey(PropertyAnalyzer.PROPERTIES_CHANGELOG_ENABLE)) {
boolean newEnable = Boolean.parseBoolean(properties.get(PropertyAnalyzer.PROPERTIES_CHANGELOG_ENABLE));
if (newEnable != enable) {
throw new AnalysisException("not support change " + PropertyAnalyzer.PROPERTIES_CHANGELOG_ENABLE
+ " from " + enable + " to " + newEnable);
}
}
if (properties.containsKey(PropertyAnalyzer.PROPERTIES_CHANGELOG_NEED_HISTORICAL_VALUE)) {
boolean newNeedHistoricalValue = Boolean.parseBoolean(
properties.get(PropertyAnalyzer.PROPERTIES_CHANGELOG_NEED_HISTORICAL_VALUE));
if (newNeedHistoricalValue != needHistoricalValue) {
throw new AnalysisException("not support change "
+ PropertyAnalyzer.PROPERTIES_CHANGELOG_NEED_HISTORICAL_VALUE
+ " from " + needHistoricalValue + " to " + newNeedHistoricalValue);
}
}
if (properties.containsKey(PropertyAnalyzer.PROPERTIES_CHANGELOG_TTL_SECONDS)) {
applyExplicitTtl(Long.parseLong(properties.get(PropertyAnalyzer.PROPERTIES_CHANGELOG_TTL_SECONDS)));
}
}
// A changelog always expires: its retention must be positive once changelog is enabled. A database
// keeps any retention as a default while it does not enable changelog.
public void applyExplicitTtl(long ttlSeconds) throws AnalysisException {
if (enable && ttlSeconds <= 0) {
throw new AnalysisException(PropertyAnalyzer.PROPERTIES_CHANGELOG_TTL_SECONDS + " must be greater than 0");
}
this.ttlSeconds = ttlSeconds;
}
public boolean isEnable() {
return enable;
}
public long getTtlSeconds() {
return ttlSeconds;
}
public void setTtlSeconds(long ttlSeconds) {
this.ttlSeconds = ttlSeconds;
}
public boolean isTtlEnabled() {
return enable && ttlSeconds > 0;
}
public boolean getNeedHistoricalValue() {
return needHistoricalValue;
}
public long getConfigVersion() {
return configVersion;
}
public void setConfigVersion(long configVersion) {
this.configVersion = configVersion;
}
/**
* The properties a table with changelog stores. A table without changelog stores none of them. The
* internal generation is included once a retention has been published, so a new table does not have it.
*/
public Map<String, String> toProperties() {
Map<String, String> properties = new HashMap<>();
if (configVersion != 0) {
properties.put(CONFIG_VERSION, String.valueOf(configVersion));
}
properties.put(PropertyAnalyzer.PROPERTIES_CHANGELOG_ENABLE, String.valueOf(enable));
properties.put(PropertyAnalyzer.PROPERTIES_CHANGELOG_TTL_SECONDS, String.valueOf(ttlSeconds));
properties.put(PropertyAnalyzer.PROPERTIES_CHANGELOG_NEED_HISTORICAL_VALUE,
String.valueOf(needHistoricalValue));
return properties;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ChangelogConfig)) {
return false;
}
ChangelogConfig other = (ChangelogConfig) obj;
return enable == other.enable
&& ttlSeconds == other.ttlSeconds
&& needHistoricalValue == other.needHistoricalValue
&& configVersion == other.configVersion;
}
@Override
public int hashCode() {
return Objects.hash(enable, ttlSeconds, needHistoricalValue, configVersion);
}
@Override
public String toString() {
return GsonUtils.GSON.toJson(this);
}
public void appendToShowCreateTable(StringBuilder sb) {
sb.append(",\n\"").append(PropertyAnalyzer.PROPERTIES_CHANGELOG_ENABLE).append("\" = \"")
.append(enable).append("\"");
sb.append(",\n\"").append(PropertyAnalyzer.PROPERTIES_CHANGELOG_TTL_SECONDS).append("\" = \"")
.append(ttlSeconds).append("\"");
sb.append(",\n\"").append(PropertyAnalyzer.PROPERTIES_CHANGELOG_NEED_HISTORICAL_VALUE).append("\" = \"")
.append(needHistoricalValue).append("\"");
}
public static ChangelogConfig fromProperties(Map<String, String> properties) {
ChangelogConfig changelogConfig = new ChangelogConfig();
changelogConfig.mergeFromProperties(properties);
return changelogConfig;
}
}