use std::fs;
use anyhow::{Result, anyhow, bail};
use crate::cli::QueryCommand;
pub(crate) fn load_query(cmd: &QueryCommand) -> Result<String> {
if let Some(sql) = &cmd.sql {
return Ok(sql.clone());
}
if let Some(query_path) = &cmd.sql_file {
return fs::read_to_string(query_path)
.map_err(Into::into)
.map(|content| content.trim().to_owned());
}
bail!("provide either --sql or --sql-file")
}
pub(crate) fn parse_query_params(entries: &[String]) -> Result<Vec<query_forge::QueryParam>> {
entries
.iter()
.map(|entry| {
let (name, value) = entry
.split_once('=')
.ok_or_else(|| anyhow!("invalid --param '{entry}', expected NAME=VALUE"))?;
if name.trim().is_empty() {
bail!("invalid --param '{entry}', parameter name cannot be empty");
}
Ok(query_forge::QueryParam {
name: name.trim().to_owned(),
value: parse_param_value(value.trim()),
})
})
.collect()
}
pub(crate) fn resolve_type_inference_options(
cmd: &QueryCommand,
) -> query_forge::TypeInferenceOptions {
let defaults = query_forge::TypeInferenceOptions::default();
query_forge::TypeInferenceOptions {
infer_types: !cmd.all_text || cmd.infer_types,
decimal_comma: cmd.decimal_comma,
date_format: cmd.date_format.clone(),
null_values: if cmd.null_values.is_empty() {
defaults.null_values
} else {
cmd.null_values
.iter()
.map(|value| value.trim().to_owned())
.collect()
},
true_values: if cmd.true_values.is_empty() {
defaults.true_values
} else {
cmd.true_values
.iter()
.map(|value| value.trim().to_owned())
.collect()
},
false_values: if cmd.false_values.is_empty() {
defaults.false_values
} else {
cmd.false_values
.iter()
.map(|value| value.trim().to_owned())
.collect()
},
}
}
pub(crate) fn resolve_input_normalization_options(
cmd: &QueryCommand,
) -> query_forge::InputNormalizationOptions {
query_forge::InputNormalizationOptions {
trim: cmd.trim,
skip_empty_rows: cmd.skip_empty_rows,
normalize_headers: cmd.normalize_headers,
header_case: match cmd.header_case {
Some(crate::cli::HeaderCase::Snake) => Some(query_forge::HeaderCase::Snake),
None => None,
},
dedupe_headers: cmd.dedupe_headers,
}
}
pub(crate) fn resolve_extraction_options(
cmd: &QueryCommand,
) -> query_forge::ExtractionOptions {
query_forge::ExtractionOptions {
json_mode: match cmd.json_mode {
Some(crate::cli::JsonMode::Array) | None => query_forge::JsonMode::Array,
Some(crate::cli::JsonMode::Object) => query_forge::JsonMode::Object,
Some(crate::cli::JsonMode::Flatten) => query_forge::JsonMode::Flatten,
},
xml_mode: match cmd.xml_mode {
Some(crate::cli::XmlMode::Rows) | None => query_forge::XmlMode::Rows,
Some(crate::cli::XmlMode::Descendants) => query_forge::XmlMode::Descendants,
Some(crate::cli::XmlMode::Attributes) => query_forge::XmlMode::Attributes,
},
}
}
fn parse_param_value(raw_value: &str) -> query_forge::QueryValue {
if raw_value.eq_ignore_ascii_case("null") {
return query_forge::QueryValue::Null;
}
if raw_value.eq_ignore_ascii_case("true") {
return query_forge::QueryValue::Integer(1);
}
if raw_value.eq_ignore_ascii_case("false") {
return query_forge::QueryValue::Integer(0);
}
if let Ok(integer) = raw_value.parse::<i64>() {
return query_forge::QueryValue::Integer(integer);
}
if let Ok(real) = raw_value.parse::<f64>() {
return query_forge::QueryValue::Real(real);
}
query_forge::QueryValue::Text(raw_value.to_owned())
}
#[cfg(test)]
mod tests {
use clap::Parser;
use crate::cli::{Cli, Commands};
use super::{parse_query_params, resolve_input_normalization_options};
#[test]
fn parses_query_params() {
let entries = vec![
"wbs=IEV092500011.2.2.4".to_owned(),
"min=10".to_owned(),
"enabled=true".to_owned(),
];
let params = parse_query_params(&entries).expect("valid params");
assert_eq!(params.len(), 3);
assert_eq!(params[0].name, "wbs");
assert_eq!(params[1].name, "min");
assert_eq!(params[2].name, "enabled");
}
#[test]
fn resolves_input_normalization_options_from_cli_flags() {
let cli = Cli::parse_from([
"qf",
"query",
"--input",
"data.csv",
"--sql",
"SELECT * FROM table",
"--trim",
"--skip-empty-rows",
"--normalize-headers",
"--header-case",
"snake",
"--dedupe-headers",
]);
let Commands::Query(cmd) = cli.command else {
panic!("expected query command");
};
let options = resolve_input_normalization_options(&cmd);
assert!(options.trim);
assert!(options.skip_empty_rows);
assert!(options.normalize_headers);
assert!(matches!(
options.header_case,
Some(query_forge::HeaderCase::Snake)
));
assert!(options.dedupe_headers);
}
#[test]
fn resolves_extraction_options_from_cli_flags() {
use super::resolve_extraction_options;
let cli = Cli::parse_from([
"qf",
"query",
"--input",
"data.json",
"--sql",
"SELECT * FROM table",
"--json-mode",
"object",
"--xml-mode",
"descendants",
]);
let Commands::Query(cmd) = cli.command else {
panic!("expected query command");
};
let opts = resolve_extraction_options(&cmd);
assert!(matches!(opts.json_mode, query_forge::JsonMode::Object));
assert!(matches!(opts.xml_mode, query_forge::XmlMode::Descendants));
}
#[test]
fn extraction_options_default_to_array_and_rows_when_not_set() {
use super::resolve_extraction_options;
let cli = Cli::parse_from([
"qf",
"query",
"--input",
"data.json",
"--sql",
"SELECT * FROM table",
]);
let Commands::Query(cmd) = cli.command else {
panic!("expected query command");
};
let opts = resolve_extraction_options(&cmd);
assert!(matches!(opts.json_mode, query_forge::JsonMode::Array));
assert!(matches!(opts.xml_mode, query_forge::XmlMode::Rows));
}
}