1use std::collections::HashMap;
40use std::fmt;
41
42use rich::{Console, ConsoleOptions, Justify, Renderable, Segment, Table};
43
44use crate::{DataType, Field, Rows, Schema, Value};
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
48pub enum InferredType {
49 Null,
51 Boolean,
53 Integer,
55 Float,
57 Date,
59 Timestamp,
61 Text,
63}
64
65impl InferredType {
66 pub fn name(self) -> &'static str {
68 match self {
69 InferredType::Null => "null",
70 InferredType::Boolean => "boolean",
71 InferredType::Integer => "integer",
72 InferredType::Float => "float",
73 InferredType::Date => "date",
74 InferredType::Timestamp => "timestamp",
75 InferredType::Text => "text",
76 }
77 }
78
79 pub fn data_type(self) -> DataType {
82 match self {
83 InferredType::Null => DataType::Unknown,
84 InferredType::Boolean => DataType::Boolean,
85 InferredType::Integer => DataType::Integer,
86 InferredType::Float => DataType::Float,
87 InferredType::Date => DataType::Date,
88 InferredType::Timestamp => DataType::timestamp(),
89 InferredType::Text => DataType::String,
90 }
91 }
92}
93
94impl fmt::Display for InferredType {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 f.write_str(self.name())
97 }
98}
99
100#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
104pub struct Evidence {
105 pub cells: usize,
107 pub nulls: usize,
109 pub booleans: usize,
111 pub integers: usize,
113 pub floats: usize,
115 pub dates: usize,
117 pub timestamps: usize,
119}
120
121impl Evidence {
122 pub fn values(&self) -> usize {
124 self.cells - self.nulls
125 }
126
127 pub fn parsed(&self, data_type: InferredType) -> usize {
129 match data_type {
130 InferredType::Null => self.nulls,
131 InferredType::Boolean => self.booleans,
132 InferredType::Integer => self.integers,
133 InferredType::Float => self.floats,
134 InferredType::Date => self.dates,
135 InferredType::Timestamp => self.timestamps,
136 InferredType::Text => self.values(),
137 }
138 }
139
140 pub fn best(&self) -> InferredType {
142 let values = self.values();
143 if values == 0 {
144 InferredType::Null
145 } else if self.booleans == values {
146 InferredType::Boolean
147 } else if self.integers == values {
148 InferredType::Integer
149 } else if self.floats == values {
150 InferredType::Float
151 } else if self.dates == values {
152 InferredType::Date
153 } else if self.timestamps == values {
154 InferredType::Timestamp
155 } else {
156 InferredType::Text
157 }
158 }
159
160 pub fn closest(&self) -> Option<(InferredType, usize)> {
163 [
164 (InferredType::Integer, self.integers),
165 (InferredType::Float, self.floats),
166 (InferredType::Boolean, self.booleans),
167 (InferredType::Date, self.dates),
168 (InferredType::Timestamp, self.timestamps),
169 ]
170 .into_iter()
171 .filter(|(_, n)| *n > 0)
172 .fold(
175 None,
176 |best: Option<(InferredType, usize)>, (t, n)| match best {
177 Some((_, m)) if m >= n => best,
178 _ => Some((t, n)),
179 },
180 )
181 }
182}
183
184#[derive(Clone, Debug, PartialEq, Eq)]
186pub struct ColumnInference {
187 name: String,
188 data_type: InferredType,
189 evidence: Evidence,
190 overridden: bool,
191}
192
193impl ColumnInference {
194 pub fn name(&self) -> &str {
196 &self.name
197 }
198
199 pub fn data_type(&self) -> InferredType {
201 self.data_type
202 }
203
204 pub fn evidence(&self) -> &Evidence {
206 &self.evidence
207 }
208
209 pub fn is_overridden(&self) -> bool {
211 self.overridden
212 }
213
214 pub fn summary(&self) -> String {
218 let e = &self.evidence;
219 let mut out = self.data_type.name().to_string();
220 if self.overridden {
221 out.push_str(" (override)");
222 }
223 let values = e.values();
224 let plural = |n: usize| if n == 1 { "value" } else { "values" };
225 match self.data_type {
226 InferredType::Null => out.push_str(&format!(": {} null", e.nulls)),
227 InferredType::Text if !self.overridden => match e.closest() {
228 Some((t, n)) => out.push_str(&format!(
229 ": {n} of {values} {} parse as {t}",
230 plural(values)
231 )),
232 None => out.push_str(&format!(": {values} {}", plural(values))),
233 },
234 t => out.push_str(&format!(": {} of {values} {}", e.parsed(t), plural(values))),
235 }
236 if e.nulls > 0 && self.data_type != InferredType::Null {
237 out.push_str(&format!(", {} null", e.nulls));
238 }
239 out
240 }
241}
242
243pub const DEFAULT_NULL_TOKENS: [&str; 5] = ["", "null", "NULL", "NA", "N/A"];
246
247#[derive(Clone, Debug)]
249pub struct Inferrer {
250 nulls: Vec<String>,
251 overrides: HashMap<String, InferredType>,
252}
253
254impl Default for Inferrer {
255 fn default() -> Self {
256 Inferrer {
257 nulls: DEFAULT_NULL_TOKENS.map(String::from).to_vec(),
258 overrides: HashMap::new(),
259 }
260 }
261}
262
263impl Inferrer {
264 pub fn new() -> Self {
266 Inferrer::default()
267 }
268
269 #[must_use]
271 pub fn null_tokens(mut self, tokens: impl IntoIterator<Item = impl Into<String>>) -> Self {
272 self.nulls = tokens.into_iter().map(Into::into).collect();
273 self
274 }
275
276 #[must_use]
278 pub fn override_type(mut self, column: impl Into<String>, data_type: InferredType) -> Self {
279 self.overrides.insert(column.into(), data_type);
280 self
281 }
282
283 pub fn infer(&self, rows: &Rows) -> Inference {
285 let columns = rows
286 .columns()
287 .iter()
288 .enumerate()
289 .map(|(index, name)| {
290 let mut evidence = Evidence::default();
291 for value in rows.column(index) {
292 self.count(value, &mut evidence);
293 }
294 let (data_type, overridden) = match self.overrides.get(name) {
295 Some(&t) => (t, true),
296 None => (evidence.best(), false),
297 };
298 ColumnInference {
299 name: name.clone(),
300 data_type,
301 evidence,
302 overridden,
303 }
304 })
305 .collect();
306 Inference {
307 columns,
308 nulls: self.nulls.clone(),
309 }
310 }
311
312 fn count(&self, value: &Value, e: &mut Evidence) {
313 e.cells += 1;
314 match value {
315 Value::Null => e.nulls += 1,
316 Value::Int(_) => {
317 e.integers += 1;
318 e.floats += 1;
319 }
320 Value::Float(_) => e.floats += 1,
321 Value::Str(_) | Value::Text(_) => {
322 let text = value.plain();
323 let text = text.trim();
324 if is_null(&self.nulls, text) {
325 e.nulls += 1;
326 return;
327 }
328 if parse_bool(text).is_some() {
329 e.booleans += 1;
330 }
331 if parse_int(text).is_some() {
332 e.integers += 1;
333 }
334 if parse_float(text).is_some() {
335 e.floats += 1;
336 }
337 if is_date(text) {
338 e.dates += 1;
339 e.timestamps += 1;
340 } else if is_timestamp(text) {
341 e.timestamps += 1;
342 }
343 }
344 }
345 }
346}
347
348fn is_null(tokens: &[String], text: &str) -> bool {
349 tokens.iter().any(|t| t == text)
350}
351
352#[derive(Clone, Debug, PartialEq, Eq)]
354pub struct Inference {
355 columns: Vec<ColumnInference>,
356 nulls: Vec<String>,
357}
358
359impl Inference {
360 pub fn columns(&self) -> &[ColumnInference] {
362 &self.columns
363 }
364
365 pub fn schema(&self) -> Schema {
368 Schema::new(self.columns.iter().map(|c| {
369 Field::new(c.name.clone(), c.data_type.data_type()).nullable(c.evidence.nulls > 0)
370 }))
371 }
372
373 pub fn apply(&self, rows: &mut Rows) {
380 let types: Vec<InferredType> = self.columns.iter().map(|c| c.data_type).collect();
381 for row in rows.rows_mut() {
382 for (cell, &data_type) in row.iter_mut().zip(&types) {
383 if data_type == InferredType::Text {
384 continue;
385 }
386 let converted = match &*cell {
387 Value::Str(_) | Value::Text(_) => {
388 let plain = cell.plain();
389 let text = plain.trim();
390 if is_null(&self.nulls, text) {
391 Some(Value::Null)
392 } else {
393 match data_type {
394 InferredType::Integer => parse_int(text).map(Value::Int),
395 InferredType::Float => parse_float(text).map(Value::Float),
396 _ => None,
397 }
398 }
399 }
400 Value::Int(n) if data_type == InferredType::Float => {
401 Some(Value::Float(*n as f64))
402 }
403 _ => None,
404 };
405 if let Some(converted) = converted {
406 *cell = converted;
407 }
408 }
409 }
410 rows.set_schema(Some(self.schema()));
411 }
412}
413
414impl Renderable for Inference {
416 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
417 self.to_table().rich_render(console, options)
418 }
419
420 fn measure(&self, console: &Console, options: &ConsoleOptions) -> rich::measure::Measurement {
421 self.to_table().measure(console, options)
422 }
423}
424
425impl Inference {
426 pub fn to_table(&self) -> Table {
428 let mut table = Table::new();
429 table.add_column("column");
430 table.add_column("type");
431 table.add_column_justify("parsed", Justify::Right);
432 table.add_column_justify("nulls", Justify::Right);
433 for c in &self.columns {
434 let mut kind = c.data_type.name().to_string();
435 if c.overridden {
436 kind.push_str(" (override)");
437 }
438 let parsed = match c.data_type {
439 InferredType::Null => String::new(),
440 InferredType::Text => match c.evidence.closest() {
441 Some((t, n)) if !c.overridden => format!("{n}/{} {t}", c.evidence.values()),
442 _ => format!("{}/{}", c.evidence.values(), c.evidence.values()),
443 },
444 t => format!("{}/{}", c.evidence.parsed(t), c.evidence.values()),
445 };
446 table.add_row_text(vec![
447 rich::Text::new(c.name.clone()),
448 rich::Text::new(kind),
449 rich::Text::new(parsed),
450 rich::Text::new(c.evidence.nulls.to_string()),
451 ]);
452 }
453 table
454 }
455}
456
457pub fn parse_bool(text: &str) -> Option<bool> {
459 if text.eq_ignore_ascii_case("true") {
460 Some(true)
461 } else if text.eq_ignore_ascii_case("false") {
462 Some(false)
463 } else {
464 None
465 }
466}
467
468pub fn parse_int(text: &str) -> Option<i64> {
470 let digits = text.strip_prefix(['+', '-']).unwrap_or(text);
471 if digits.is_empty() || !digits.bytes().all(|b| b.is_ascii_digit()) {
472 return None;
473 }
474 text.parse().ok()
475}
476
477pub fn parse_float(text: &str) -> Option<f64> {
480 let body = text.strip_prefix(['+', '-']).unwrap_or(text);
481 let (mantissa, exponent) = match body.find(['e', 'E']) {
482 Some(at) => (&body[..at], Some(&body[at + 1..])),
483 None => (body, None),
484 };
485 let (whole, fraction) = mantissa.split_once('.').unwrap_or((mantissa, ""));
486 let digits = |s: &str| s.bytes().all(|b| b.is_ascii_digit());
487 if whole.len() + fraction.len() == 0 || !digits(whole) || !digits(fraction) {
488 return None;
489 }
490 if let Some(exponent) = exponent {
491 let exponent = exponent.strip_prefix(['+', '-']).unwrap_or(exponent);
492 if exponent.is_empty() || !digits(exponent) {
493 return None;
494 }
495 }
496 text.parse().ok()
497}
498
499pub fn is_date(text: &str) -> bool {
501 let b = text.as_bytes();
502 if b.len() != 10 || b[4] != b'-' || b[7] != b'-' {
503 return false;
504 }
505 let number = |s: &[u8]| -> Option<u32> {
506 s.iter().try_fold(0u32, |n, &c| {
507 c.is_ascii_digit().then(|| n * 10 + u32::from(c - b'0'))
508 })
509 };
510 let (Some(year), Some(month), Some(day)) = (number(&b[..4]), number(&b[5..7]), number(&b[8..]))
511 else {
512 return false;
513 };
514 let leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
515 let days = match month {
516 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
517 4 | 6 | 9 | 11 => 30,
518 2 if leap => 29,
519 2 => 28,
520 _ => return false,
521 };
522 (1..=days).contains(&day)
523}
524
525pub fn is_timestamp(text: &str) -> bool {
528 if text.len() < 16 || !text.is_char_boundary(10) || !is_date(&text[..10]) {
529 return false;
530 }
531 let rest = &text.as_bytes()[10..];
532 if rest[0] != b'T' && rest[0] != b't' && rest[0] != b' ' {
533 return false;
534 }
535 let mut time = &rest[1..];
536 let two = |s: &[u8], max: u32| -> bool {
537 s.len() >= 2
538 && s[0].is_ascii_digit()
539 && s[1].is_ascii_digit()
540 && u32::from(s[0] - b'0') * 10 + u32::from(s[1] - b'0') <= max
541 };
542 if !(two(time, 23) && time.get(2) == Some(&b':') && two(&time[3..], 59)) {
543 return false;
544 }
545 time = &time[5..];
546 if time.first() == Some(&b':') {
547 if !two(&time[1..], 60) {
548 return false;
549 }
550 time = &time[3..];
551 if time.first() == Some(&b'.') {
552 let digits = time[1..].iter().take_while(|c| c.is_ascii_digit()).count();
553 if digits == 0 {
554 return false;
555 }
556 time = &time[1 + digits..];
557 }
558 }
559 match time {
560 [] | [b'Z'] | [b'z'] => true,
561 [b'+' | b'-', h1, h2, b':', m1, m2] | [b'+' | b'-', h1, h2, m1, m2] => {
562 two(&[*h1, *h2], 23) && two(&[*m1, *m2], 59)
563 }
564 _ => false,
565 }
566}
567
568#[cfg(test)]
569mod tests {
570 use super::*;
571
572 #[test]
573 fn numbers_parse_strictly() {
574 assert_eq!(parse_int("-42"), Some(-42));
575 assert_eq!(parse_int("+7"), Some(7));
576 assert_eq!(parse_int("1.0"), None);
577 assert_eq!(parse_int("99999999999999999999"), None);
578 assert_eq!(parse_float("99999999999999999999"), Some(1e20));
579 assert_eq!(parse_float(".5"), Some(0.5));
580 assert_eq!(parse_float("5."), Some(5.0));
581 assert_eq!(parse_float("-1.5e-3"), Some(-0.0015));
582 for text in ["inf", "NaN", ".", "-", "1e", "1_000", "0x10", " 1", "1.2.3"] {
583 assert_eq!(parse_float(text), None, "{text}");
584 }
585 assert_eq!(parse_bool("TRUE"), Some(true));
586 assert_eq!(parse_bool("yes"), None);
587 }
588
589 #[test]
590 fn dates_and_timestamps_are_checked() {
591 assert!(is_date("2024-02-29"));
592 assert!(!is_date("2023-02-29"));
593 assert!(!is_date("2024-13-01"));
594 assert!(!is_date("2024-1-01"));
595 for ok in [
596 "2024-02-29T23:59",
597 "2024-02-29 23:59:60",
598 "2024-02-29T00:00:00.123456Z",
599 "2024-02-29T00:00:00+05:30",
600 "2024-02-29T00:00-0800",
601 ] {
602 assert!(is_timestamp(ok), "{ok}");
603 }
604 for bad in [
605 "2024-02-29",
606 "2024-02-29T24:00",
607 "2024-02-29T10:00:00.",
608 "2024-02-29T10:00+5",
609 "2024-02-29X10:00",
610 ] {
611 assert!(!is_timestamp(bad), "{bad}");
612 }
613 }
614
615 #[test]
616 fn columns_take_the_narrowest_type_and_overrides_win() {
617 let mut rows = Rows::new(["n", "x", "b", "empty", "mixed"]);
618 rows.push([
619 "1".into(),
620 "1".into(),
621 "true".into(),
622 "".into(),
623 Value::Int(3),
624 ]);
625 rows.push([
626 "2".into(),
627 "2.5".into(),
628 "False".into(),
629 "NA".into(),
630 "x".into(),
631 ]);
632 let inference = Inferrer::new()
633 .override_type("n", InferredType::Float)
634 .infer(&rows);
635 let types: Vec<InferredType> = inference.columns().iter().map(|c| c.data_type()).collect();
636 assert_eq!(
637 types,
638 [
639 InferredType::Float,
640 InferredType::Float,
641 InferredType::Boolean,
642 InferredType::Null,
643 InferredType::Text
644 ]
645 );
646 assert!(inference.columns()[0].is_overridden());
647 assert_eq!(
648 inference.columns()[0].summary(),
649 "float (override): 2 of 2 values"
650 );
651 assert_eq!(inference.columns()[3].summary(), "null: 2 null");
652 let schema = inference.schema();
653 assert!(schema.fields()[3].is_nullable());
654 assert!(!schema.fields()[0].is_nullable());
655
656 inference.apply(&mut rows);
657 assert_eq!(rows.rows()[0][0], Value::Float(1.0));
658 assert_eq!(rows.rows()[1][1], Value::Float(2.5));
659 assert_eq!(rows.rows()[1][2], Value::from("False"));
660 assert_eq!(rows.rows()[1][3], Value::Null);
661 assert_eq!(rows.rows()[0][4], Value::Int(3));
662 assert_eq!(
663 rows.schema().unwrap().fields()[2].data_type(),
664 &DataType::Boolean
665 );
666 }
667
668 #[test]
669 fn the_report_is_a_table() {
670 let mut rows = Rows::new(["id"]);
671 rows.push(["1".into()]);
672 rows.push(["x".into()]);
673 let inference = Inferrer::new().infer(&rows);
674 let out = Console::builder()
675 .width(40)
676 .build()
677 .render_export(&inference);
678 assert!(
679 out.contains("│ id │ text │ 1/2 integer │ 0 │"),
680 "{out}"
681 );
682 }
683}