1use anyhow::{Context, Result};
2use std::fs;
3use std::io::{self, Write};
4use std::path::Path;
5use std::time::Instant;
6use tracing::{debug, info};
7
8use crate::config::config::Config;
9use crate::data::data_view::DataView;
10use crate::data::datatable::{DataTable, DataValue};
11use crate::data::datatable_loaders::{load_csv_to_datatable, load_json_to_datatable};
12use crate::services::query_execution_service::QueryExecutionService;
13use crate::sql::parser::ast::{CTEType, TableSource, CTE};
14use crate::sql::script_parser::{ScriptParser, ScriptResult};
15
16fn extract_cte_dependencies(cte: &CTE) -> Vec<String> {
18 let mut deps = Vec::new();
21
22 if let CTEType::Standard(query) = &cte.cte_type {
23 if let Some(from_table) = &query.from_table {
24 deps.push(from_table.clone());
25 }
26
27 for join in &query.joins {
29 match &join.table {
30 TableSource::Table(table_name) => {
31 deps.push(table_name.clone());
32 }
33 TableSource::DerivedTable { alias, .. } => {
34 deps.push(alias.clone());
35 }
36 }
37 }
38 }
39
40 deps
41}
42
43#[derive(Debug, Clone)]
45pub enum OutputFormat {
46 Csv,
47 Json,
48 Table,
49 Tsv,
50}
51
52impl OutputFormat {
53 pub fn from_str(s: &str) -> Result<Self> {
54 match s.to_lowercase().as_str() {
55 "csv" => Ok(OutputFormat::Csv),
56 "json" => Ok(OutputFormat::Json),
57 "table" => Ok(OutputFormat::Table),
58 "tsv" => Ok(OutputFormat::Tsv),
59 _ => Err(anyhow::anyhow!(
60 "Invalid output format: {}. Use csv, json, table, or tsv",
61 s
62 )),
63 }
64 }
65}
66
67pub struct NonInteractiveConfig {
69 pub data_file: String,
70 pub query: String,
71 pub output_format: OutputFormat,
72 pub output_file: Option<String>,
73 pub case_insensitive: bool,
74 pub auto_hide_empty: bool,
75 pub limit: Option<usize>,
76 pub query_plan: bool,
77 pub show_work_units: bool,
78 pub execution_plan: bool,
79 pub cte_info: bool,
80 pub rewrite_analysis: bool,
81 pub lift_in_expressions: bool,
82 pub script_file: Option<String>, pub debug_trace: bool,
84}
85
86pub fn execute_non_interactive(config: NonInteractiveConfig) -> Result<()> {
88 let start_time = Instant::now();
89
90 let (data_table, _is_dual) = if config.data_file.is_empty() {
92 info!("No data file provided, using DUAL table");
93 (crate::data::datatable::DataTable::dual(), true)
94 } else {
95 info!("Loading data from: {}", config.data_file);
96 let table = load_data_file(&config.data_file)?;
97 info!(
98 "Loaded {} rows with {} columns",
99 table.row_count(),
100 table.column_count()
101 );
102 (table, false)
103 };
104 let _table_name = data_table.name.clone();
105
106 let dataview = DataView::new(std::sync::Arc::new(data_table));
108
109 info!("Executing query: {}", config.query);
111
112 if config.execution_plan {
114 println!("\n=== EXECUTION PLAN ===");
115 println!("Query: {}", config.query);
116 println!("\nExecution Steps:");
117 println!("1. PARSE - Parse SQL query");
118 println!("2. LOAD_DATA - Load data from {}", &config.data_file);
119 println!(
120 " • Loaded {} rows, {} columns",
121 dataview.row_count(),
122 dataview.column_count()
123 );
124 }
125
126 if config.show_work_units {
128 use crate::query_plan::{ExpressionLifter, QueryAnalyzer};
129 use crate::sql::recursive_parser::Parser;
130
131 let mut parser = Parser::new(&config.query);
132 match parser.parse() {
133 Ok(stmt) => {
134 let mut analyzer = QueryAnalyzer::new();
135 let mut lifter = ExpressionLifter::new();
136
137 let mut stmt_copy = stmt.clone();
139 let lifted = lifter.lift_expressions(&mut stmt_copy);
140
141 match analyzer.analyze(&stmt_copy, config.query.clone()) {
143 Ok(plan) => {
144 println!("\n{}", plan.explain());
145
146 if !lifted.is_empty() {
147 println!("\nLifted CTEs:");
148 for cte in &lifted {
149 println!(" - {}", cte.name);
150 }
151 }
152
153 return Ok(());
154 }
155 Err(e) => {
156 eprintln!("Error analyzing query: {}", e);
157 return Err(anyhow::anyhow!("Query analysis failed: {}", e));
158 }
159 }
160 }
161 Err(e) => {
162 eprintln!("Error parsing query: {}", e);
163 return Err(anyhow::anyhow!("Parse error: {}", e));
164 }
165 }
166 }
167
168 if config.query_plan {
170 use crate::sql::recursive_parser::Parser;
171 let mut parser = Parser::new(&config.query);
172 match parser.parse() {
173 Ok(statement) => {
174 println!("\n=== QUERY PLAN (AST) ===");
175 println!("{statement:#?}");
176 println!("=== END QUERY PLAN ===\n");
177 }
178 Err(e) => {
179 eprintln!("Failed to parse query for plan: {e}");
180 }
181 }
182 }
183
184 if config.rewrite_analysis {
186 use crate::sql::query_rewriter::{QueryRewriter, RewriteAnalysis};
187 use crate::sql::recursive_parser::Parser;
188 use serde_json::json;
189
190 let mut parser = Parser::new(&config.query);
191 match parser.parse() {
192 Ok(statement) => {
193 let mut rewriter = QueryRewriter::new();
194 let suggestions = rewriter.analyze(&statement);
195
196 let analysis = RewriteAnalysis::from_suggestions(suggestions);
197 println!("{}", serde_json::to_string_pretty(&analysis).unwrap());
198 return Ok(());
199 }
200 Err(e) => {
201 let output = json!({
202 "success": false,
203 "error": format!("{}", e),
204 "suggestions": [],
205 "can_auto_rewrite": false,
206 });
207 println!("{}", serde_json::to_string_pretty(&output).unwrap());
208 return Ok(());
209 }
210 }
211 }
212
213 if config.cte_info {
215 use crate::sql::recursive_parser::Parser;
216 use serde_json::json;
217
218 let mut parser = Parser::new(&config.query);
219 match parser.parse() {
220 Ok(statement) => {
221 let mut cte_info = Vec::new();
222
223 for (index, cte) in statement.ctes.iter().enumerate() {
225 let cte_json = json!({
226 "index": index,
227 "name": cte.name,
228 "columns": cte.column_list,
229 "dependencies": extract_cte_dependencies(cte),
232 });
233 cte_info.push(cte_json);
234 }
235
236 let output = json!({
237 "success": true,
238 "ctes": cte_info,
239 "total": statement.ctes.len(),
240 "has_final_select": !statement.columns.is_empty() || !statement.select_items.is_empty(),
241 });
242
243 println!("{}", serde_json::to_string_pretty(&output).unwrap());
244 return Ok(());
245 }
246 Err(e) => {
247 let output = json!({
248 "success": false,
249 "error": format!("{}", e),
250 "ctes": [],
251 "total": 0,
252 });
253 println!("{}", serde_json::to_string_pretty(&output).unwrap());
254 return Ok(());
255 }
256 }
257 }
258
259 let query_start = Instant::now();
260
261 let app_config = Config::load().unwrap_or_else(|e| {
263 debug!("Could not load config file: {}. Using defaults.", e);
264 Config::default()
265 });
266
267 crate::config::global::init_config(app_config.clone());
269
270 let mut behavior_config = app_config.behavior.clone();
272 debug!(
273 "Using date notation: {}",
274 behavior_config.default_date_notation
275 );
276 if config.case_insensitive {
278 behavior_config.case_insensitive_default = true;
279 }
280 if config.auto_hide_empty {
281 behavior_config.hide_empty_columns = true;
282 }
283
284 let exec_start = Instant::now();
286 let result = if config.lift_in_expressions {
287 use crate::data::query_engine::QueryEngine;
288 use crate::query_plan::{CTEHoister, InOperatorLifter};
289 use crate::sql::recursive_parser::Parser;
290
291 let mut parser = Parser::new(&config.query);
292 match parser.parse() {
293 Ok(mut stmt) => {
294 use crate::query_plan::ExpressionLifter;
296 let mut expr_lifter = ExpressionLifter::new();
297 let lifted = expr_lifter.lift_expressions(&mut stmt);
298 if !lifted.is_empty() {
299 info!(
300 "Applied expression lifting - {} CTEs generated",
301 lifted.len()
302 );
303 }
304
305 stmt = CTEHoister::hoist_ctes(stmt);
307
308 let mut lifter = InOperatorLifter::new();
310 if lifter.rewrite_query(&mut stmt) {
311 info!("Applied IN expression lifting - query rewritten with CTEs");
312
313 let engine = QueryEngine::with_case_insensitive(config.case_insensitive);
315
316 match engine.execute_statement(dataview.source_arc(), stmt) {
317 Ok(result_view) => {
318 Ok(
320 crate::services::query_execution_service::QueryExecutionResult {
321 dataview: result_view,
322 stats: crate::services::query_execution_service::QueryStats {
323 row_count: 0, column_count: 0,
325 execution_time: exec_start.elapsed(),
326 query_engine_time: exec_start.elapsed(),
327 },
328 hidden_columns: Vec::new(),
329 query: config.query.clone(),
330 execution_plan: None,
331 debug_trace: None,
332 },
333 )
334 }
335 Err(e) => Err(e),
336 }
337 } else {
338 let query_service =
340 QueryExecutionService::with_behavior_config(behavior_config);
341 if config.debug_trace {
342 let debug_ctx = crate::debug_trace::DebugContext::new(
343 crate::debug_trace::DebugLevel::Debug,
344 );
345 query_service.execute_with_debug(
346 &config.query,
347 Some(&dataview),
348 Some(dataview.source()),
349 Some(debug_ctx),
350 )
351 } else {
352 query_service.execute(
353 &config.query,
354 Some(&dataview),
355 Some(dataview.source()),
356 )
357 }
358 }
359 }
360 Err(_) => {
361 let query_service = QueryExecutionService::with_behavior_config(behavior_config);
363 if config.debug_trace {
364 let debug_ctx = crate::debug_trace::DebugContext::new(
365 crate::debug_trace::DebugLevel::Debug,
366 );
367 query_service.execute_with_debug(
368 &config.query,
369 Some(&dataview),
370 Some(dataview.source()),
371 Some(debug_ctx),
372 )
373 } else {
374 query_service.execute(&config.query, Some(&dataview), Some(dataview.source()))
375 }
376 }
377 }
378 } else {
379 let query_service = QueryExecutionService::with_behavior_config(behavior_config);
381
382 if config.debug_trace {
384 let debug_ctx =
385 crate::debug_trace::DebugContext::new(crate::debug_trace::DebugLevel::Debug);
386 query_service.execute_with_debug(
387 &config.query,
388 Some(&dataview),
389 Some(dataview.source()),
390 Some(debug_ctx),
391 )
392 } else {
393 query_service.execute(&config.query, Some(&dataview), Some(dataview.source()))
394 }
395 }?;
396 let exec_time = exec_start.elapsed();
397
398 let query_time = query_start.elapsed();
399 info!("Query executed in {:?}", query_time);
400 info!(
401 "Result: {} rows, {} columns",
402 result.dataview.row_count(),
403 result.dataview.column_count()
404 );
405
406 if config.execution_plan {
408 use crate::data::query_engine::QueryEngine;
410
411 let query_engine = QueryEngine::new();
412
413 match query_engine.execute_with_plan(
414 std::sync::Arc::new(dataview.source().clone()),
415 &config.query,
416 ) {
417 Ok((_view, plan)) => {
418 print!("{}", plan.format_tree());
420 }
421 Err(e) => {
422 eprintln!("Could not generate detailed execution plan: {}", e);
424 println!(
425 "3. QUERY_EXECUTION [{:.3}ms]",
426 exec_time.as_secs_f64() * 1000.0
427 );
428
429 use crate::sql::recursive_parser::Parser;
431 let mut parser = Parser::new(&config.query);
432 if let Ok(stmt) = parser.parse() {
433 if stmt.where_clause.is_some() {
434 println!(" • WHERE clause filtering applied");
435 println!(" • Rows after filter: {}", result.dataview.row_count());
436 }
437
438 if let Some(ref order_by) = stmt.order_by {
439 println!(" • ORDER BY: {} column(s)", order_by.len());
440 }
441
442 if let Some(ref group_by) = stmt.group_by {
443 println!(" • GROUP BY: {} column(s)", group_by.len());
444 }
445
446 if let Some(limit) = stmt.limit {
447 println!(" • LIMIT: {} rows", limit);
448 }
449
450 if stmt.distinct {
451 println!(" • DISTINCT applied");
452 }
453 }
454 }
455 }
456
457 println!("\nExecution Statistics:");
458 println!(
459 " Preparation: {:.3}ms",
460 (exec_start - start_time).as_secs_f64() * 1000.0
461 );
462 println!(
463 " Query time: {:.3}ms",
464 exec_time.as_secs_f64() * 1000.0
465 );
466 println!(
467 " Total time: {:.3}ms",
468 query_time.as_secs_f64() * 1000.0
469 );
470 println!(" Rows returned: {}", result.dataview.row_count());
471 println!(" Columns: {}", result.dataview.column_count());
472 println!("\n=== END EXECUTION PLAN ===");
473 println!();
474 }
475
476 let final_view = if let Some(limit) = config.limit {
478 let limited_table = limit_results(&result.dataview, limit)?;
479 DataView::new(std::sync::Arc::new(limited_table))
480 } else {
481 result.dataview
482 };
483
484 if let Some(ref trace_output) = result.debug_trace {
486 eprintln!("{}", trace_output);
487 }
488
489 let output_result = if let Some(ref path) = config.output_file {
491 let mut file = fs::File::create(path)
492 .with_context(|| format!("Failed to create output file: {path}"))?;
493 output_results(&final_view, config.output_format, &mut file)?;
494 info!("Results written to: {}", path);
495 Ok(())
496 } else {
497 output_results(&final_view, config.output_format, &mut io::stdout())?;
498 Ok(())
499 };
500
501 let total_time = start_time.elapsed();
502 debug!("Total execution time: {:?}", total_time);
503
504 if config.output_file.is_none() {
506 eprintln!(
507 "\n# Query completed: {} rows in {:?}",
508 final_view.row_count(),
509 query_time
510 );
511 }
512
513 output_result
514}
515
516pub fn execute_script(config: NonInteractiveConfig) -> Result<()> {
518 let _start_time = Instant::now();
519
520 let parser = ScriptParser::new(&config.query);
522 let statements = parser.parse_and_validate()?;
523
524 info!("Found {} statements in script", statements.len());
525
526 let data_file = if !config.data_file.is_empty() {
528 config.data_file.clone()
530 } else if let Some(hint) = parser.data_file_hint() {
531 info!("Using data file from script hint: {}", hint);
533
534 if let Some(script_path) = config.script_file.as_ref() {
536 let script_dir = std::path::Path::new(script_path)
537 .parent()
538 .unwrap_or(std::path::Path::new("."));
539 let hint_path = std::path::Path::new(hint);
540
541 if hint_path.is_relative() {
542 script_dir.join(hint_path).to_string_lossy().to_string()
543 } else {
544 hint.to_string()
545 }
546 } else {
547 hint.to_string()
548 }
549 } else {
550 String::new()
551 };
552
553 let (data_table, _is_dual) = if data_file.is_empty() {
555 info!("No data file provided, using DUAL table");
557 (DataTable::dual(), true)
558 } else {
559 if !std::path::Path::new(&data_file).exists() {
561 anyhow::bail!(
562 "Data file not found: {}\n\
563 Please check the path is correct",
564 data_file
565 );
566 }
567
568 info!("Loading data from: {}", data_file);
569 let table = load_data_file(&data_file)?;
570 info!(
571 "Loaded {} rows with {} columns",
572 table.row_count(),
573 table.column_count()
574 );
575 (table, false)
576 };
577
578 let mut script_result = ScriptResult::new();
580 let mut output = Vec::new();
581
582 let arc_data_table = std::sync::Arc::new(data_table);
584
585 for (idx, statement) in statements.iter().enumerate() {
587 let statement_num = idx + 1;
588 let stmt_start = Instant::now();
589
590 if matches!(config.output_format, OutputFormat::Table) {
592 if idx > 0 {
593 output.push(String::new()); }
595 output.push(format!("-- Query {} --", statement_num));
596 }
597
598 let dataview = DataView::new(arc_data_table.clone());
600
601 let service = QueryExecutionService::new(config.case_insensitive, config.auto_hide_empty);
603 match service.execute(statement, Some(&dataview), None) {
604 Ok(result) => {
605 let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
606 let final_view = result.dataview;
607
608 let mut statement_output = Vec::new();
610 match config.output_format {
611 OutputFormat::Csv => {
612 output_csv(&final_view, &mut statement_output, ',')?;
613 }
614 OutputFormat::Json => {
615 output_json(&final_view, &mut statement_output)?;
616 }
617 OutputFormat::Table => {
618 output_table(&final_view, &mut statement_output)?;
619 writeln!(
620 &mut statement_output,
621 "Query completed: {} rows in {:.2}ms",
622 final_view.row_count(),
623 exec_time
624 )?;
625 }
626 OutputFormat::Tsv => {
627 output_csv(&final_view, &mut statement_output, '\t')?;
628 }
629 }
630
631 output.extend(
633 String::from_utf8_lossy(&statement_output)
634 .lines()
635 .map(String::from),
636 );
637
638 script_result.add_success(
639 statement_num,
640 statement.clone(),
641 final_view.row_count(),
642 exec_time,
643 );
644 }
645 Err(e) => {
646 let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
647 let error_msg = format!("Query {} failed: {}", statement_num, e);
648
649 if matches!(config.output_format, OutputFormat::Table) {
650 output.push(error_msg.clone());
651 }
652
653 script_result.add_failure(
654 statement_num,
655 statement.clone(),
656 e.to_string(),
657 exec_time,
658 );
659
660 }
662 }
663 }
664
665 if let Some(ref output_file) = config.output_file {
667 let mut file = fs::File::create(output_file)?;
668 for line in &output {
669 writeln!(file, "{}", line)?;
670 }
671 info!("Results written to: {}", output_file);
672 } else {
673 for line in &output {
674 println!("{}", line);
675 }
676 }
677
678 if matches!(config.output_format, OutputFormat::Table) {
680 println!("\n=== Script Summary ===");
681 println!("Total statements: {}", script_result.total_statements);
682 println!("Successful: {}", script_result.successful_statements);
683 println!("Failed: {}", script_result.failed_statements);
684 println!(
685 "Total execution time: {:.2}ms",
686 script_result.total_execution_time_ms
687 );
688 }
689
690 if !script_result.all_successful() {
691 return Err(anyhow::anyhow!(
692 "{} of {} statements failed",
693 script_result.failed_statements,
694 script_result.total_statements
695 ));
696 }
697
698 Ok(())
699}
700
701fn load_data_file(path: &str) -> Result<DataTable> {
703 let path = Path::new(path);
704
705 if !path.exists() {
706 return Err(anyhow::anyhow!("File not found: {}", path.display()));
707 }
708
709 let extension = path
711 .extension()
712 .and_then(|ext| ext.to_str())
713 .map(str::to_lowercase)
714 .unwrap_or_default();
715
716 let table_name = path
717 .file_stem()
718 .and_then(|stem| stem.to_str())
719 .unwrap_or("data")
720 .to_string();
721
722 match extension.as_str() {
723 "csv" => load_csv_to_datatable(path, &table_name)
724 .with_context(|| format!("Failed to load CSV file: {}", path.display())),
725 "json" => load_json_to_datatable(path, &table_name)
726 .with_context(|| format!("Failed to load JSON file: {}", path.display())),
727 _ => Err(anyhow::anyhow!(
728 "Unsupported file type: {}. Use .csv or .json",
729 extension
730 )),
731 }
732}
733
734fn limit_results(dataview: &DataView, limit: usize) -> Result<DataTable> {
736 let source = dataview.source();
737 let mut limited_table = DataTable::new(&source.name);
738
739 for col in &source.columns {
741 limited_table.add_column(col.clone());
742 }
743
744 let rows_to_copy = dataview.row_count().min(limit);
746 for i in 0..rows_to_copy {
747 if let Some(row) = dataview.get_row(i) {
748 limited_table.add_row(row.clone());
749 }
750 }
751
752 Ok(limited_table)
753}
754
755fn output_results<W: Write>(
757 dataview: &DataView,
758 format: OutputFormat,
759 writer: &mut W,
760) -> Result<()> {
761 match format {
762 OutputFormat::Csv => output_csv(dataview, writer, ','),
763 OutputFormat::Tsv => output_csv(dataview, writer, '\t'),
764 OutputFormat::Json => output_json(dataview, writer),
765 OutputFormat::Table => output_table(dataview, writer),
766 }
767}
768
769fn output_csv<W: Write>(dataview: &DataView, writer: &mut W, delimiter: char) -> Result<()> {
771 let columns = dataview.column_names();
773 for (i, col) in columns.iter().enumerate() {
774 if i > 0 {
775 write!(writer, "{delimiter}")?;
776 }
777 write!(writer, "{}", escape_csv_field(col, delimiter))?;
778 }
779 writeln!(writer)?;
780
781 for row_idx in 0..dataview.row_count() {
783 if let Some(row) = dataview.get_row(row_idx) {
784 for (i, value) in row.values.iter().enumerate() {
785 if i > 0 {
786 write!(writer, "{delimiter}")?;
787 }
788 write!(
789 writer,
790 "{}",
791 escape_csv_field(&format_value(value), delimiter)
792 )?;
793 }
794 writeln!(writer)?;
795 }
796 }
797
798 Ok(())
799}
800
801fn output_json<W: Write>(dataview: &DataView, writer: &mut W) -> Result<()> {
803 let columns = dataview.column_names();
804 let mut rows = Vec::new();
805
806 for row_idx in 0..dataview.row_count() {
807 if let Some(row) = dataview.get_row(row_idx) {
808 let mut json_row = serde_json::Map::new();
809 for (col_idx, value) in row.values.iter().enumerate() {
810 if col_idx < columns.len() {
811 json_row.insert(columns[col_idx].clone(), value_to_json(value));
812 }
813 }
814 rows.push(serde_json::Value::Object(json_row));
815 }
816 }
817
818 let json = serde_json::to_string_pretty(&rows)?;
819 writeln!(writer, "{json}")?;
820
821 Ok(())
822}
823
824fn output_table<W: Write>(dataview: &DataView, writer: &mut W) -> Result<()> {
826 let columns = dataview.column_names();
827
828 let mut widths = vec![0; columns.len()];
830 for (i, col) in columns.iter().enumerate() {
831 widths[i] = col.len();
832 }
833
834 let sample_size = dataview.row_count().min(100);
836 for row_idx in 0..sample_size {
837 if let Some(row) = dataview.get_row(row_idx) {
838 for (i, value) in row.values.iter().enumerate() {
839 if i < widths.len() {
840 let value_str = format_value(value);
841 widths[i] = widths[i].max(value_str.len());
842 }
843 }
844 }
845 }
846
847 for width in &mut widths {
849 *width = (*width).min(50);
850 }
851
852 write!(writer, "+")?;
854 for width in &widths {
855 write!(writer, "-{}-+", "-".repeat(*width))?;
856 }
857 writeln!(writer)?;
858
859 write!(writer, "|")?;
861 for (i, col) in columns.iter().enumerate() {
862 write!(writer, " {:^width$} |", col, width = widths[i])?;
863 }
864 writeln!(writer)?;
865
866 write!(writer, "+")?;
868 for width in &widths {
869 write!(writer, "-{}-+", "-".repeat(*width))?;
870 }
871 writeln!(writer)?;
872
873 for row_idx in 0..dataview.row_count() {
875 if let Some(row) = dataview.get_row(row_idx) {
876 write!(writer, "|")?;
877 for (i, value) in row.values.iter().enumerate() {
878 if i < widths.len() {
879 let value_str = format_value(value);
880 let truncated = if value_str.len() > widths[i] {
881 format!("{}...", &value_str[..widths[i] - 3])
882 } else {
883 value_str
884 };
885 write!(writer, " {:<width$} |", truncated, width = widths[i])?;
886 }
887 }
888 writeln!(writer)?;
889 }
890 }
891
892 write!(writer, "+")?;
894 for width in &widths {
895 write!(writer, "-{}-+", "-".repeat(*width))?;
896 }
897 writeln!(writer)?;
898
899 Ok(())
900}
901
902fn format_value(value: &DataValue) -> String {
904 match value {
905 DataValue::Null => String::new(),
906 DataValue::Integer(i) => i.to_string(),
907 DataValue::Float(f) => f.to_string(),
908 DataValue::String(s) => s.clone(),
909 DataValue::InternedString(s) => s.to_string(),
910 DataValue::Boolean(b) => b.to_string(),
911 DataValue::DateTime(dt) => dt.to_string(),
912 }
913}
914
915fn value_to_json(value: &DataValue) -> serde_json::Value {
917 match value {
918 DataValue::Null => serde_json::Value::Null,
919 DataValue::Integer(i) => serde_json::Value::Number((*i).into()),
920 DataValue::Float(f) => {
921 if let Some(n) = serde_json::Number::from_f64(*f) {
922 serde_json::Value::Number(n)
923 } else {
924 serde_json::Value::Null
925 }
926 }
927 DataValue::String(s) => serde_json::Value::String(s.clone()),
928 DataValue::InternedString(s) => serde_json::Value::String(s.to_string()),
929 DataValue::Boolean(b) => serde_json::Value::Bool(*b),
930 DataValue::DateTime(dt) => serde_json::Value::String(dt.to_string()),
931 }
932}
933
934fn escape_csv_field(field: &str, delimiter: char) -> String {
936 if field.contains(delimiter)
937 || field.contains('"')
938 || field.contains('\n')
939 || field.contains('\r')
940 {
941 format!("\"{}\"", field.replace('"', "\"\""))
942 } else {
943 field.to_string()
944 }
945}