1use std::collections::{HashMap, HashSet};
13use std::hash::Hash;
14
15pub const DATE_UNITS: &[&str] = &["day", "week", "month", "quarter", "year", "year_month"];
17
18pub fn seq_get<T>(seq: &[T], index: isize) -> Option<&T> {
33 let len = seq.len() as isize;
34 if len == 0 {
35 return None;
36 }
37
38 let actual_index = if index < 0 { len + index } else { index };
39
40 if actual_index < 0 || actual_index >= len {
41 None
42 } else {
43 seq.get(actual_index as usize)
44 }
45}
46
47pub trait EnsureList {
51 type Item;
52 fn ensure_list(self) -> Vec<Self::Item>;
53}
54
55impl<T> EnsureList for Vec<T> {
56 type Item = T;
57 fn ensure_list(self) -> Vec<Self::Item> {
58 self
59 }
60}
61
62impl<T> EnsureList for Option<T> {
63 type Item = T;
64 fn ensure_list(self) -> Vec<Self::Item> {
65 match self {
66 Some(v) => vec![v],
67 None => vec![],
68 }
69 }
70}
71
72pub fn ensure_list<T>(value: T) -> Vec<T> {
74 vec![value]
75}
76
77pub fn ensure_list_option<T>(value: Option<T>) -> Vec<T> {
79 match value {
80 Some(v) => vec![v],
81 None => vec![],
82 }
83}
84
85pub fn csv(args: &[&str], sep: &str) -> String {
96 args.iter()
97 .filter(|s| !s.is_empty())
98 .copied()
99 .collect::<Vec<_>>()
100 .join(sep)
101}
102
103pub fn csv_default(args: &[&str]) -> String {
105 csv(args, ", ")
106}
107
108pub fn while_changing<T, F>(mut value: T, func: F) -> T
127where
128 T: Clone + PartialEq,
129 F: Fn(T) -> T,
130{
131 loop {
132 let new_value = func(value.clone());
133 if new_value == value {
134 return new_value;
135 }
136 value = new_value;
137 }
138}
139
140pub fn while_changing_hash<T, F, H>(mut value: T, func: F, hasher: H) -> T
144where
145 F: Fn(T) -> T,
146 H: Fn(&T) -> u64,
147{
148 loop {
149 let start_hash = hasher(&value);
150 value = func(value);
151 let end_hash = hasher(&value);
152 if start_hash == end_hash {
153 return value;
154 }
155 }
156}
157
158pub fn tsort<T>(mut dag: HashMap<T, HashSet<T>>) -> Result<Vec<T>, TsortError>
183where
184 T: Clone + Eq + Hash + Ord,
185{
186 let mut result = Vec::new();
187
188 let all_deps: Vec<T> = dag.values().flat_map(|deps| deps.iter().cloned()).collect();
190
191 for dep in all_deps {
192 dag.entry(dep).or_insert_with(HashSet::new);
193 }
194
195 while !dag.is_empty() {
196 let mut current: Vec<T> = dag
198 .iter()
199 .filter(|(_, deps)| deps.is_empty())
200 .map(|(node, _)| node.clone())
201 .collect();
202
203 if current.is_empty() {
204 return Err(TsortError::CycleDetected);
205 }
206
207 current.sort();
209
210 for node in ¤t {
212 dag.remove(node);
213 }
214
215 let current_set: HashSet<_> = current.iter().cloned().collect();
217 for deps in dag.values_mut() {
218 *deps = deps.difference(¤t_set).cloned().collect();
219 }
220
221 result.extend(current);
222 }
223
224 Ok(result)
225}
226
227#[derive(Debug, Clone, PartialEq, Eq)]
229pub enum TsortError {
230 CycleDetected,
231}
232
233impl std::fmt::Display for TsortError {
234 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235 match self {
236 TsortError::CycleDetected => write!(f, "Cycle detected in DAG"),
237 }
238 }
239}
240
241impl std::error::Error for TsortError {}
242
243pub fn find_new_name(taken: &HashSet<String>, base: &str) -> String {
263 if !taken.contains(base) {
264 return base.to_string();
265 }
266
267 let mut i = 2;
268 loop {
269 let new_name = format!("{}_{}", base, i);
270 if !taken.contains(&new_name) {
271 return new_name;
272 }
273 i += 1;
274 }
275}
276
277pub fn name_sequence(prefix: &str) -> impl FnMut() -> String {
292 let prefix = prefix.to_string();
293 let mut counter = 0usize;
294 move || {
295 let name = format!("{}{}", prefix, counter);
296 counter += 1;
297 name
298 }
299}
300
301pub fn is_int(text: &str) -> bool {
314 text.parse::<i64>().is_ok()
315}
316
317pub fn is_float(text: &str) -> bool {
330 text.parse::<f64>().is_ok()
331}
332
333pub fn is_iso_date(text: &str) -> bool {
345 if text.len() != 10 {
347 return false;
348 }
349 let parts: Vec<&str> = text.split('-').collect();
350 if parts.len() != 3 {
351 return false;
352 }
353 if parts[0].len() != 4 || parts[1].len() != 2 || parts[2].len() != 2 {
354 return false;
355 }
356
357 let year: u32 = match parts[0].parse() {
358 Ok(y) => y,
359 Err(_) => return false,
360 };
361 let month: u32 = match parts[1].parse() {
362 Ok(m) => m,
363 Err(_) => return false,
364 };
365 let day: u32 = match parts[2].parse() {
366 Ok(d) => d,
367 Err(_) => return false,
368 };
369
370 if month < 1 || month > 12 {
371 return false;
372 }
373 if day < 1 || day > 31 {
374 return false;
375 }
376
377 let days_in_month = match month {
379 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
380 4 | 6 | 9 | 11 => 30,
381 2 => {
382 if (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) {
384 29
385 } else {
386 28
387 }
388 }
389 _ => return false,
390 };
391
392 day <= days_in_month
393}
394
395pub fn is_iso_datetime(text: &str) -> bool {
403 if text.len() < 10 {
405 return false;
406 }
407
408 if !is_iso_date(&text[..10]) {
410 return false;
411 }
412
413 if text.len() > 10 {
415 let sep = text.chars().nth(10).expect("length checked above");
417 if sep != 'T' && sep != ' ' {
418 return false;
419 }
420
421 let time_str = &text[11..];
423
424 let time_end = time_str
426 .find('+')
427 .or_else(|| time_str.rfind('-'))
428 .or_else(|| time_str.find('Z'))
429 .unwrap_or(time_str.len());
430
431 let time_without_tz = &time_str[..time_end];
432
433 let (time_part, _frac_part) = match time_without_tz.find('.') {
435 Some(idx) => (&time_without_tz[..idx], Some(&time_without_tz[idx + 1..])),
436 None => (time_without_tz, None),
437 };
438
439 if time_part.len() < 8 {
441 if time_part.len() != 5 {
443 return false;
444 }
445 }
446
447 let parts: Vec<&str> = time_part.split(':').collect();
448 if parts.len() < 2 || parts.len() > 3 {
449 return false;
450 }
451
452 let hour: u32 = match parts[0].parse() {
453 Ok(h) => h,
454 Err(_) => return false,
455 };
456 let minute: u32 = match parts[1].parse() {
457 Ok(m) => m,
458 Err(_) => return false,
459 };
460
461 if hour > 23 || minute > 59 {
462 return false;
463 }
464
465 if parts.len() == 3 {
466 let second: u32 = match parts[2].parse() {
467 Ok(s) => s,
468 Err(_) => return false,
469 };
470 if second > 59 {
471 return false;
472 }
473 }
474 }
475
476 true
477}
478
479pub fn camel_to_snake_case(name: &str) -> String {
490 let mut result = String::with_capacity(name.len() + 4);
491 for (i, ch) in name.chars().enumerate() {
492 if ch.is_uppercase() && i > 0 {
493 result.push('_');
494 }
495 result.push(ch.to_ascii_uppercase());
496 }
497 result
498}
499
500pub fn snake_to_camel_case(name: &str) -> String {
511 let mut result = String::with_capacity(name.len());
512 let mut capitalize_next = true;
513
514 for ch in name.chars() {
515 if ch == '_' {
516 capitalize_next = true;
517 } else if capitalize_next {
518 result.push(ch.to_ascii_uppercase());
519 capitalize_next = false;
520 } else {
521 result.push(ch.to_ascii_lowercase());
522 }
523 }
524 result
525}
526
527pub fn dict_depth<K, V>(d: &HashMap<K, V>) -> usize
543where
544 K: std::hash::Hash + Eq,
545{
546 if d.is_empty() {
549 1
550 } else {
551 1
552 }
553}
554
555pub fn first<I, T>(mut iter: I) -> Option<T>
568where
569 I: Iterator<Item = T>,
570{
571 iter.next()
572}
573
574pub fn split_num_words(
597 value: &str,
598 sep: &str,
599 min_num_words: usize,
600 fill_from_start: bool,
601) -> Vec<Option<String>> {
602 let words: Vec<String> = value.split(sep).map(|s| s.to_string()).collect();
603 let num_words = words.len();
604
605 if num_words >= min_num_words {
606 return words.into_iter().map(Some).collect();
607 }
608
609 let padding = min_num_words - num_words;
610 let mut result = Vec::with_capacity(min_num_words);
611
612 if fill_from_start {
613 result.extend(std::iter::repeat(None).take(padding));
614 result.extend(words.into_iter().map(Some));
615 } else {
616 result.extend(words.into_iter().map(Some));
617 result.extend(std::iter::repeat(None).take(padding));
618 }
619
620 result
621}
622
623pub fn flatten<T: Clone>(values: &[Vec<T>]) -> Vec<T> {
628 values.iter().flat_map(|v| v.iter().cloned()).collect()
629}
630
631pub fn merge_ranges<T: Ord + Copy>(mut ranges: Vec<(T, T)>) -> Vec<(T, T)> {
648 if ranges.is_empty() {
649 return vec![];
650 }
651
652 ranges.sort_by(|a, b| a.0.cmp(&b.0));
653
654 let mut merged = vec![ranges[0]];
655
656 for (start, end) in ranges.into_iter().skip(1) {
657 let last = merged
658 .last_mut()
659 .expect("merged initialized with at least one element");
660 if start <= last.1 {
661 last.1 = std::cmp::max(last.1, end);
662 } else {
663 merged.push((start, end));
664 }
665 }
666
667 merged
668}
669
670pub fn is_date_unit(unit: &str) -> bool {
672 DATE_UNITS.contains(&unit.to_lowercase().as_str())
673}
674
675pub fn apply_index_offset(expression: &str, offset: i64) -> Option<String> {
701 if offset == 0 {
702 return Some(expression.to_string());
703 }
704
705 if let Ok(value) = expression.parse::<i64>() {
707 return Some((value + offset).to_string());
708 }
709
710 None
712}
713
714#[derive(Debug, Clone)]
735pub struct SingleValuedMapping<K, V>
736where
737 K: Eq + Hash,
738{
739 keys: HashSet<K>,
740 value: V,
741}
742
743impl<K, V> SingleValuedMapping<K, V>
744where
745 K: Eq + Hash,
746{
747 pub fn new(keys: HashSet<K>, value: V) -> Self {
749 Self { keys, value }
750 }
751
752 pub fn from_iter<I: IntoIterator<Item = K>>(keys: I, value: V) -> Self {
754 Self {
755 keys: keys.into_iter().collect(),
756 value,
757 }
758 }
759
760 pub fn get(&self, key: &K) -> Option<&V> {
762 if self.keys.contains(key) {
763 Some(&self.value)
764 } else {
765 None
766 }
767 }
768
769 pub fn contains_key(&self, key: &K) -> bool {
771 self.keys.contains(key)
772 }
773
774 pub fn len(&self) -> usize {
776 self.keys.len()
777 }
778
779 pub fn is_empty(&self) -> bool {
781 self.keys.is_empty()
782 }
783
784 pub fn keys(&self) -> impl Iterator<Item = &K> {
786 self.keys.iter()
787 }
788
789 pub fn value(&self) -> &V {
791 &self.value
792 }
793
794 pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
796 self.keys.iter().map(move |k| (k, &self.value))
797 }
798}
799
800pub fn to_bool(value: &str) -> Option<bool> {
814 let lower = value.to_lowercase();
815 match lower.as_str() {
816 "true" | "1" => Some(true),
817 "false" | "0" => Some(false),
818 _ => None,
819 }
820}
821
822#[cfg(test)]
823mod tests {
824 use super::*;
825
826 #[test]
827 fn test_seq_get() {
828 let v = vec![1, 2, 3, 4, 5];
829 assert_eq!(seq_get(&v, 0), Some(&1));
830 assert_eq!(seq_get(&v, 4), Some(&5));
831 assert_eq!(seq_get(&v, 5), None);
832 assert_eq!(seq_get(&v, -1), Some(&5));
833 assert_eq!(seq_get(&v, -5), Some(&1));
834 assert_eq!(seq_get(&v, -6), None);
835
836 let empty: Vec<i32> = vec![];
837 assert_eq!(seq_get(&empty, 0), None);
838 assert_eq!(seq_get(&empty, -1), None);
839 }
840
841 #[test]
842 fn test_csv() {
843 assert_eq!(csv(&["a", "b", "c"], ", "), "a, b, c");
844 assert_eq!(csv(&["a", "", "c"], ", "), "a, c");
845 assert_eq!(csv(&["", "", ""], ", "), "");
846 assert_eq!(csv(&["a"], ", "), "a");
847 }
848
849 #[test]
850 fn test_while_changing() {
851 let result = while_changing(16, |n| if n % 2 == 0 { n / 2 } else { n });
853 assert_eq!(result, 1);
854
855 let result = while_changing(5, |n| if n % 2 == 0 { n / 2 } else { n });
857 assert_eq!(result, 5);
858 }
859
860 #[test]
861 fn test_tsort() {
862 let mut dag = HashMap::new();
863 dag.insert("a", HashSet::from(["b", "c"]));
864 dag.insert("b", HashSet::from(["c"]));
865 dag.insert("c", HashSet::new());
866
867 let sorted = tsort(dag).unwrap();
868 assert_eq!(sorted, vec!["c", "b", "a"]);
869 }
870
871 #[test]
872 fn test_tsort_cycle() {
873 let mut dag = HashMap::new();
874 dag.insert("a", HashSet::from(["b"]));
875 dag.insert("b", HashSet::from(["a"]));
876
877 let result = tsort(dag);
878 assert!(result.is_err());
879 }
880
881 #[test]
882 fn test_find_new_name() {
883 let taken = HashSet::from(["col".to_string(), "col_2".to_string()]);
884 assert_eq!(find_new_name(&taken, "col"), "col_3");
885 assert_eq!(find_new_name(&taken, "other"), "other");
886
887 let empty = HashSet::new();
888 assert_eq!(find_new_name(&empty, "col"), "col");
889 }
890
891 #[test]
892 fn test_name_sequence() {
893 let mut gen = name_sequence("a");
894 assert_eq!(gen(), "a0");
895 assert_eq!(gen(), "a1");
896 assert_eq!(gen(), "a2");
897 }
898
899 #[test]
900 fn test_is_int() {
901 assert!(is_int("123"));
902 assert!(is_int("-456"));
903 assert!(is_int("0"));
904 assert!(!is_int("12.34"));
905 assert!(!is_int("abc"));
906 assert!(!is_int(""));
907 }
908
909 #[test]
910 fn test_is_float() {
911 assert!(is_float("12.34"));
912 assert!(is_float("123"));
913 assert!(is_float("-1.5e10"));
914 assert!(is_float("0.0"));
915 assert!(!is_float("abc"));
916 assert!(!is_float(""));
917 }
918
919 #[test]
920 fn test_is_iso_date() {
921 assert!(is_iso_date("2023-01-15"));
922 assert!(is_iso_date("2024-02-29")); assert!(!is_iso_date("2023-02-29")); assert!(!is_iso_date("01-15-2023"));
925 assert!(!is_iso_date("2023-13-01")); assert!(!is_iso_date("2023-01-32")); assert!(!is_iso_date("not a date"));
928 }
929
930 #[test]
931 fn test_is_iso_datetime() {
932 assert!(is_iso_datetime("2023-01-15T10:30:00"));
933 assert!(is_iso_datetime("2023-01-15 10:30:00"));
934 assert!(is_iso_datetime("2023-01-15T10:30:00.123456"));
935 assert!(is_iso_datetime("2023-01-15T10:30:00+00:00"));
936 assert!(is_iso_datetime("2023-01-15"));
937 assert!(!is_iso_datetime("not a datetime"));
938 assert!(!is_iso_datetime("2023-01-15X10:30:00")); }
940
941 #[test]
942 fn test_camel_to_snake_case() {
943 assert_eq!(camel_to_snake_case("camelCase"), "CAMEL_CASE");
944 assert_eq!(camel_to_snake_case("PascalCase"), "PASCAL_CASE");
945 assert_eq!(camel_to_snake_case("simple"), "SIMPLE");
946 }
947
948 #[test]
949 fn test_snake_to_camel_case() {
950 assert_eq!(snake_to_camel_case("snake_case"), "SnakeCase");
951 assert_eq!(snake_to_camel_case("my_http_server"), "MyHttpServer");
952 assert_eq!(snake_to_camel_case("simple"), "Simple");
953 }
954
955 #[test]
956 fn test_split_num_words() {
957 assert_eq!(
958 split_num_words("db.table", ".", 3, true),
959 vec![None, Some("db".to_string()), Some("table".to_string())]
960 );
961 assert_eq!(
962 split_num_words("db.table", ".", 3, false),
963 vec![Some("db".to_string()), Some("table".to_string()), None]
964 );
965 assert_eq!(
966 split_num_words("catalog.db.table", ".", 3, true),
967 vec![
968 Some("catalog".to_string()),
969 Some("db".to_string()),
970 Some("table".to_string())
971 ]
972 );
973 assert_eq!(
974 split_num_words("db.table", ".", 1, true),
975 vec![Some("db".to_string()), Some("table".to_string())]
976 );
977 }
978
979 #[test]
980 fn test_merge_ranges() {
981 assert_eq!(merge_ranges(vec![(1, 3), (2, 6)]), vec![(1, 6)]);
982 assert_eq!(
983 merge_ranges(vec![(1, 3), (2, 6), (8, 10)]),
984 vec![(1, 6), (8, 10)]
985 );
986 assert_eq!(merge_ranges(vec![(1, 5), (2, 3)]), vec![(1, 5)]);
987 assert_eq!(merge_ranges::<i32>(vec![]), vec![]);
988 }
989
990 #[test]
991 fn test_is_date_unit() {
992 assert!(is_date_unit("day"));
993 assert!(is_date_unit("MONTH"));
994 assert!(is_date_unit("Year"));
995 assert!(!is_date_unit("hour"));
996 assert!(!is_date_unit("minute"));
997 }
998
999 #[test]
1000 fn test_apply_index_offset() {
1001 assert_eq!(apply_index_offset("0", 1), Some("1".to_string()));
1003 assert_eq!(apply_index_offset("5", 1), Some("6".to_string()));
1004 assert_eq!(apply_index_offset("10", -1), Some("9".to_string()));
1005
1006 assert_eq!(apply_index_offset("5", 0), Some("5".to_string()));
1008
1009 assert_eq!(apply_index_offset("-1", 1), Some("0".to_string()));
1011
1012 assert_eq!(apply_index_offset("col", 1), None);
1014 assert_eq!(apply_index_offset("1.5", 1), None);
1015 assert_eq!(apply_index_offset("abc", 1), None);
1016 }
1017
1018 #[test]
1019 fn test_single_valued_mapping() {
1020 let columns = HashSet::from(["id".to_string(), "name".to_string(), "email".to_string()]);
1021 let mapping = SingleValuedMapping::new(columns, "users".to_string());
1022
1023 assert_eq!(mapping.get(&"id".to_string()), Some(&"users".to_string()));
1025 assert_eq!(mapping.get(&"name".to_string()), Some(&"users".to_string()));
1026 assert_eq!(
1027 mapping.get(&"email".to_string()),
1028 Some(&"users".to_string())
1029 );
1030
1031 assert_eq!(mapping.get(&"unknown".to_string()), None);
1033
1034 assert_eq!(mapping.len(), 3);
1036 assert!(!mapping.is_empty());
1037
1038 assert!(mapping.contains_key(&"id".to_string()));
1040 assert!(!mapping.contains_key(&"unknown".to_string()));
1041
1042 assert_eq!(mapping.value(), &"users".to_string());
1044 }
1045
1046 #[test]
1047 fn test_single_valued_mapping_from_iter() {
1048 let mapping = SingleValuedMapping::from_iter(vec!["a".to_string(), "b".to_string()], 42);
1049
1050 assert_eq!(mapping.get(&"a".to_string()), Some(&42));
1051 assert_eq!(mapping.get(&"b".to_string()), Some(&42));
1052 assert_eq!(mapping.len(), 2);
1053 }
1054
1055 #[test]
1056 fn test_to_bool() {
1057 assert_eq!(to_bool("true"), Some(true));
1058 assert_eq!(to_bool("TRUE"), Some(true));
1059 assert_eq!(to_bool("True"), Some(true));
1060 assert_eq!(to_bool("1"), Some(true));
1061
1062 assert_eq!(to_bool("false"), Some(false));
1063 assert_eq!(to_bool("FALSE"), Some(false));
1064 assert_eq!(to_bool("False"), Some(false));
1065 assert_eq!(to_bool("0"), Some(false));
1066
1067 assert_eq!(to_bool("maybe"), None);
1068 assert_eq!(to_bool("yes"), None);
1069 assert_eq!(to_bool("no"), None);
1070 }
1071}