Skip to content

Commit dd86b4e

Browse files
feat(config): warn unknown field instead of panic on start up (#17504)
* add serde_ignored * add toml * use TomlIgnored * remove deny_unkown_fields * fix test * fix license * fix format * keep config load as origin and add more unit test * Update read_cache_content.rs --------- Co-authored-by: BohuTANG <overred.shuttler@gmail.com>
1 parent bbbca20 commit dd86b4e

File tree

8 files changed

+192
-14
lines changed

8 files changed

+192
-14
lines changed

Cargo.lock

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,7 @@ scroll = "0.12.0"
457457
semver = "1.0.14"
458458
serde = { version = "1.0.164", features = ["derive", "rc"] }
459459
serde_derive = "1"
460+
serde_ignored = "0.1.10"
460461
serde_json = { version = "1.0.85", default-features = false, features = ["preserve_order", "unbounded_depth"] }
461462
serde_repr = "0.1.9"
462463
serde_stacker = { version = "0.1" }
@@ -501,7 +502,7 @@ tikv-jemalloc-sys = "0.6.0"
501502
tokio = { version = "1.35.0", features = ["full"] }
502503
tokio-stream = "0.1.11"
503504
tokio-util = { version = "0.7.13" }
504-
toml = { version = "0.8", default-features = false }
505+
toml = { version = "0.8", features = ["parse"] }
505506
tonic = { version = "0.12.3", features = ["transport", "codegen", "prost", "tls-roots", "tls"] }
506507
tonic-build = { version = "0.12.3" }
507508
tonic-reflection = { version = "0.12.3" }

src/query/config/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ storage-hdfs = ["databend-common-storage/storage-hdfs"]
1616
ignored = ["strum"]
1717

1818
[dependencies]
19+
anyhow = { workspace = true }
1920
clap = { workspace = true }
2021
databend-common-base = { workspace = true }
2122
databend-common-exception = { workspace = true }
@@ -25,11 +26,14 @@ databend-common-storage = { workspace = true }
2526
databend-common-tracing = { workspace = true }
2627
log = { workspace = true }
2728
serde = { workspace = true }
29+
serde_ignored = { workspace = true }
2830
serde_with = { workspace = true }
2931
serfig = { workspace = true }
32+
toml = { workspace = true }
3033

3134
[dev-dependencies]
3235
pretty_assertions = { workspace = true }
36+
tempfile = { workspace = true }
3337

3438
[build-dependencies]
3539
databend-common-building = { workspace = true }

src/query/config/src/config.rs

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ use serde_with::with_prefix;
6161
use serfig::collectors::from_env;
6262
use serfig::collectors::from_file;
6363
use serfig::collectors::from_self;
64-
use serfig::parsers::Toml;
6564

6665
use super::inner;
6766
use super::inner::CatalogConfig as InnerCatalogConfig;
@@ -73,6 +72,7 @@ use super::inner::QueryConfig as InnerQueryConfig;
7372
use crate::builtin::BuiltInConfig;
7473
use crate::builtin::UDFConfig;
7574
use crate::builtin::UserConfig;
75+
use crate::toml::TomlIgnored;
7676
use crate::DATABEND_COMMIT_VERSION;
7777

7878
const CATALOG_HIVE: &str = "hive";
@@ -200,7 +200,7 @@ impl Config {
200200

201201
// Load from config file first.
202202
{
203-
let config_file = if !arg_conf.config_file.is_empty() {
203+
let final_config_file = if !arg_conf.config_file.is_empty() {
204204
// TODO: remove this `allow(clippy::redundant_clone)`
205205
// as soon as this issue is fixed:
206206
// https://github.com/rust-lang/rust-clippy/issues/10940
@@ -212,8 +212,11 @@ impl Config {
212212
"".to_string()
213213
};
214214

215-
if !config_file.is_empty() {
216-
builder = builder.collect(from_file(Toml, &config_file));
215+
if !final_config_file.is_empty() {
216+
let toml = TomlIgnored::new(Box::new(|path| {
217+
log::warn!("unknown field in config: {}", &path);
218+
}));
219+
builder = builder.collect(from_file(toml, &final_config_file));
217220
}
218221
}
219222

@@ -246,7 +249,10 @@ impl Config {
246249
};
247250

248251
if !config_file.is_empty() {
249-
builder = builder.collect(from_file(Toml, &config_file));
252+
let toml = TomlIgnored::new(Box::new(|path| {
253+
log::warn!("unknown field in config: {}", &path);
254+
}));
255+
builder = builder.collect(from_file(toml, &config_file));
250256
}
251257
}
252258

@@ -1418,7 +1424,7 @@ impl serde::de::Visitor<'_> for SettingVisitor {
14181424

14191425
/// Query config group.
14201426
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Args)]
1421-
#[serde(default, deny_unknown_fields)]
1427+
#[serde(default)]
14221428
pub struct QueryConfig {
14231429
/// Tenant id for get the information from the MetaSrv.
14241430
#[clap(long, value_name = "VALUE", default_value = "admin")]
@@ -2650,10 +2656,9 @@ impl From<InnerOTLPEndpointConfig> for OTLPEndpointConfig {
26502656
}
26512657

26522658
/// Meta config group.
2653-
/// deny_unknown_fields to check unknown field, like the deprecated `address`.
26542659
/// TODO(xuanwo): All meta_xxx should be rename to xxx.
26552660
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Args)]
2656-
#[serde(default, deny_unknown_fields)]
2661+
#[serde(default)]
26572662
pub struct MetaConfig {
26582663
/// The dir to store persisted meta state for a embedded meta store
26592664
#[clap(long = "meta-embedded-dir", value_name = "VALUE", default_value_t)]
@@ -2859,7 +2864,7 @@ impl TryInto<InnerLocalConfig> for LocalConfig {
28592864
}
28602865

28612866
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Args)]
2862-
#[serde(default, deny_unknown_fields)]
2867+
#[serde(default)]
28632868
pub struct CacheConfig {
28642869
/// Enable table meta cache. Default is enabled. Set it to false to disable all the table meta caches
28652870
#[clap(long = "cache-enable-table-meta-cache", default_value = "true")]
@@ -3097,7 +3102,7 @@ impl Default for DiskCacheKeyReloadPolicy {
30973102
}
30983103

30993104
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Args)]
3100-
#[serde(default, deny_unknown_fields)]
3105+
#[serde(default)]
31013106
pub struct DiskCacheConfig {
31023107
/// Max bytes of cached raw table data. Default 20GB, set it to 0 to disable it.
31033108
#[clap(
@@ -3135,7 +3140,7 @@ impl Default for DiskCacheConfig {
31353140
}
31363141

31373142
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Args)]
3138-
#[serde(default, deny_unknown_fields)]
3143+
#[serde(default)]
31393144
pub struct SpillConfig {
31403145
/// Path of spill to local disk. disable if it's empty.
31413146
#[clap(long, value_name = "VALUE", default_value = "")]
@@ -3161,7 +3166,7 @@ impl Default for SpillConfig {
31613166
}
31623167

31633168
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Args, Default)]
3164-
#[serde(default, deny_unknown_fields)]
3169+
#[serde(default)]
31653170
pub struct ResourcesManagementConfig {
31663171
#[clap(long = "type", value_name = "VALUE", default_value = "self_managed")]
31673172
#[serde(rename = "type")]

src/query/config/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ mod global;
3333
mod inner;
3434
mod mask;
3535
mod obsolete;
36+
mod toml;
3637
pub use builtin::*;
3738
pub use config::CacheStorageTypeConfig;
3839
pub use config::Commands;

src/query/config/src/toml.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2021 Datafuse Labs
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use anyhow::anyhow;
16+
use anyhow::Result;
17+
use serde::de::DeserializeOwned;
18+
use serfig::Parser;
19+
20+
/// Toml parser which used with serfig.
21+
///
22+
/// This parser will ignore unknown fields and call the handler with the path of the unknown field.
23+
pub struct TomlIgnored {
24+
handler: TomlUnknownFieldHandler,
25+
}
26+
27+
type TomlUnknownFieldHandler = Box<dyn Fn(&str) + Send + Sync + 'static>;
28+
29+
impl TomlIgnored {
30+
pub fn new(handler: TomlUnknownFieldHandler) -> Self {
31+
Self { handler }
32+
}
33+
}
34+
35+
impl std::fmt::Debug for TomlIgnored {
36+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37+
f.debug_struct("TomlIgnored").finish()
38+
}
39+
}
40+
41+
impl Parser for TomlIgnored {
42+
fn parse<T: DeserializeOwned>(&mut self, bs: &[u8]) -> Result<T> {
43+
let s = std::str::from_utf8(bs)
44+
.map_err(|err| anyhow!("input value is not valid utf-8: {err:?}"))?;
45+
let de = toml::Deserializer::new(s);
46+
let handler = &self.handler;
47+
Ok(serde_ignored::deserialize(de, move |path| {
48+
handler(path.to_string().as_str());
49+
})?)
50+
}
51+
}

src/query/config/tests/it/config.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2023 Datafuse Labs.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use std::ffi::OsString;
16+
use std::io::Write;
17+
18+
use clap::Parser;
19+
use databend_common_config::Config;
20+
use databend_common_config::InnerConfig;
21+
use pretty_assertions::assert_eq;
22+
23+
/// It's required to make sure setting's default value is the same with clap.
24+
#[test]
25+
fn test_config_default() {
26+
let setting_default = InnerConfig::default();
27+
let config_default: InnerConfig = Config::parse_from(Vec::<OsString>::new())
28+
.try_into()
29+
.expect("parse from args must succeed");
30+
31+
assert_eq!(
32+
setting_default, config_default,
33+
"default setting is different from default config, please check again"
34+
)
35+
}
36+
37+
#[test]
38+
fn test_load_config() {
39+
// Create a comprehensive test configuration with multiple sections and data types
40+
let conf = r#"
41+
# Query configuration section
42+
[query]
43+
max_active_sessions = 256
44+
shutdown_wait_timeout_ms = 5000
45+
flight_record_quota_size = 1048576
46+
unknown_query_field = "should be ignored"
47+
48+
# Log configuration section
49+
[log]
50+
file.level = "INFO"
51+
file.dir = "/tmp/databend/logs"
52+
unknown_log_field = 123
53+
54+
# Storage configuration section
55+
[storage]
56+
type = "fs"
57+
58+
[storage.fs]
59+
data_path = "/tmp/databend/data"
60+
61+
# Meta configuration section
62+
[meta]
63+
endpoint = "localhost:9191"
64+
username = "databend"
65+
password = "databend123"
66+
"#;
67+
68+
let mut temp_file = tempfile::NamedTempFile::new().unwrap();
69+
temp_file.write_all(conf.as_bytes()).unwrap();
70+
let temp_path = temp_file.path().to_str().unwrap().to_string();
71+
72+
// Save the original environment variable (if it exists)
73+
let original_env = std::env::var("CONFIG_FILE").ok();
74+
75+
// Set the environment variable to our test config file
76+
std::env::set_var("CONFIG_FILE", &temp_path);
77+
78+
// Use the original load function (without a config_file parameter)
79+
let config = Config::load(false).unwrap();
80+
81+
// Restore the original environment variable
82+
match original_env {
83+
Some(val) => std::env::set_var("CONFIG_FILE", val),
84+
None => std::env::remove_var("CONFIG_FILE"),
85+
}
86+
87+
// Test query configuration values
88+
assert_eq!(config.query.max_active_sessions, 256, "max_active_sessions should be 256");
89+
assert_eq!(config.query.shutdown_wait_timeout_ms, 5000, "shutdown_wait_timeout_ms should be 5000");
90+
assert_eq!(config.query.flight_record_quota_size, 1048576, "flight_record_quota_size should be 1048576");
91+
92+
// Test log configuration values
93+
assert_eq!(config.log.file.level, "INFO", "log.file.level should be INFO");
94+
assert_eq!(config.log.file.dir, "/tmp/databend/logs", "log.file.dir should be /tmp/databend/logs");
95+
96+
// Test storage configuration values
97+
assert_eq!(config.storage.fs.data_path, "/tmp/databend/data", "storage.fs.data_path should be /tmp/databend/data");
98+
99+
// Test meta configuration values
100+
assert_eq!(config.meta.endpoint, "localhost:9191", "meta.endpoint should be localhost:9191");
101+
assert_eq!(config.meta.username, "databend", "meta.username should be databend");
102+
assert_eq!(config.meta.password, "databend123", "meta.password should be databend123");
103+
}

src/query/service/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ sysinfo = { workspace = true }
168168
tempfile = { workspace = true }
169169
tokio = { workspace = true }
170170
tokio-stream = { workspace = true, features = ["net"] }
171-
toml = { workspace = true, default-features = false }
171+
toml = { workspace = true, features = ["parse"] }
172172
tonic = { workspace = true }
173173
typetag = { workspace = true }
174174
url = { workspace = true }

0 commit comments

Comments
 (0)