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