robin_sparkless_core/
runtime_config.rs1use std::collections::HashMap;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
7pub enum PysparkCompat {
8 #[default]
10 V3_5,
11 V4_0,
13}
14
15impl PysparkCompat {
16 pub fn parse(s: &str) -> Option<Self> {
17 match s.trim() {
18 "3.5" | "3" | "3.x" => Some(Self::V3_5),
19 "4.0" | "4" | "4.x" => Some(Self::V4_0),
20 _ => None,
21 }
22 }
23
24 pub fn as_config_str(self) -> &'static str {
25 match self {
26 Self::V3_5 => "3.5",
27 Self::V4_0 => "4.0",
28 }
29 }
30}
31
32#[derive(Clone, Debug, PartialEq, Eq)]
34pub struct SessionRuntimeConfig {
35 pub pyspark_compat: PysparkCompat,
36 pub ansi_enabled: bool,
37 pub disable_map_key_normalization: bool,
38 pub infer_map_type_from_first_pair: bool,
39 pub infer_array_type_from_first_element: bool,
40 pub legacy_postgres_datetime_mapping: bool,
42 pub legacy_mysql_datetime_mapping: bool,
44 pub legacy_oracle_timestamp_mapping: bool,
46 pub legacy_mssqlserver_datetime_mapping: bool,
48 pub legacy_db2_datetime_mapping: bool,
50}
51
52impl Default for SessionRuntimeConfig {
53 fn default() -> Self {
54 Self::for_compat(PysparkCompat::V3_5)
55 }
56}
57
58fn parse_bool(s: &str) -> bool {
59 matches!(s.trim().to_ascii_lowercase().as_str(), "true" | "1" | "yes")
60}
61
62fn bool_str(v: bool) -> String {
63 if v {
64 "true".to_string()
65 } else {
66 "false".to_string()
67 }
68}
69
70impl SessionRuntimeConfig {
71 pub fn for_compat(compat: PysparkCompat) -> Self {
73 match compat {
74 PysparkCompat::V3_5 => Self {
75 pyspark_compat: PysparkCompat::V3_5,
76 ansi_enabled: false,
77 disable_map_key_normalization: true,
78 infer_map_type_from_first_pair: true,
79 infer_array_type_from_first_element: true,
80 legacy_postgres_datetime_mapping: true,
81 legacy_mysql_datetime_mapping: true,
82 legacy_oracle_timestamp_mapping: true,
83 legacy_mssqlserver_datetime_mapping: true,
84 legacy_db2_datetime_mapping: true,
85 },
86 PysparkCompat::V4_0 => Self {
87 pyspark_compat: PysparkCompat::V4_0,
88 ansi_enabled: true,
89 disable_map_key_normalization: false,
90 infer_map_type_from_first_pair: false,
91 infer_array_type_from_first_element: false,
92 legacy_postgres_datetime_mapping: false,
93 legacy_mysql_datetime_mapping: false,
94 legacy_oracle_timestamp_mapping: false,
95 legacy_mssqlserver_datetime_mapping: false,
96 legacy_db2_datetime_mapping: false,
97 },
98 }
99 }
100
101 pub fn from_session_map(map: &HashMap<String, String>) -> Self {
103 let mut cfg = map
104 .get("sparkless.pyspark.compat")
105 .and_then(|v| PysparkCompat::parse(v).map(Self::for_compat))
106 .unwrap_or_default();
107
108 if let Some(v) = map.get("spark.sql.ansi.enabled") {
109 cfg.ansi_enabled = parse_bool(v);
110 }
111 if let Some(v) = map.get("spark.sql.legacy.disableMapKeyNormalization") {
112 cfg.disable_map_key_normalization = parse_bool(v);
113 }
114 if let Some(v) = map.get("spark.sql.pyspark.legacy.inferMapTypeFromFirstPair.enabled") {
115 cfg.infer_map_type_from_first_pair = parse_bool(v);
116 }
117 if let Some(v) = map.get("spark.sql.pyspark.legacy.inferArrayTypeFromFirstElement.enabled")
118 {
119 cfg.infer_array_type_from_first_element = parse_bool(v);
120 }
121 if let Some(v) = map.get("spark.sql.legacy.postgres.datetimeMapping.enabled") {
122 cfg.legacy_postgres_datetime_mapping = parse_bool(v);
123 }
124 if let Some(v) = map.get("spark.sql.legacy.mysql.datetimeMapping.enabled") {
125 cfg.legacy_mysql_datetime_mapping = parse_bool(v);
126 }
127 if let Some(v) = map.get("spark.sql.legacy.oracle.timestampMapping.enabled") {
128 cfg.legacy_oracle_timestamp_mapping = parse_bool(v);
129 }
130 if let Some(v) = map.get("spark.sql.legacy.mssqlserver.datetimeMapping.enabled") {
131 cfg.legacy_mssqlserver_datetime_mapping = parse_bool(v);
132 }
133 if let Some(v) = map.get("spark.sql.legacy.db2.datetimeMapping.enabled") {
134 cfg.legacy_db2_datetime_mapping = parse_bool(v);
135 }
136 cfg
137 }
138
139 pub fn apply_profile_to_map(&self, map: &mut HashMap<String, String>) {
141 map.insert(
142 "sparkless.pyspark.compat".to_string(),
143 self.pyspark_compat.as_config_str().to_string(),
144 );
145 map.insert(
146 "spark.sql.ansi.enabled".to_string(),
147 bool_str(self.ansi_enabled),
148 );
149 map.insert(
150 "spark.sql.legacy.disableMapKeyNormalization".to_string(),
151 bool_str(self.disable_map_key_normalization),
152 );
153 map.insert(
154 "spark.sql.pyspark.legacy.inferMapTypeFromFirstPair.enabled".to_string(),
155 bool_str(self.infer_map_type_from_first_pair),
156 );
157 map.insert(
158 "spark.sql.pyspark.legacy.inferArrayTypeFromFirstElement.enabled".to_string(),
159 bool_str(self.infer_array_type_from_first_element),
160 );
161 map.insert(
162 "spark.sql.legacy.postgres.datetimeMapping.enabled".to_string(),
163 bool_str(self.legacy_postgres_datetime_mapping),
164 );
165 map.insert(
166 "spark.sql.legacy.mysql.datetimeMapping.enabled".to_string(),
167 bool_str(self.legacy_mysql_datetime_mapping),
168 );
169 map.insert(
170 "spark.sql.legacy.oracle.timestampMapping.enabled".to_string(),
171 bool_str(self.legacy_oracle_timestamp_mapping),
172 );
173 map.insert(
174 "spark.sql.legacy.mssqlserver.datetimeMapping.enabled".to_string(),
175 bool_str(self.legacy_mssqlserver_datetime_mapping),
176 );
177 map.insert(
178 "spark.sql.legacy.db2.datetimeMapping.enabled".to_string(),
179 bool_str(self.legacy_db2_datetime_mapping),
180 );
181 }
182
183 pub fn set_compat_profile(map: &mut HashMap<String, String>, compat: PysparkCompat) {
185 let cfg = Self::for_compat(compat);
186 cfg.apply_profile_to_map(map);
187 }
188
189 pub fn compat_from_env() -> Option<PysparkCompat> {
191 std::env::var("SPARKLESS_PYSPARK_COMPAT")
192 .ok()
193 .and_then(|v| PysparkCompat::parse(&v))
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[test]
202 fn default_is_v3_5_ansi_off() {
203 let c = SessionRuntimeConfig::default();
204 assert_eq!(c.pyspark_compat, PysparkCompat::V3_5);
205 assert!(!c.ansi_enabled);
206 assert!(c.disable_map_key_normalization);
207 }
208
209 #[test]
210 fn v4_profile_ansi_on() {
211 let c = SessionRuntimeConfig::for_compat(PysparkCompat::V4_0);
212 assert!(c.ansi_enabled);
213 assert!(!c.disable_map_key_normalization);
214 }
215
216 #[test]
217 fn from_session_map_compat_4() {
218 let mut m = HashMap::new();
219 m.insert("sparkless.pyspark.compat".to_string(), "4.0".to_string());
220 let c = SessionRuntimeConfig::from_session_map(&m);
221 assert_eq!(c.pyspark_compat, PysparkCompat::V4_0);
222 assert!(c.ansi_enabled);
223 }
224
225 #[test]
226 fn ansi_override_after_profile() {
227 let mut m = HashMap::new();
228 m.insert("sparkless.pyspark.compat".to_string(), "4.0".to_string());
229 m.insert("spark.sql.ansi.enabled".to_string(), "false".to_string());
230 let c = SessionRuntimeConfig::from_session_map(&m);
231 assert!(!c.ansi_enabled);
232 }
233
234 #[test]
235 fn v4_jdbc_legacy_flags_off_by_default() {
236 let c = SessionRuntimeConfig::for_compat(PysparkCompat::V4_0);
237 assert!(!c.legacy_postgres_datetime_mapping);
238 assert!(!c.legacy_mysql_datetime_mapping);
239 assert!(!c.legacy_mssqlserver_datetime_mapping);
240 }
241
242 #[test]
243 fn v3_jdbc_legacy_flags_on_by_default() {
244 let c = SessionRuntimeConfig::for_compat(PysparkCompat::V3_5);
245 assert!(c.legacy_postgres_datetime_mapping);
246 assert!(c.legacy_mysql_datetime_mapping);
247 }
248}