This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 46e0bd81388 [fix](fe) Reject unknown binlog properties and non-boolean
binlog.enable (#69375)
46e0bd81388 is described below
commit 46e0bd813888309a7faaba3544fe2a98959063f4
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Thu Oct 22 10:16:43 2026 +0100
[fix](fe) Reject unknown binlog properties and non-boolean binlog.enable
(#69375)
### What problem does this PR solve?
Issue Number: None
Related PR: #69311
Problem Summary:
**In short.** CCR binlog is configured by four properties:
`binlog.enable`, `binlog.ttl_seconds`, `binlog.max_bytes` and
`binlog.max_history_nums`. `PropertyAnalyzer.analyzeBinlogConfig`, which
every DDL uses to check them, ignored any other key under `binlog.` and
read `binlog.enable` with `Boolean.parseBoolean`, so a misspelled key or
a value such as `"yes"` was accepted without a word. The worst case:
`ALTER TABLE t SET ("binlog.enable" = "yes")` succeeds and turns off the
CCR binlog of a table that had it on. This PR rejects both, the way
#69311 already made the `changelog.*` properties strict.
**Background.**
- Every DDL path validates the binlog properties with
`analyzeBinlogConfig`: CREATE DATABASE and ALTER DATABASE
(`CreateDatabaseCommand` / `AlterDatabasePropertiesCommand.validate`),
CREATE TABLE (`InternalCatalog.createOlapTable`) and ALTER TABLE
(`ModifyTablePropertiesOp.validate`, then
`SchemaChangeHandler.updateBinlogConfig`). Database properties are
stored as the user wrote them.
- #69311 split changelog (formerly row binlog) from CCR binlog. Its
`changelog.*` properties reject unknown keys (`Unknown changelog
property: <key>`) and accept only `true` / `false` for
`changelog.enable`; the replaced `binlog.format` and
`binlog.need_historical_value` are rejected in every DDL. The four CCR
keys kept the lenient parsing.
**The problem, and what it cost.** On a cluster running master before
this change:
| Statement | Before |
|---|---|
| `ALTER TABLE t SET ("binlog.enable" = "yes")`, `t` has binlog on |
succeeds; `t`'s binlog is now off |
| `CREATE TABLE ... ("binlog.enable" = "yes")` | the table is created
with binlog off |
| `ALTER TABLE t SET ("binlog.ttl_second" = "3600")` (typo) | `Nothing
is changed. please check your alter stmt.` |
| `CREATE TABLE ... ("binlog.ttl_second" = "3600")` | `Unknown
properties: {binlog.ttl_second=3600}` |
| `CREATE DATABASE d PROPERTIES ("binlog.ttl_second" = "3600")` |
succeeds; the key is persisted |
| `ALTER DATABASE d SET PROPERTIES ("binlog.max_byte" = "1024")` |
succeeds; the key is persisted |
| `ALTER DATABASE d SET PROPERTIES ("binlog.enable" = "1")` | succeeds;
`"1"` is persisted and read as false |
**How this PR fixes it.**
- `analyzeBinlogConfig` rejects any key under `binlog.` other than the
four with `Unknown binlog property: <key>`. The replaced `binlog.format`
and `binlog.need_historical_value` are still checked first, so they keep
their message that names the replacement.
- `binlog.enable` accepts only `true` or `false`, in any case, and
otherwise fails with the existing `Invalid binlog enable value:
<value>`, which the old `try` / `catch` around `Boolean.parseBoolean`
could never reach.
- CREATE TABLE runs this check on the properties as written, where it
only checked the replaced keys before. Its later call to
`analyzeBinlogConfig` sees the effective binlog config (the database
default merged with the table properties) that `createOlapTable` puts
back over them, and by then `"yes"` has already become `"false"`.
- Persisted metadata is not validated again: image load and edit log
replay read binlog properties through
`BinlogConfig.mergeFromProperties`, which is unchanged, so a database
that already stored such a key loads as before. A database's binlog
default reaches CREATE TABLE as a `BinlogConfig` object, not as
properties, so it does not make CREATE TABLE fail either.
**Results.**
| Statement | After |
|---|---|
| `ALTER TABLE t SET ("binlog.enable" = "yes")` | `Invalid binlog enable
value: yes`; `t` keeps its binlog setting |
| `CREATE TABLE ... ("binlog.enable" = "yes")` | `Invalid binlog enable
value: yes` |
| `ALTER TABLE` / `CREATE TABLE` / `CREATE DATABASE` with
`"binlog.ttl_second"` | `Unknown binlog property: binlog.ttl_second` |
| `ALTER DATABASE ... ("binlog.max_byte" = "1024")` | `Unknown binlog
property: binlog.max_byte` |
| `ALTER DATABASE ... ("binlog.enable" = "1")` | `Invalid binlog enable
value: 1` |
| `"binlog.enable" = "TRUE"` / `"False"` | accepted, stored as `true` /
`false` (unchanged) |
---
.../apache/doris/common/util/PropertyAnalyzer.java | 17 ++--
.../apache/doris/datasource/InternalCatalog.java | 3 +-
.../doris/catalog/ChangelogConfigSplitTest.java | 16 +++
.../apache/doris/common/PropertyAnalyzerTest.java | 31 ++++++
.../ddl_p0/test_binlog_property_validation.out | 6 ++
.../ddl_p0/test_binlog_property_validation.groovy | 108 +++++++++++++++++++++
6 files changed, 174 insertions(+), 7 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/common/util/PropertyAnalyzer.java
b/fe/fe-core/src/main/java/org/apache/doris/common/util/PropertyAnalyzer.java
index 4387caf05f1..746bb1d07a3 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/common/util/PropertyAnalyzer.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/common/util/PropertyAnalyzer.java
@@ -1534,17 +1534,22 @@ public class PropertyAnalyzer {
return null;
}
checkLegacyBinlogProperties(properties);
+ for (String key : properties.keySet()) {
+ if (key.startsWith(PROPERTIES_BINLOG_PREFIX) &&
!key.equals(PROPERTIES_BINLOG_ENABLE)
+ && !key.equals(PROPERTIES_BINLOG_TTL_SECONDS) &&
!key.equals(PROPERTIES_BINLOG_MAX_BYTES)
+ && !key.equals(PROPERTIES_BINLOG_MAX_HISTORY_NUMS)) {
+ throw new AnalysisException("Unknown binlog property: " + key);
+ }
+ }
Map<String, String> binlogConfigMap = Maps.newHashMap();
// check PROPERTIES_BINLOG_ENABLE = "binlog.enable";
if (properties.containsKey(PROPERTIES_BINLOG_ENABLE)) {
- String enable = properties.get(PROPERTIES_BINLOG_ENABLE);
- try {
- binlogConfigMap.put(PROPERTIES_BINLOG_ENABLE,
String.valueOf(Boolean.parseBoolean(enable)));
- properties.remove(PROPERTIES_BINLOG_ENABLE);
- } catch (Exception e) {
+ String enable = properties.remove(PROPERTIES_BINLOG_ENABLE);
+ if (!StringUtils.equalsAnyIgnoreCase(enable, "true", "false")) {
throw new AnalysisException("Invalid binlog enable value: " +
enable);
}
+ binlogConfigMap.put(PROPERTIES_BINLOG_ENABLE,
String.valueOf(Boolean.parseBoolean(enable)));
}
// check PROPERTIES_BINLOG_TTL_SECONDS = "binlog.ttl_seconds";
if (properties.containsKey(PROPERTIES_BINLOG_TTL_SECONDS)) {
@@ -1587,7 +1592,7 @@ public class PropertyAnalyzer {
/**
* Rejects the binlog keys the changelog.* properties replaced, whatever
their value is.
*/
- public static void checkLegacyBinlogProperties(Map<String, String>
properties) throws AnalysisException {
+ private static void checkLegacyBinlogProperties(Map<String, String>
properties) throws AnalysisException {
if (properties.containsKey(PROPERTIES_LEGACY_BINLOG_FORMAT)) {
throw new AnalysisException("Property " +
PROPERTIES_LEGACY_BINLOG_FORMAT
+ " is no longer supported, use \"" +
PROPERTIES_CHANGELOG_ENABLE
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java
index 676ed8dc5cb..1b879531b12 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java
@@ -2495,7 +2495,8 @@ public class InternalCatalog implements
CatalogIf<Database> {
boolean tableHasExist = false;
ChangelogConfig createTableChangelogConfig;
try {
-
PropertyAnalyzer.checkLegacyBinlogProperties(createTableInfo.getProperties());
+ // Check the binlog properties as written: the effective binlog
config is put back over them below.
+
PropertyAnalyzer.analyzeBinlogConfig(Maps.newHashMap(createTableInfo.getProperties()));
createTableChangelogConfig =
db.getChangelogConfigForCreateTable(createTableInfo.getProperties());
} catch (AnalysisException e) {
throw new DdlException(e.getMessage());
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/catalog/ChangelogConfigSplitTest.java
b/fe/fe-core/src/test/java/org/apache/doris/catalog/ChangelogConfigSplitTest.java
index d157a67200b..5a7b2968568 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/catalog/ChangelogConfigSplitTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/catalog/ChangelogConfigSplitTest.java
@@ -280,6 +280,22 @@ public class ChangelogConfigSplitTest extends
TestWithFeService {
"Unknown changelog property: changelog.format");
}
+ @Test
+ public void testDdlValidatesBinlogProperties() throws Exception {
+ String dup = "(k INT, v INT) DUPLICATE KEY(k)";
+ assertCreateFails("binlog_invalid_enable", dup,
"'binlog.enable'='yes'", "Invalid binlog enable value: yes");
+ assertCreateFails("binlog_unknown_key", dup,
"'binlog.ttl_second'='10'",
+ "Unknown binlog property: binlog.ttl_second");
+
+ OlapTable table = createDupTable(db, "binlog_alter",
"'binlog.enable'='TRUE'");
+ Assertions.assertTrue(table.getBinlogConfig().isEnableForCCR());
+ assertAlterFails("ALTER TABLE split.binlog_alter SET
('binlog.enable'='yes')",
+ "Invalid binlog enable value: yes");
+ assertAlterFails("ALTER TABLE split.binlog_alter SET
('binlog.ttl_second'='10')",
+ "Unknown binlog property: binlog.ttl_second");
+ Assertions.assertTrue(table.getBinlogConfig().isEnableForCCR());
+ }
+
@Test
public void testDatabaseChangelogDefaults() throws Exception {
createDatabaseWithSql("CREATE DATABASE split_default_db "
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/common/PropertyAnalyzerTest.java
b/fe/fe-core/src/test/java/org/apache/doris/common/PropertyAnalyzerTest.java
index 13304fc5704..9101501f33e 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/common/PropertyAnalyzerTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/common/PropertyAnalyzerTest.java
@@ -67,6 +67,37 @@ public class PropertyAnalyzerTest {
() -> PropertyAnalyzer.analyzeBinlogConfig(properties));
}
+ @Test
+ public void testAnalyzeBinlogConfig() throws AnalysisException {
+ Map<String, String> properties = new HashMap<>();
+ properties.put(PropertyAnalyzer.PROPERTIES_BINLOG_ENABLE, "TRUE");
+ properties.put(PropertyAnalyzer.PROPERTIES_BINLOG_TTL_SECONDS, "60");
+ properties.put(PropertyAnalyzer.PROPERTIES_BINLOG_MAX_BYTES, "1024");
+ properties.put(PropertyAnalyzer.PROPERTIES_BINLOG_MAX_HISTORY_NUMS,
"10");
+ properties.put(PropertyAnalyzer.PROPERTIES_CHANGELOG_ENABLE, "false");
+ Map<String, String> analyzed =
PropertyAnalyzer.analyzeBinlogConfig(properties);
+
Assertions.assertEquals(Map.of(PropertyAnalyzer.PROPERTIES_BINLOG_ENABLE,
"true",
+ PropertyAnalyzer.PROPERTIES_BINLOG_TTL_SECONDS, "60",
+ PropertyAnalyzer.PROPERTIES_BINLOG_MAX_BYTES, "1024",
+ PropertyAnalyzer.PROPERTIES_BINLOG_MAX_HISTORY_NUMS, "10"),
analyzed);
+
Assertions.assertEquals(Map.of(PropertyAnalyzer.PROPERTIES_CHANGELOG_ENABLE,
"false"), properties);
+
+ properties.put(PropertyAnalyzer.PROPERTIES_BINLOG_ENABLE, "False");
+ Assertions.assertEquals("false",
PropertyAnalyzer.analyzeBinlogConfig(properties)
+ .get(PropertyAnalyzer.PROPERTIES_BINLOG_ENABLE));
+
+ for (Map.Entry<String, String> invalid : Map.of(
+ PropertyAnalyzer.PROPERTIES_BINLOG_ENABLE, "Invalid binlog
enable value: yes",
+ "binlog.ttl_second", "Unknown binlog property:
binlog.ttl_second",
+ "binlog.compaction", "Unknown binlog property:
binlog.compaction").entrySet()) {
+ Map<String, String> invalidProperties = new HashMap<>();
+ invalidProperties.put(invalid.getKey(), "yes");
+ AnalysisException exception =
Assertions.assertThrows(AnalysisException.class,
+ () ->
PropertyAnalyzer.analyzeBinlogConfig(invalidProperties));
+
Assertions.assertTrue(exception.getMessage().contains(invalid.getValue()),
exception.getMessage());
+ }
+ }
+
@ParameterizedTest
@ValueSource(strings = {"ROW", "row", "STATEMENT_AND_SNAPSHOT",
"statement_and_snapshot", "rows"})
public void testReplacedBinlogKeysAreRejected(String value) {
diff --git a/regression-test/data/ddl_p0/test_binlog_property_validation.out
b/regression-test/data/ddl_p0/test_binlog_property_validation.out
new file mode 100644
index 00000000000..61b5eb70522
--- /dev/null
+++ b/regression-test/data/ddl_p0/test_binlog_property_validation.out
@@ -0,0 +1,6 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !table_binlog_enable --
+test_binlog_property_validation_tbl binlog.enable false
+
+-- !database_binlog_properties --
+
diff --git
a/regression-test/suites/ddl_p0/test_binlog_property_validation.groovy
b/regression-test/suites/ddl_p0/test_binlog_property_validation.groovy
new file mode 100644
index 00000000000..15e9d1b82a2
--- /dev/null
+++ b/regression-test/suites/ddl_p0/test_binlog_property_validation.groovy
@@ -0,0 +1,108 @@
+// 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.
+
+// The "binlog.*" properties configure CCR binlog. A key under "binlog." other
than binlog.enable,
+// binlog.ttl_seconds, binlog.max_bytes and binlog.max_history_nums is
rejected instead of being
+// ignored, and binlog.enable accepts only true or false, in any case.
+suite("test_binlog_property_validation") {
+ sql "DROP TABLE IF EXISTS test_binlog_property_validation_tbl"
+
+ test {
+ sql """
+ CREATE TABLE test_binlog_property_validation_tbl (
+ k1 INT NOT NULL,
+ v1 INT
+ )
+ DUPLICATE KEY(k1)
+ DISTRIBUTED BY HASH(k1) BUCKETS 1
+ PROPERTIES (
+ "replication_num" = "1",
+ "binlog.ttl_second" = "3600"
+ )
+ """
+ exception "Unknown binlog property: binlog.ttl_second"
+ }
+ test {
+ sql """
+ CREATE TABLE test_binlog_property_validation_tbl (
+ k1 INT NOT NULL,
+ v1 INT
+ )
+ DUPLICATE KEY(k1)
+ DISTRIBUTED BY HASH(k1) BUCKETS 1
+ PROPERTIES (
+ "replication_num" = "1",
+ "binlog.enable" = "yes"
+ )
+ """
+ exception "Invalid binlog enable value: yes"
+ }
+
+ sql """
+ CREATE TABLE test_binlog_property_validation_tbl (
+ k1 INT NOT NULL,
+ v1 INT
+ )
+ DUPLICATE KEY(k1)
+ DISTRIBUTED BY HASH(k1) BUCKETS 1
+ PROPERTIES (
+ "replication_num" = "1",
+ "binlog.enable" = "False"
+ )
+ """
+ order_qt_table_binlog_enable """
+ SELECT TABLE_NAME, PROPERTY_NAME, PROPERTY_VALUE
+ FROM information_schema.table_properties
+ WHERE TABLE_CATALOG = 'internal'
+ AND TABLE_SCHEMA = '${context.dbName}'
+ AND TABLE_NAME = 'test_binlog_property_validation_tbl'
+ AND PROPERTY_NAME = 'binlog.enable'
+ """
+
+ // The unknown key used to fail with "Nothing is changed", and "yes" used
to be read as false,
+ // which turned binlog off on a table that had it on.
+ test {
+ sql """ALTER TABLE test_binlog_property_validation_tbl SET
("binlog.ttl_second" = "3600")"""
+ exception "Unknown binlog property: binlog.ttl_second"
+ }
+ test {
+ sql """ALTER TABLE test_binlog_property_validation_tbl SET
("binlog.enable" = "yes")"""
+ exception "Invalid binlog enable value: yes"
+ }
+
+ sql "DROP DATABASE IF EXISTS test_binlog_property_validation_db"
+ test {
+ sql """CREATE DATABASE test_binlog_property_validation_db PROPERTIES
("binlog.ttl_second" = "3600")"""
+ exception "Unknown binlog property: binlog.ttl_second"
+ }
+ sql "CREATE DATABASE test_binlog_property_validation_db"
+ // Database properties are stored as given, so these used to be persisted
as they were.
+ test {
+ sql """ALTER DATABASE test_binlog_property_validation_db SET
PROPERTIES ("binlog.max_byte" = "1024")"""
+ exception "Unknown binlog property: binlog.max_byte"
+ }
+ test {
+ sql """ALTER DATABASE test_binlog_property_validation_db SET
PROPERTIES ("binlog.enable" = "1")"""
+ exception "Invalid binlog enable value: 1"
+ }
+ order_qt_database_binlog_properties """
+ SELECT SCHEMA_NAME, PROPERTY_NAME, PROPERTY_VALUE
+ FROM information_schema.database_properties
+ WHERE SCHEMA_NAME = 'test_binlog_property_validation_db'
+ AND PROPERTY_NAME LIKE 'binlog.%'
+ """
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]