Skip to main content

query_forge/
types.rs

1use std::path::Path;
2
3#[derive(Debug, Clone, PartialEq)]
4pub enum QueryValue {
5    Null,
6    Integer(i64),
7    Real(f64),
8    Text(String),
9}
10
11#[derive(Debug, Clone, PartialEq)]
12pub struct QueryResult {
13    pub columns: Vec<String>,
14    pub rows: Vec<Vec<QueryValue>>,
15}
16
17#[derive(Debug, Clone, PartialEq)]
18pub struct QueryParam {
19    pub name: String,
20    pub value: QueryValue,
21}
22
23#[derive(Debug, Clone, Copy)]
24pub struct WorkbookInput<'a> {
25    pub path: &'a Path,
26    pub sheet_name: Option<&'a str>,
27    pub table_name: Option<&'a str>,
28    /// Explicit format override (e.g. parsed from `|type` suffix in the path spec).
29    /// Takes priority over the file extension for format detection.
30    pub explicit_format: Option<&'a str>,
31}
32
33#[derive(Debug, Clone)]
34pub struct TypeInferenceOptions {
35    pub infer_types: bool,
36    pub decimal_comma: bool,
37    pub date_format: Option<String>,
38    pub null_values: Vec<String>,
39    pub true_values: Vec<String>,
40    pub false_values: Vec<String>,
41}
42
43impl Default for TypeInferenceOptions {
44    fn default() -> Self {
45        Self {
46            infer_types: true,
47            decimal_comma: false,
48            date_format: None,
49            null_values: vec![String::new()],
50            true_values: vec!["true".to_owned()],
51            false_values: vec!["false".to_owned()],
52        }
53    }
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum HeaderCase {
58    Snake,
59    Camel,
60    Pascal,
61    ScreamingSnake,
62}
63
64#[derive(Debug, Clone, Default)]
65pub struct InputNormalizationOptions {
66    pub trim: bool,
67    pub skip_empty_rows: bool,
68    pub normalize_headers: bool,
69    pub header_case: Option<HeaderCase>,
70    pub dedupe_headers: bool,
71}
72
73/// Controls how JSON input files are parsed into rows.
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
75pub enum JsonMode {
76    /// Each element of a top-level JSON array becomes a row (default).
77    ///
78    /// If the root is an object the object itself becomes a single row.
79    #[default]
80    Array,
81    /// Each key-value pair of a JSON object becomes a row with "key" and "value" columns.
82    ///
83    /// Useful for flat configuration or metadata objects.
84    Object,
85    /// Recursively flatten nested JSON objects and arrays into a single row per top-level
86    /// element, using dotted key paths (e.g. `address.city`) for nested fields.
87    Flatten,
88}
89
90/// Controls how XML input files are parsed into rows.
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
92pub enum XmlMode {
93    /// Detect and extract tabular rows from the XML structure (default).
94    ///
95    /// Looks first for direct `<row>` children, then for any element whose direct
96    /// children are all leaf elements (no deeper nesting).
97    #[default]
98    Rows,
99    /// Collect every leaf text element in the document as a separate row.
100    ///
101    /// Each row has two columns: `tag` (element name) and `value` (text content).
102    Descendants,
103    /// Extract XML element attributes as columns.
104    ///
105    /// Every element that carries at least one XML attribute becomes a row; attribute
106    /// names map to column names. Text content of the element is also included as a
107    /// `value` column when present.
108    Attributes,
109}
110
111/// Options that control how JSON and XML inputs are extracted into rows.
112#[derive(Debug, Clone, Default)]
113pub struct ExtractionOptions {
114    pub json_mode: JsonMode,
115    pub xml_mode: XmlMode,
116}
117
118/// The SQL-compatible type inferred for a column.
119#[derive(Debug, Clone, PartialEq)]
120pub enum InferredType {
121    Integer,
122    Real,
123    Text,
124    /// Column contains a mix of numeric and text values.
125    Mixed,
126}
127
128impl InferredType {
129    pub fn as_str(&self) -> &'static str {
130        match self {
131            InferredType::Integer => "INTEGER",
132            InferredType::Real => "REAL",
133            InferredType::Text => "TEXT",
134            InferredType::Mixed => "MIXED",
135        }
136    }
137}
138
139/// Per-column information returned by inspection commands.
140#[derive(Debug, Clone)]
141pub struct ColumnInfo {
142    pub table_name: String,
143    pub column_name: String,
144    pub inferred_type: InferredType,
145    pub nullable: bool,
146}
147
148/// Optional per-column statistics (enabled by `--stats`).
149#[derive(Debug, Clone)]
150pub struct ColumnStats {
151    pub distinct_count: usize,
152    pub min_value: Option<String>,
153    pub max_value: Option<String>,
154}
155
156/// Summary of a single loaded table, returned by `load_table_summaries`.
157#[derive(Debug, Clone)]
158pub struct TableSummary {
159    /// The logical SQL table name (explicit or auto-generated).
160    pub table_name: String,
161    /// The original file path as provided on the command line.
162    pub source_path: String,
163    /// The sheet/tag/key/index selector, if any.
164    pub source_selector: Option<String>,
165    pub row_count: usize,
166    pub columns: Vec<ColumnInfo>,
167    /// Up to `sample` rows of actual data (column values in column order).
168    pub sample_rows: Vec<Vec<QueryValue>>,
169    /// Non-fatal warnings (e.g. mixed-type columns, duplicate header normalisations).
170    pub warnings: Vec<String>,
171    /// Per-column statistics; `None` when `--stats` is not requested.
172    pub column_stats: Option<Vec<ColumnStats>>,
173}