morningman opened a new pull request, #68982:
URL: https://github.com/apache/doris/pull/68982
### What problem does this PR solve?
Issue Number: None
Related PR: None
Problem Summary:
#### In short
The table and database property `binlog.format` only accepted the exact
upper case names `ROW` and `STATEMENT_AND_SNAPSHOT`. `'binlog.format' = 'row'`
was rejected by every DDL, and CREATE TABLE even answered with Java's internal
`No enum constant ...` message. This PR matches the value case-insensitively at
every place it is parsed and always stores the canonical upper case name,
including for database properties, which Doris persists as written and parses
again on replay. Unknown values are still rejected, now with the same message
everywhere.
#### Background
- `binlog.format` is a table and database property. `ROW` turns on row
binlog (table streams, IVM, the `binlog()` TVF); `STATEMENT_AND_SNAPSHOT` is
the CCR binlog. A table's format cannot be changed after it is created.
- The value is parsed in three places, all with `BinlogFormat.valueOf`,
which is case-sensitive:
- `PropertyAnalyzer#analyzeBinlogConfig`: validation used by ALTER TABLE,
CREATE DATABASE and ALTER DATABASE (and by CREATE TABLE, but only later, see
below).
- `BinlogConfig#mergeFromProperties`: builds an effective config. CREATE
TABLE calls it on the raw properties first, in
`Database#getBinlogConfigsForCreateTable`, before anything validates them.
Database properties also go through it on every replay and image load
(`Database#gsonPostProcess`, `replayUpdateDbProperties`).
- `TableProperty#buildBinlogConfig`: rebuilds a table's config from its
persisted properties.
- Table metadata stores the format from the enum
(`BinlogConfig#toProperties`), so it is always canonical. Database properties
are different: `DatabaseProperty` keeps the map exactly as the user wrote it,
and that map is what goes to the edit log and the image.
- Neighbouring enum-like properties such as `storage_medium` and
`compression` are already matched case-insensitively.
#### The problem, and what it cost
- `'binlog.format' = 'row'`, or any spelling other than all upper case, is
rejected by all four DDLs that take the property. CREATE TABLE fails with `No
enum constant org.apache.doris.catalog.BinlogConfig.BinlogFormat.row`, which
does not even name the property; ALTER TABLE, CREATE DATABASE and ALTER
DATABASE fail with `Invalid binlog format value: row`.
- Users have to know that this one value is case-sensitive while the
properties next to it are not. The full before / after comparison is in Results
below.
#### How this PR fixes it
1. `BinlogConfig.BinlogFormat#fromString` looks the value up by enum name,
ignoring case, and throws `IllegalArgumentException("Invalid binlog format
value: <value>")` for anything else. The three parse sites above all use it.
2. `PropertyAnalyzer#analyzeBinlogConfig` returns the canonical name, so the
table property, `SHOW CREATE TABLE` and `information_schema.table_properties`
show `ROW`.
3. `PropertyAnalyzer#normalizeBinlogFormat`, called by
`CreateDatabaseCommand` and `AlterDatabasePropertiesCommand` right after
validation, rewrites the database property to its canonical name before it is
stored. This matters for compatibility: the stored map goes to the edit log and
the image, and an FE without this change (an old follower during a rolling
upgrade, or after a downgrade) parses it with `valueOf`. Had it been stored as
`row`, that FE would fail to replay it with the same `No enum constant`
exception. With the rewrite, the stored value is always one that an FE without
this change could have written itself.
What it buys:
- Any case is accepted by all four DDLs, and the stored and displayed value
is always upper case.
- Persisted metadata keeps exactly the old format, so mixed-version clusters
and downgrades are unaffected.
- An unknown value such as `rows` gets `Invalid binlog format value: rows`
from CREATE TABLE too, instead of the `No enum constant` message.
#### Results
"Before" is current master: the messages were reproduced by switching
`fromString` back to `valueOf` in the FE unit tests. "After" is this branch on
a local 1 FE + 1 BE cluster, checked by the new regression suite.
| Statement | Before | After |
|---|---|---|
| `CREATE TABLE ... ('binlog.enable' = 'true', 'binlog.format' = 'row')` |
`No enum constant org.apache.doris.catalog.BinlogConfig.BinlogFormat.row` | ROW
binlog table; `information_schema.table_properties` shows `ROW`; the `binlog()`
TVF returns the inserted rows |
| `CREATE TABLE ... ('binlog.enable' = 'true', 'binlog.format' =
'Statement_And_Snapshot')` | `No enum constant
...BinlogFormat.Statement_And_Snapshot` | CCR binlog table, stored as
`STATEMENT_AND_SNAPSHOT` |
| `ALTER TABLE <ROW table> SET ('binlog.format' = 'Row')` | `Invalid binlog
format value: Row` | accepted, no-op |
| `ALTER TABLE <ROW table> SET ('binlog.format' = 'statement_and_snapshot')`
| `Invalid binlog format value: statement_and_snapshot` | `not support change
binlog format from ROW to STATEMENT_AND_SNAPSHOT` (the real reason) |
| `CREATE DATABASE d PROPERTIES ('binlog.enable' = 'true', 'binlog.format' =
'row')` | `Invalid binlog format value: row` | created;
`information_schema.database_properties` shows `ROW`; a new table in `d`
inherits ROW binlog |
| `ALTER DATABASE d SET PROPERTIES ('binlog.enable' = 'false',
'binlog.format' = 'statement_and_snapshot')` | `Invalid binlog format value:
statement_and_snapshot` | accepted, stored as `STATEMENT_AND_SNAPSHOT` |
| `CREATE TABLE ... ('binlog.format' = 'rows')` | `No enum constant
...BinlogFormat.rows` | `Invalid binlog format value: rows` |
| ALTER TABLE / CREATE DATABASE / ALTER DATABASE with `'rows'` | `Invalid
binlog format value: rows` | unchanged |
#### Classes, and how they call each other
- `BinlogConfig.BinlogFormat#fromString` (new): the only parser of the value.
- `PropertyAnalyzer#analyzeBinlogConfig`: validates, now returns the
canonical name.
- `PropertyAnalyzer#normalizeBinlogFormat` (new): rewrites a map's value in
place; used only for database properties.
- `BinlogConfig#mergeFromProperties`, `TableProperty#buildBinlogConfig`:
switched from `valueOf` to `fromString`.
- `CreateDatabaseCommand#validate`,
`AlterDatabasePropertiesCommand#validate`: call `normalizeBinlogFormat` after
`analyzeBinlogConfig`.
```
CREATE TABLE InternalCatalog#createOlapTable
Database#getBinlogConfigsForCreateTable ->
BinlogConfig#mergeFromProperties -> fromString
PropertyAnalyzer#analyzeBinlogConfig
-> fromString
(the table stores BinlogConfig#toProperties, i.e. the
enum name)
ALTER TABLE ModifyTablePropertiesOp#validate ->
PropertyAnalyzer#analyzeBinlogConfig -> fromString
SchemaChangeHandler#updateBinlogConfig ->
analyzeBinlogConfig + mergeFromProperties
CREATE DATABASE CreateDatabaseCommand#validate ->
analyzeBinlogConfig, then normalizeBinlogFormat -> fromString
ALTER DATABASE AlterDatabasePropertiesCommand#validate ->
analyzeBinlogConfig, then normalizeBinlogFormat -> fromString
(the map is then stored in DatabaseProperty and journaled
as is)
replay / image Database#gsonPostProcess, #replayUpdateDbProperties
-> DatabaseProperty#getBinlogConfig ->
BinlogConfig#mergeFromProperties -> fromString
TableProperty#buildBinlogConfig
-> fromString
```
Not changed: BE, thrift and protobuf (they receive the enum, not the
string), the rule that a table's format is immutable, and every other
`binlog.*` property.
### Release note
The table and database property `binlog.format` is now case-insensitive, for
example `'binlog.format' = 'row'` is accepted. The value is always stored and
shown in upper case.
### Check List (For Author)
- Test <!-- At least one of them must be included. -->
- [x] Regression test
- [x] Unit Test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
- [ ] Previous test can cover this change.
- [ ] No code files have been changed.
- [ ] Other reason <!-- Add your reason? -->
- FE UT: `BinlogFormatCaseInsensitiveTest` (new; CREATE / ALTER TABLE,
CREATE / ALTER DATABASE, unknown values, re-parsing stored values) and new
cases in `PropertyAnalyzerTest`. Together with `RowBinlogTtlConfigTest`,
`RowBinlogFeatureGateTest` and `RowBinlogConfigPublicationTest`: 56 tests, all
pass. With `fromString` switched back to `valueOf`, the 9 new case-insensitive
cases fail.
- Regression: `row_binlog_p0/test_binlog_format_case_insensitive` (new;
`.out` generated by the runner) passes on a local 1 FE + 1 BE cluster, and so
do the existing `test_binlog_property_alter_exception`,
`test_row_binlog_basic`, `test_row_binlog_cluster_key`,
`test_alter_database_properties_command` and `test_database_properties`.
- Behavior changed:
- [ ] No.
- [x] Yes. <!-- Explain the behavior change --> `binlog.format` values
in any case, which used to be rejected, are now accepted and stored by their
canonical upper case name. CREATE TABLE reports an unknown value as `Invalid
binlog format value: <value>` instead of `No enum constant ...`.
- Does this need documentation?
- [x] No.
- [ ] Yes. <!-- Add document PR link here. eg:
https://github.com/apache/doris-website/pull/1214 -->
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label <!-- Add branch pick label that this PR should
merge into -->
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]