1use rudb_common::{Field, LogicalType};
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct SettingEntry {
39 pub name: &'static str,
41 pub description: &'static str,
43 pub input_type: &'static str,
45 pub scope: &'static str,
47 pub aliases: &'static [&'static str],
49}
50
51pub const GLOBAL: &str = "GLOBAL";
53
54pub static SETTINGS: &[SettingEntry] = &[
56 SettingEntry {
57 name: "TimeZone",
58 description: "The current time zone",
59 input_type: "VARCHAR",
60 scope: GLOBAL,
61 aliases: &[],
62 },
63 SettingEntry {
64 name: "current_dialect",
65 description: "The SQL dialect used by the parser",
66 input_type: "VARCHAR",
67 scope: GLOBAL,
68 aliases: &[],
69 },
70 SettingEntry {
71 name: "default_null_order",
72 description: "NULL ordering used when none is specified (NULLS_FIRST or NULLS_LAST)",
73 input_type: "VARCHAR",
74 scope: GLOBAL,
75 aliases: &[],
76 },
77 SettingEntry {
78 name: "default_order",
79 description: "The order type used when none is specified (ASC or DESC)",
80 input_type: "VARCHAR",
81 scope: GLOBAL,
82 aliases: &[],
83 },
84 SettingEntry {
85 name: "dialect_compatibility_mode",
86 description: "Enable SQL dialect compatibility for a certain engine (e.g. `SET dialect_compatibility_mode='spark'`)",
87 input_type: "VARCHAR",
88 scope: GLOBAL,
89 aliases: &[],
90 },
91 SettingEntry {
92 name: "disable_timestamptz_casts",
93 description: "Disable casting from timestamp to timestamptz ",
94 input_type: "BOOLEAN",
95 scope: GLOBAL,
96 aliases: &[],
97 },
98 SettingEntry {
99 name: "disabled_optimizers",
100 description: "DEBUG SETTING: disable a specific set of optimizers (comma separated)",
101 input_type: "VARCHAR",
102 scope: GLOBAL,
103 aliases: &[],
104 },
105 SettingEntry {
106 name: "errors_as_json",
107 description: "Output error messages as structured JSON instead of as a raw string",
108 input_type: "BOOLEAN",
109 scope: GLOBAL,
110 aliases: &[],
111 },
112 SettingEntry {
113 name: "ieee_floating_point_ops",
114 description: "Use IEEE 754 behavior for supported floating point operations, returning NAN/INF instead of errors/NULL.",
115 input_type: "BOOLEAN",
116 scope: GLOBAL,
117 aliases: &[],
118 },
119 SettingEntry {
120 name: "integer_division",
121 description: "Whether or not the / operator defaults to integer division, or to floating point division",
122 input_type: "BOOLEAN",
123 scope: GLOBAL,
124 aliases: &[],
125 },
126 SettingEntry {
127 name: "max_memory",
128 description: "The maximum memory of the system (e.g. 1GB)",
129 input_type: "VARCHAR",
130 scope: GLOBAL,
131 aliases: &["memory_limit"],
132 },
133 SettingEntry {
134 name: "memory_limit",
135 description: "The maximum memory of the system (e.g. 1GB)",
136 input_type: "VARCHAR",
137 scope: GLOBAL,
138 aliases: &[],
139 },
140 SettingEntry {
141 name: "null_on_division_by_zero",
142 description: "Return NULL instead of throwing an error when dividing by zero.",
143 input_type: "BOOLEAN",
144 scope: GLOBAL,
145 aliases: &[],
146 },
147 SettingEntry {
148 name: "order_by_non_integer_literal",
149 description: "Allow ordering by non-integer literals - ordering by such literals has no effect.",
150 input_type: "BOOLEAN",
151 scope: GLOBAL,
152 aliases: &[],
153 },
154 SettingEntry {
155 name: "preserve_identifier_case",
156 description: "How to fold non-quoted identifiers: 'preserve_case' keeps the case as written, 'lowercase' lowercases them, 'uppercase' uppercases them",
157 input_type: "VARCHAR",
158 scope: GLOBAL,
159 aliases: &[],
160 },
161 SettingEntry {
162 name: "regex_match_operator_semantics",
163 description: "Configures whether regex match operators use partial or full string matching",
164 input_type: "VARCHAR",
165 scope: GLOBAL,
166 aliases: &[],
167 },
168 SettingEntry {
169 name: "show_behavior",
170 description: "How SHOW resolves a bare identifier: 'auto' (describe a table if one exists, else a setting; deprecated), 'table' (always a table), or 'setting' (always a setting)",
171 input_type: "VARCHAR",
172 scope: GLOBAL,
173 aliases: &[],
174 },
175 SettingEntry {
176 name: "threads",
177 description: "The number of total threads used by the system.",
178 input_type: "BIGINT",
179 scope: GLOBAL,
180 aliases: &["worker_threads"],
181 },
182 SettingEntry {
183 name: "warnings_as_errors",
184 description: "Escalate all warnings to errors.",
185 input_type: "BOOLEAN",
186 scope: GLOBAL,
187 aliases: &[],
188 },
189 SettingEntry {
190 name: "worker_threads",
191 description: "The number of total threads used by the system.",
192 input_type: "BIGINT",
193 scope: GLOBAL,
194 aliases: &[],
195 },
196];
197
198#[must_use]
200pub fn setting_fields() -> Vec<Field> {
201 vec![
202 Field::new("name", LogicalType::Varchar),
203 Field::new("value", LogicalType::Varchar),
204 Field::new("description", LogicalType::Varchar),
205 Field::new("input_type", LogicalType::Varchar),
206 Field::new("scope", LogicalType::Varchar),
207 Field::new("aliases", LogicalType::list(LogicalType::Varchar)),
208 Field::new("typed_value", LogicalType::Varchar),
209 ]
210}
211
212#[must_use]
218pub fn setting_named(name: &str) -> Option<&'static SettingEntry> {
219 SETTINGS.iter().find(|entry| entry.name.eq_ignore_ascii_case(name))
220}
221
222#[must_use]
231pub fn unknown_setting(name: &str) -> String {
232 let known: Vec<String> = SETTINGS.iter().map(|entry| format!("\"{}\"", entry.name)).collect();
233 format!("unrecognized configuration parameter \"{name}\"\n\nDid you mean: {}", known.join(", "))
234}
235
236#[cfg(test)]
237mod tests {
238 use super::{GLOBAL, SETTINGS, setting_fields, setting_named, unknown_setting};
239
240 #[test]
241 fn the_table_is_the_shape_the_pin_returns() {
242 assert_eq!(SETTINGS.len(), 20, "eighteen settings and two of them have a second spelling");
243 assert_eq!(setting_fields().len(), 7);
244 }
245
246 #[test]
247 fn the_names_are_sorted_because_the_pin_returns_them_that_way() {
248 let names: Vec<&str> = SETTINGS.iter().map(|entry| entry.name).collect();
249 let mut sorted = names.clone();
250 sorted.sort_unstable();
251 assert_eq!(names, sorted);
252 }
253
254 #[test]
257 fn an_alias_is_a_row_of_its_own_and_the_list_sits_on_the_other_one() {
258 let memory = setting_named("max_memory").expect("a setting");
259 assert_eq!(memory.aliases, ["memory_limit"]);
260 assert_eq!(setting_named("memory_limit").expect("a setting").aliases, [] as [&str; 0]);
261 let threads = setting_named("threads").expect("a setting");
262 assert_eq!(threads.aliases, ["worker_threads"]);
263 assert_eq!(setting_named("worker_threads").expect("a setting").aliases, [] as [&str; 0]);
264 assert_eq!(
266 memory.description,
267 setting_named("memory_limit").expect("a setting").description
268 );
269 assert_eq!(
270 threads.input_type,
271 setting_named("worker_threads").expect("a setting").input_type
272 );
273 }
274
275 #[test]
276 fn nothing_here_is_per_connection_yet_and_the_table_says_so() {
277 for entry in SETTINGS {
278 assert_eq!(entry.scope, GLOBAL, "{}", entry.name);
279 }
280 assert_eq!(setting_named("nothing_called_this"), None);
281 }
282
283 #[test]
285 fn a_setting_is_found_whichever_way_the_name_is_cased() {
286 assert_eq!(setting_named("THREADS").expect("a setting").name, "threads");
287 assert_eq!(setting_named("Memory_Limit").expect("a setting").name, "memory_limit");
288 }
289
290 #[test]
292 fn an_unknown_setting_is_named_and_then_the_known_ones_are_listed() {
293 let message = unknown_setting("nope");
294 assert!(
295 message.starts_with("unrecognized configuration parameter \"nope\"\n\nDid you mean: ")
296 );
297 for entry in SETTINGS {
298 assert!(message.contains(&format!("\"{}\"", entry.name)), "{message}");
299 }
300 }
301}