1use super::definitions::{
2 db_column::DbColumn, db_constraint::DbConstraint, db_foreignkey::DbForeignKey,
3 db_index::DbIndex, db_table::DbTable,
4};
5use nu_protocol::{
6 CustomValue, IntoPipelineData, PipelineData, Record, ShellError, Signals, Span, Spanned, Value,
7 ast, casing::Casing, engine::EngineState, shell_error::generic::GenericError,
8 shell_error::io::IoError,
9};
10use rusqlite::{
11 Connection, Error as SqliteError, OpenFlags, Row, Statement, ToSql, types::ValueRef,
12};
13use serde::{Deserialize, Serialize};
14use std::{
15 collections::BTreeMap,
16 fmt::Write,
17 fs::File,
18 io::Read,
19 path::{Path, PathBuf},
20};
21
22const SQLITE_MAGIC_BYTES: &[u8] = "SQLite format 3\0".as_bytes();
23pub const MEMORY_DB: &str = "file:memdb1?mode=memory&cache=shared";
24const DATABASE_NAME: &str = "main";
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct SQLiteDatabase {
28 pub path: PathBuf,
32 #[serde(skip, default = "Signals::empty")]
33 signals: Signals,
36}
37
38impl SQLiteDatabase {
39 pub fn new(path: &Path, signals: Signals) -> Self {
40 Self {
41 path: PathBuf::from(path),
42 signals,
43 }
44 }
45
46 pub fn try_from_path(path: &Path, span: Span, signals: Signals) -> Result<Self, ShellError> {
47 let mut file = File::open(path).map_err(|e| IoError::new(e, span, PathBuf::from(path)))?;
48
49 let mut buf: [u8; 16] = [0; 16];
50 file.read_exact(&mut buf)
51 .map_err(|e| ShellError::Io(IoError::new(e, span, PathBuf::from(path))))
52 .and_then(|_| {
53 if buf == SQLITE_MAGIC_BYTES {
54 Ok(SQLiteDatabase::new(path, signals))
55 } else {
56 Err(ShellError::Generic(GenericError::new(
57 "Not a SQLite file",
58 format!("Could not read '{}' as SQLite file", path.display()),
59 span,
60 )))
61 }
62 })
63 }
64
65 pub fn try_from_value(value: Value) -> Result<Self, ShellError> {
66 let span = value.span();
67 match value {
68 Value::Custom { val, .. } => match val.as_any().downcast_ref::<Self>() {
69 Some(db) => Ok(Self {
70 path: db.path.clone(),
71 signals: db.signals.clone(),
72 }),
73 None => Err(ShellError::CantConvert {
74 to_type: "database".into(),
75 from_type: "non-database".into(),
76 span,
77 help: None,
78 }),
79 },
80 x => Err(ShellError::CantConvert {
81 to_type: "database".into(),
82 from_type: x.get_type().to_string(),
83 span: x.span(),
84 help: None,
85 }),
86 }
87 }
88
89 pub fn try_from_pipeline(input: PipelineData, span: Span) -> Result<Self, ShellError> {
90 let value = input.into_value(span)?;
91 Self::try_from_value(value)
92 }
93
94 pub fn into_value(self, span: Span) -> Value {
95 let db = Box::new(self);
96 Value::custom(db, span)
97 }
98
99 pub fn query(
100 &self,
101 sql: &Spanned<String>,
102 params: NuSqlParams,
103 call_span: Span,
104 ) -> Result<Value, ShellError> {
105 let conn = open_sqlite_db(&self.path, call_span)?;
106 let stream = run_sql_query(conn, sql, params, &self.signals, None)
107 .map_err(|e| e.into_shell_error(sql.span, "Failed to query SQLite database"))?;
108
109 Ok(stream)
110 }
111
112 pub fn open_connection(&self) -> Result<Connection, ShellError> {
113 if self.path.to_string_lossy() == MEMORY_DB {
114 open_connection_in_memory_custom()
115 } else {
116 let conn = Connection::open(&self.path).map_err(|e| {
117 ShellError::Generic(GenericError::new_internal(
118 "Failed to open SQLite database from open_connection",
119 e.to_string(),
120 ))
121 })?;
122 conn.busy_handler(Some(SQLiteDatabase::sleeper))
123 .map_err(|e| {
124 ShellError::Generic(GenericError::new_internal(
125 "Failed to set busy handler for SQLite database",
126 e.to_string(),
127 ))
128 })?;
129 Ok(conn)
130 }
131 }
132
133 fn sleeper(attempts: i32) -> bool {
134 log::warn!("SQLITE_BUSY, retrying after 250ms (attempt {attempts})");
135 std::thread::sleep(std::time::Duration::from_millis(250));
136 true
137 }
138
139 pub fn get_tables(&self, conn: &Connection) -> Result<Vec<DbTable>, SqliteError> {
140 let mut table_names =
141 conn.prepare("SELECT name FROM sqlite_master WHERE type = 'table'")?;
142 let rows = table_names.query_map([], |row| row.get(0))?;
143 let mut tables = Vec::new();
144
145 for row in rows {
146 let table_name: String = row?;
147 tables.push(DbTable {
148 name: table_name,
149 create_time: None,
150 update_time: None,
151 engine: None,
152 schema: None,
153 })
154 }
155
156 Ok(tables.into_iter().collect())
157 }
158
159 pub fn drop_all_tables(&self, conn: &Connection) -> Result<(), SqliteError> {
160 let tables = self.get_tables(conn)?;
161
162 for table in tables {
163 conn.execute(&format!("DROP TABLE {}", table.name), [])?;
164 }
165
166 Ok(())
167 }
168
169 pub fn export_in_memory_database_to_file(
170 &self,
171 conn: &Connection,
172 filename: String,
173 ) -> Result<(), SqliteError> {
174 conn.execute(&format!("vacuum main into '{filename}'"), [])?;
176
177 Ok(())
178 }
179
180 pub fn backup_database_to_file(
181 &self,
182 conn: &Connection,
183 filename: String,
184 ) -> Result<(), SqliteError> {
185 conn.backup(DATABASE_NAME, Path::new(&filename), None)?;
186 Ok(())
187 }
188
189 pub fn restore_database_from_file(
190 &self,
191 conn: &mut Connection,
192 filename: String,
193 ) -> Result<(), SqliteError> {
194 conn.restore(
195 DATABASE_NAME,
196 Path::new(&filename),
197 Some(|p: rusqlite::backup::Progress| {
198 let percent = if p.pagecount == 0 {
199 100
200 } else {
201 (p.pagecount - p.remaining) * 100 / p.pagecount
202 };
203 if percent % 10 == 0 {
204 log::trace!("Restoring: {percent} %");
205 }
206 }),
207 )?;
208 Ok(())
209 }
210
211 fn get_column_info(&self, row: &Row) -> Result<DbColumn, SqliteError> {
212 let dbc = DbColumn {
213 cid: row.get("cid")?,
214 name: row.get("name")?,
215 r#type: row.get("type")?,
216 notnull: row.get("notnull")?,
217 default: row.get("dflt_value")?,
218 pk: row.get("pk")?,
219 };
220 Ok(dbc)
221 }
222
223 pub fn get_columns(
224 &self,
225 conn: &Connection,
226 table: &DbTable,
227 ) -> Result<Vec<DbColumn>, SqliteError> {
228 let mut column_names = conn.prepare(&format!(
229 "SELECT * FROM pragma_table_info('{}');",
230 table.name
231 ))?;
232
233 let mut columns: Vec<DbColumn> = Vec::new();
234 let rows = column_names.query_and_then([], |row| self.get_column_info(row))?;
235
236 for row in rows {
237 columns.push(row?);
238 }
239
240 Ok(columns)
241 }
242
243 fn get_constraint_info(&self, row: &Row) -> Result<DbConstraint, SqliteError> {
244 let dbc = DbConstraint {
245 name: row.get("index_name")?,
246 column_name: row.get("column_name")?,
247 origin: row.get("origin")?,
248 };
249 Ok(dbc)
250 }
251
252 pub fn get_constraints(
253 &self,
254 conn: &Connection,
255 table: &DbTable,
256 ) -> Result<Vec<DbConstraint>, SqliteError> {
257 let mut column_names = conn.prepare(&format!(
258 "
259 SELECT
260 p.origin,
261 s.name AS index_name,
262 i.name AS column_name
263 FROM
264 sqlite_master s
265 JOIN pragma_index_list(s.tbl_name) p ON s.name = p.name,
266 pragma_index_info(s.name) i
267 WHERE
268 s.type = 'index'
269 AND tbl_name = '{}'
270 AND NOT p.origin = 'c'
271 ",
272 &table.name
273 ))?;
274
275 let mut constraints: Vec<DbConstraint> = Vec::new();
276 let rows = column_names.query_and_then([], |row| self.get_constraint_info(row))?;
277
278 for row in rows {
279 constraints.push(row?);
280 }
281
282 Ok(constraints)
283 }
284
285 fn get_foreign_keys_info(&self, row: &Row) -> Result<DbForeignKey, SqliteError> {
286 let dbc = DbForeignKey {
287 column_name: row.get("from")?,
288 ref_table: row.get("table")?,
289 ref_column: row.get("to")?,
290 };
291 Ok(dbc)
292 }
293
294 pub fn get_foreign_keys(
295 &self,
296 conn: &Connection,
297 table: &DbTable,
298 ) -> Result<Vec<DbForeignKey>, SqliteError> {
299 let mut column_names = conn.prepare(&format!(
300 "SELECT p.`from`, p.`to`, p.`table` FROM pragma_foreign_key_list('{}') p",
301 &table.name
302 ))?;
303
304 let mut foreign_keys: Vec<DbForeignKey> = Vec::new();
305 let rows = column_names.query_and_then([], |row| self.get_foreign_keys_info(row))?;
306
307 for row in rows {
308 foreign_keys.push(row?);
309 }
310
311 Ok(foreign_keys)
312 }
313
314 fn get_index_info(&self, row: &Row) -> Result<DbIndex, SqliteError> {
315 let dbc = DbIndex {
316 name: row.get("index_name")?,
317 column_name: row.get("name")?,
318 seqno: row.get("seqno")?,
319 };
320 Ok(dbc)
321 }
322
323 pub fn get_indexes(
324 &self,
325 conn: &Connection,
326 table: &DbTable,
327 ) -> Result<Vec<DbIndex>, SqliteError> {
328 let mut column_names = conn.prepare(&format!(
329 "
330 SELECT
331 m.name AS index_name,
332 p.*
333 FROM
334 sqlite_master m,
335 pragma_index_info(m.name) p
336 WHERE
337 m.type = 'index'
338 AND m.tbl_name = '{}'
339 ",
340 &table.name,
341 ))?;
342
343 let mut indexes: Vec<DbIndex> = Vec::new();
344 let rows = column_names.query_and_then([], |row| self.get_index_info(row))?;
345
346 for row in rows {
347 indexes.push(row?);
348 }
349
350 Ok(indexes)
351 }
352}
353
354impl CustomValue for SQLiteDatabase {
355 fn clone_value(&self, span: Span) -> Value {
356 Value::custom(Box::new(self.clone()), span)
357 }
358
359 fn type_name(&self) -> String {
360 self.typetag_name().to_string()
361 }
362
363 fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
364 let db = open_sqlite_db(&self.path, span)?;
365 read_entire_sqlite_db(db, span, &self.signals)
366 .map_err(|e| e.into_shell_error(span, "Failed to read from SQLite database"))
367 }
368
369 fn as_any(&self) -> &dyn std::any::Any {
370 self
371 }
372
373 fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
374 self
375 }
376
377 fn follow_path_int(
378 &self,
379 _self_span: Span,
380 _index: usize,
381 path_span: Span,
382 _optional: bool,
383 ) -> Result<Value, ShellError> {
384 Err(ShellError::IncompatiblePathAccess { type_name: "SQLite databases do not support integer-indexed access. Try specifying a table name instead".into(), span: path_span })
386 }
387
388 fn follow_path_string(
389 &self,
390 _self_span: Span,
391 column_name: String,
392 path_span: Span,
393 _optional: bool,
394 _casing: Casing,
395 ) -> Result<Value, ShellError> {
396 let table = SQLiteQueryBuilder::new(self.path.clone(), column_name, self.signals.clone());
398 Ok(Value::custom(Box::new(table), path_span))
399 }
400
401 fn typetag_name(&self) -> &'static str {
402 "SQLiteDatabase"
403 }
404
405 fn typetag_deserialize(&self) {
406 unimplemented!("typetag_deserialize")
407 }
408}
409
410pub fn open_sqlite_db(path: &Path, call_span: Span) -> Result<Connection, ShellError> {
411 if path.to_string_lossy() == MEMORY_DB {
412 open_connection_in_memory_custom()
413 } else {
414 let path = path.to_string_lossy().to_string();
415 Connection::open(path).map_err(|err| {
416 ShellError::Generic(GenericError::new(
417 "Failed to open SQLite database",
418 err.to_string(),
419 call_span,
420 ))
421 })
422 }
423}
424
425fn run_sql_query(
426 conn: Connection,
427 sql: &Spanned<String>,
428 params: NuSqlParams,
429 signals: &Signals,
430 column_adapters: Option<&BTreeMap<String, SQLiteColumnAdapter>>,
431) -> Result<Value, SqliteOrShellError> {
432 let stmt = conn.prepare(&sql.item)?;
433 prepared_statement_to_nu_list(stmt, params, sql.span, signals, column_adapters)
434}
435
436pub fn value_to_sql(
438 engine_state: &EngineState,
439 value: Value,
440 call_span: Span,
441) -> Result<Box<dyn rusqlite::ToSql>, ShellError> {
442 match value {
443 Value::Bool { val, .. } => Ok(Box::new(val)),
444 Value::Int { val, .. } => Ok(Box::new(val)),
445 Value::Float { val, .. } => Ok(Box::new(val)),
446 Value::Filesize { val, .. } => Ok(Box::new(val.get())),
447 Value::Duration { val, .. } => Ok(Box::new(val)),
448 Value::Date { val, .. } => Ok(Box::new(val)),
449 Value::String { val, .. } => Ok(Box::new(val)),
450 Value::Binary { val, .. } => Ok(Box::new(val)),
451 Value::Nothing { .. } => Ok(Box::new(rusqlite::types::Null)),
452 val => {
453 let span = val.span();
454 let ty = val.get_type();
455 let json_value = crate::value_to_json_value(engine_state, val, call_span, false)?;
456 match nu_json::to_string_raw(&json_value) {
457 Ok(s) => Ok(Box::new(s)),
458 Err(err) => Err(ShellError::CantConvert {
459 to_type: "JSON".into(),
460 from_type: ty.to_string(),
461 span,
462 help: Some(err.to_string()),
463 }),
464 }
465 }
466 }
467}
468
469pub fn values_to_sql(
470 engine_state: &EngineState,
471 values: impl IntoIterator<Item = Value>,
472 call_span: Span,
473) -> Result<Vec<Box<dyn rusqlite::ToSql>>, ShellError> {
474 values
475 .into_iter()
476 .map(|v| value_to_sql(engine_state, v, call_span))
477 .collect::<Result<Vec<_>, _>>()
478}
479
480pub enum NuSqlParams {
481 List(Vec<Box<dyn ToSql>>),
482 Named(Vec<(String, Box<dyn ToSql>)>),
483}
484
485impl Default for NuSqlParams {
486 fn default() -> Self {
487 NuSqlParams::List(Vec::new())
488 }
489}
490
491pub fn nu_value_to_params(
492 engine_state: &EngineState,
493 value: Value,
494 call_span: Span,
495) -> Result<NuSqlParams, ShellError> {
496 match value {
497 Value::Record { val, .. } => {
498 let mut params = Vec::with_capacity(val.len());
499
500 for (mut column, value) in val.into_owned().into_iter() {
501 let sql_type_erased = value_to_sql(engine_state, value, call_span)?;
502
503 if !column.starts_with([':', '@', '$']) {
504 column.insert(0, ':');
505 }
506
507 params.push((column, sql_type_erased));
508 }
509
510 Ok(NuSqlParams::Named(params))
511 }
512 Value::List { vals, .. } => {
513 let mut params = Vec::with_capacity(vals.len());
514
515 for value in vals.into_iter() {
516 let sql_type_erased = value_to_sql(engine_state, value, call_span)?;
517
518 params.push(sql_type_erased);
519 }
520
521 Ok(NuSqlParams::List(params))
522 }
523
524 Value::Nothing { .. } => Ok(NuSqlParams::default()),
526
527 _ => Err(ShellError::TypeMismatch {
528 err_message: "Invalid parameters value: expected record or list".to_string(),
529 span: value.span(),
530 }),
531 }
532}
533
534#[derive(Debug)]
535enum SqliteOrShellError {
536 SqliteError(SqliteError),
537 ShellError(ShellError),
538}
539
540impl From<SqliteError> for SqliteOrShellError {
541 fn from(error: SqliteError) -> Self {
542 Self::SqliteError(error)
543 }
544}
545
546impl From<ShellError> for SqliteOrShellError {
547 fn from(error: ShellError) -> Self {
548 Self::ShellError(error)
549 }
550}
551
552impl SqliteOrShellError {
553 fn into_shell_error(self, span: Span, msg: &str) -> ShellError {
554 match self {
555 Self::SqliteError(err) => {
556 ShellError::Generic(GenericError::new(msg.to_string(), err.to_string(), span))
557 }
558 Self::ShellError(err) => err,
559 }
560 }
561}
562
563#[derive(Clone, Copy)]
565pub enum DeclType {
566 Json,
567 Jsonb,
568}
569
570impl DeclType {
571 pub fn from_str(s: &str) -> Option<Self> {
572 match s.to_uppercase().as_str() {
573 "JSON" => Some(DeclType::Json),
574 "JSONB" => Some(DeclType::Jsonb),
575 _ => None, }
577 }
578}
579
580pub struct TypedColumn {
582 pub name: String,
583 pub decl_type: Option<DeclType>,
584}
585
586impl TypedColumn {
587 pub fn from_rusqlite_column(c: &rusqlite::Column) -> Self {
588 Self {
589 name: c.name().to_owned(),
590 decl_type: c.decl_type().and_then(DeclType::from_str),
591 }
592 }
593}
594
595fn prepared_statement_to_nu_list(
596 mut stmt: Statement,
597 params: NuSqlParams,
598 call_span: Span,
599 signals: &Signals,
600 column_adapters: Option<&BTreeMap<String, SQLiteColumnAdapter>>,
601) -> Result<Value, SqliteOrShellError> {
602 let columns: Vec<TypedColumn> = stmt
603 .columns()
604 .iter()
605 .map(TypedColumn::from_rusqlite_column)
606 .collect();
607
608 fn collect_row_values(
609 row_results: impl IntoIterator<Item = Result<Value, SqliteError>>,
610 signals: &Signals,
611 call_span: Span,
612 ) -> Result<Vec<Value>, SqliteOrShellError> {
613 let mut row_values = vec![];
614
615 for row_result in row_results {
616 signals.check(&call_span)?;
617 if let Ok(row_value) = row_result {
618 row_values.push(row_value);
619 }
620 }
621
622 Ok(row_values)
623 }
624
625 let row_values = match params {
629 NuSqlParams::List(params) => {
630 let refs: Vec<&dyn ToSql> = params.iter().map(|value| &**value).collect();
631
632 let row_results = stmt.query_map(refs.as_slice(), |row| {
633 Ok(convert_sqlite_row_to_nu_value(
634 row,
635 call_span,
636 &columns,
637 column_adapters,
638 ))
639 })?;
640
641 collect_row_values(row_results, signals, call_span)?
642 }
643 NuSqlParams::Named(pairs) => {
644 let refs: Vec<_> = pairs
645 .iter()
646 .map(|(column, value)| (column.as_str(), &**value))
647 .collect();
648
649 let row_results = stmt.query_map(refs.as_slice(), |row| {
650 Ok(convert_sqlite_row_to_nu_value(
651 row,
652 call_span,
653 &columns,
654 column_adapters,
655 ))
656 })?;
657
658 collect_row_values(row_results, signals, call_span)?
659 }
660 };
661
662 Ok(Value::list(row_values, call_span))
663}
664
665fn read_entire_sqlite_db(
666 conn: Connection,
667 call_span: Span,
668 signals: &Signals,
669) -> Result<Value, SqliteOrShellError> {
670 let mut tables = Record::new();
671
672 let mut get_table_names =
673 conn.prepare("SELECT name FROM sqlite_master WHERE type = 'table'")?;
674 let rows = get_table_names.query_map([], |row| row.get(0))?;
675
676 for row in rows {
677 let table_name: String = row?;
678 let table_stmt = conn.prepare(&format!("select * from [{table_name}]"))?;
680 let rows = prepared_statement_to_nu_list(
681 table_stmt,
682 NuSqlParams::default(),
683 call_span,
684 signals,
685 None,
686 )?;
687 tables.push(table_name, rows);
688 }
689
690 Ok(Value::record(tables, call_span))
691}
692
693pub fn convert_sqlite_row_to_nu_value(
694 row: &Row,
695 span: Span,
696 columns: &[TypedColumn],
697 column_adapters: Option<&BTreeMap<String, SQLiteColumnAdapter>>,
698) -> Value {
699 let record = columns
700 .iter()
701 .enumerate()
702 .map(|(i, col)| {
703 let adapter = column_adapters
704 .and_then(|adapters| adapters.get(&col.name))
705 .copied();
706 (
707 col.name.clone(),
708 convert_sqlite_value_to_nu_value_with_adapter(
709 row.get_ref_unwrap(i),
710 col.decl_type,
711 adapter,
712 span,
713 ),
714 )
715 })
716 .collect();
717
718 Value::record(record, span)
719}
720
721#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
722pub enum SQLiteColumnAdapter {
723 UnixMillisToDate,
725 MillisToDuration,
727}
728
729fn convert_sqlite_value_to_nu_value_with_adapter(
730 value: ValueRef,
731 decl_type: Option<DeclType>,
732 adapter: Option<SQLiteColumnAdapter>,
733 span: Span,
734) -> Value {
735 match adapter {
736 Some(SQLiteColumnAdapter::UnixMillisToDate) => match value {
737 ValueRef::Integer(i) => chrono::DateTime::from_timestamp_millis(i)
738 .map(|datetime| Value::date(datetime.into(), span))
739 .unwrap_or_else(|| Value::int(i, span)),
740 _ => convert_sqlite_value_to_nu_value(value, decl_type, span),
741 },
742 Some(SQLiteColumnAdapter::MillisToDuration) => match value {
743 ValueRef::Integer(i) => i
744 .checked_mul(1_000_000)
745 .map(|nanos| Value::duration(nanos, span))
746 .unwrap_or_else(|| Value::int(i, span)),
747 _ => convert_sqlite_value_to_nu_value(value, decl_type, span),
748 },
749 None => convert_sqlite_value_to_nu_value(value, decl_type, span),
750 }
751}
752
753pub fn convert_sqlite_value_to_nu_value(
754 value: ValueRef,
755 decl_type: Option<DeclType>,
756 span: Span,
757) -> Value {
758 match value {
759 ValueRef::Null => Value::nothing(span),
760 ValueRef::Integer(i) => Value::int(i, span),
761 ValueRef::Real(f) => Value::float(f, span),
762 ValueRef::Text(buf) => match (std::str::from_utf8(buf), decl_type) {
763 (Ok(txt), Some(DeclType::Json | DeclType::Jsonb)) => {
764 match crate::try_json_str_to_value(txt, span, false, &Signals::empty()) {
765 Ok(val) => val,
766 Err(err) => Value::error(err, span),
767 }
768 }
769 (Ok(txt), _) => Value::string(txt.to_string(), span),
770 (Err(_), _) => Value::error(ShellError::NonUtf8 { span }, span),
771 },
772 ValueRef::Blob(u) => Value::binary(u.to_vec(), span),
773 }
774}
775
776pub fn open_connection_in_memory_custom() -> Result<Connection, ShellError> {
777 let flags = OpenFlags::default();
778 let conn = Connection::open_with_flags(MEMORY_DB, flags).map_err(|e| {
779 ShellError::Generic(GenericError::new(
780 "Failed to open SQLite custom connection in memory",
781 e.to_string(),
782 Span::test_data(),
783 ))
784 })?;
785 conn.busy_handler(Some(SQLiteDatabase::sleeper))
786 .map_err(|e| {
787 ShellError::Generic(GenericError::new(
788 "Failed to set busy handler for SQLite custom connection in memory",
789 e.to_string(),
790 Span::test_data(),
791 ))
792 })?;
793 Ok(conn)
794}
795
796pub fn open_connection_in_memory() -> Result<Connection, ShellError> {
797 Connection::open_in_memory().map_err(|e| {
798 ShellError::Generic(GenericError::new(
799 "Failed to open SQLite standard connection in memory",
800 e.to_string(),
801 Span::test_data(),
802 ))
803 })
804}
805
806#[derive(Debug, Clone, Serialize, Deserialize)]
809pub struct SQLiteQueryBuilder {
810 pub db_path: PathBuf,
811 pub table_name: String,
812 pub sql_select: Option<String>, pub sql_where: Option<String>, pub sql_params: Vec<String>, pub sql_order_by: Option<String>, pub sql_limit: Option<i64>,
817 pub sql_offset: Option<i64>,
818 pub sql_distinct: bool,
819 #[serde(default)]
820 pub column_adapters: BTreeMap<String, SQLiteColumnAdapter>,
821 #[serde(skip, default = "Signals::empty")]
822 signals: Signals,
823}
824
825impl SQLiteQueryBuilder {
826 pub fn new(db_path: PathBuf, table_name: String, signals: Signals) -> Self {
827 Self {
828 db_path,
829 table_name,
830 sql_select: None,
831 sql_where: None,
832 sql_params: Vec::new(),
833 sql_order_by: None,
834 sql_limit: None,
835 sql_offset: None,
836 sql_distinct: false,
837 column_adapters: BTreeMap::new(),
838 signals,
839 }
840 }
841
842 pub fn with_select(mut self, select: String) -> Self {
843 self.sql_select = Some(select);
844 self
845 }
846
847 pub fn with_where(mut self, where_clause: String, params: Vec<String>) -> Self {
848 self.sql_where = Some(where_clause);
849 self.sql_params = params;
850 self
851 }
852
853 pub fn with_order_by(mut self, order_by: String) -> Self {
854 self.sql_order_by = Some(order_by);
855 self
856 }
857
858 pub fn with_limit(mut self, limit: i64) -> Self {
859 self.sql_limit = Some(limit);
860 self
861 }
862
863 pub fn with_offset(mut self, offset: i64) -> Self {
864 self.sql_offset = Some(offset);
865 self
866 }
867
868 pub fn with_distinct(mut self) -> Self {
869 self.sql_distinct = true;
870 self
871 }
872
873 pub fn with_column_adapter(
874 mut self,
875 column_name: String,
876 adapter: SQLiteColumnAdapter,
877 ) -> Self {
878 self.column_adapters.insert(column_name, adapter);
879 self
880 }
881
882 pub fn with_unix_millis_datetime_column(self, column_name: String) -> Self {
884 self.with_column_adapter(column_name, SQLiteColumnAdapter::UnixMillisToDate)
885 }
886
887 pub fn with_millis_duration_column(self, column_name: String) -> Self {
889 self.with_column_adapter(column_name, SQLiteColumnAdapter::MillisToDuration)
890 }
891
892 pub fn project_output_columns(&self, columns: &[String]) -> Option<Self> {
909 if columns.is_empty() {
910 return Some(self.clone());
911 }
912
913 let new_select = if let Some(select) = &self.sql_select {
914 let current = parse_sql_select_projection(select)?;
917 let mut projected = Vec::with_capacity(columns.len());
918
919 for requested in columns {
920 let expression = current.iter().find_map(|(output_name, expression)| {
922 output_name
923 .eq_ignore_ascii_case(requested)
924 .then_some(expression)
925 })?;
926 projected.push(expression.clone());
927 }
928
929 projected.join(", ")
930 } else {
931 columns.join(", ")
932 };
933
934 Some(self.clone().with_select(new_select))
935 }
936
937 pub fn build_sql(&self) -> String {
938 let distinct = if self.sql_distinct { "DISTINCT " } else { "" };
939 let select = self.sql_select.as_deref().unwrap_or("*");
940 let mut sql = format!("SELECT {distinct}{select} FROM [{}]", self.table_name);
941
942 if let Some(where_clause) = &self.sql_where {
943 write!(sql, " WHERE {}", where_clause).expect("writing to a String is infallible");
944 }
945
946 if let Some(order_by) = &self.sql_order_by {
947 write!(sql, " ORDER BY {}", order_by).expect("writing to a String is infallible");
948 }
949
950 match (self.sql_limit, self.sql_offset) {
951 (Some(limit), Some(offset)) => {
952 write!(sql, " LIMIT {limit} OFFSET {offset}")
953 .expect("writing to a String is infallible");
954 }
955 (Some(limit), None) => {
956 write!(sql, " LIMIT {limit}").expect("writing to a String is infallible");
957 }
958 (None, Some(offset)) => {
959 write!(sql, " LIMIT -1 OFFSET {offset}")
960 .expect("writing to a String is infallible");
961 }
962 (None, None) => {}
963 }
964
965 sql
966 }
967
968 pub fn execute(&self, call_span: Span) -> Result<PipelineData, ShellError> {
969 let conn = open_sqlite_db(&self.db_path, call_span)?;
970 let sql = self.build_sql();
971 let params = NuSqlParams::List(Vec::new()); let query = Spanned {
973 item: sql,
974 span: call_span,
975 };
976 run_sql_query(
977 conn,
978 &query,
979 params,
980 &self.signals,
981 (!self.column_adapters.is_empty()).then_some(&self.column_adapters),
982 )
983 .map(IntoPipelineData::into_pipeline_data)
984 .map_err(|e| e.into_shell_error(call_span, "Failed to execute query"))
985 }
986
987 pub fn count(&self, call_span: Span) -> Result<i64, ShellError> {
988 let conn = open_sqlite_db(&self.db_path, call_span)?;
989 let mut sql = format!("SELECT COUNT(*) FROM [{}]", self.table_name);
990 if let Some(where_clause) = &self.sql_where {
991 write!(sql, " WHERE {}", where_clause).expect("writing to a String is infallible");
992 }
993 let mut stmt = conn.prepare(&sql).map_err(|e| {
994 ShellError::Generic(GenericError::new(
995 "Failed to prepare count query",
996 e.to_string(),
997 call_span,
998 ))
999 })?;
1000 let params: Vec<Box<dyn ToSql>> = self
1001 .sql_params
1002 .iter()
1003 .map(|s| Box::new(s.clone()) as Box<dyn ToSql>)
1004 .collect();
1005 let count: i64 = stmt
1006 .query_row(rusqlite::params_from_iter(params), |row| row.get(0))
1007 .map_err(|e| {
1008 ShellError::Generic(GenericError::new(
1009 "Failed to execute count query",
1010 e.to_string(),
1011 call_span,
1012 ))
1013 })?;
1014 Ok(count)
1015 }
1016}
1017
1018fn parse_sql_select_projection(select: &str) -> Option<Vec<(String, String)>> {
1028 let projection = split_select_expressions(select)
1029 .into_iter()
1030 .map(|expr| parse_projection_expression(&expr))
1031 .collect::<Option<Vec<_>>>()?;
1032
1033 (!projection.is_empty()).then_some(projection)
1034}
1035
1036fn split_select_expressions(select: &str) -> Vec<String> {
1044 let mut expressions = Vec::new();
1045 let mut current = String::new();
1046 let mut depth = 0usize;
1047 let mut quote = None;
1048
1049 for ch in select.chars() {
1050 match ch {
1051 '\'' | '"' => {
1052 if quote == Some(ch) {
1054 quote = None;
1055 } else if quote.is_none() {
1056 quote = Some(ch);
1057 }
1058 current.push(ch);
1059 }
1060 '(' if quote.is_none() => {
1061 depth = depth.saturating_add(1);
1063 current.push(ch);
1064 }
1065 ')' if quote.is_none() => {
1066 depth = depth.saturating_sub(1);
1067 current.push(ch);
1068 }
1069 ',' if quote.is_none() && depth == 0 => {
1070 let trimmed = current.trim();
1072 if !trimmed.is_empty() {
1073 expressions.push(trimmed.to_string());
1074 }
1075 current.clear();
1076 }
1077 _ => current.push(ch),
1078 }
1079 }
1080
1081 let trimmed = current.trim();
1082 if !trimmed.is_empty() {
1083 expressions.push(trimmed.to_string());
1084 }
1085
1086 expressions
1087}
1088
1089fn parse_projection_expression(expr: &str) -> Option<(String, String)> {
1099 let trimmed = expr.trim();
1100 if trimmed.is_empty() {
1101 return None;
1102 }
1103
1104 if let Some((_lhs, rhs)) = split_alias(trimmed) {
1105 let alias = normalize_identifier(rhs.trim());
1107 if alias.is_empty() {
1108 return None;
1109 }
1110 return Some((alias, trimmed.to_string()));
1111 }
1112
1113 let output_name = normalize_identifier(last_identifier_segment(trimmed));
1114 if output_name.is_empty() {
1115 return None;
1116 }
1117
1118 Some((output_name, trimmed.to_string()))
1119}
1120
1121fn split_alias(expr: &str) -> Option<(&str, &str)> {
1128 let bytes = expr.as_bytes();
1129 for idx in 0..bytes.len().saturating_sub(2) {
1130 if idx > 0
1131 && bytes[idx - 1].is_ascii_whitespace()
1132 && bytes[idx + 2].is_ascii_whitespace()
1133 && bytes[idx].eq_ignore_ascii_case(&b'a')
1134 && bytes[idx + 1].eq_ignore_ascii_case(&b's')
1135 {
1136 let lhs = expr[..idx].trim_end();
1139 let rhs = expr[idx + 2..].trim_start();
1140 if !lhs.is_empty() && !rhs.is_empty() {
1141 return Some((lhs, rhs));
1142 }
1143 }
1144 }
1145
1146 None
1147}
1148
1149fn last_identifier_segment(expr: &str) -> &str {
1150 expr.rsplit('.').next().unwrap_or(expr)
1151}
1152
1153fn normalize_identifier(identifier: &str) -> String {
1159 let trimmed = identifier.trim();
1160 if trimmed.len() >= 2 {
1161 let first = trimmed.as_bytes()[0] as char;
1162 let last = trimmed.as_bytes()[trimmed.len() - 1] as char;
1163 let is_wrapped = matches!((first, last), ('"', '"') | ('`', '`') | ('[', ']'));
1164 if is_wrapped {
1165 return trimmed[1..trimmed.len() - 1].trim().to_string();
1166 }
1167 }
1168
1169 trimmed.to_string()
1170}
1171
1172impl CustomValue for SQLiteQueryBuilder {
1173 fn clone_value(&self, span: Span) -> Value {
1174 Value::custom(Box::new(self.clone()), span)
1175 }
1176
1177 fn type_name(&self) -> String {
1178 "SQLiteQueryBuilder".to_string()
1179 }
1180
1181 fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
1182 self.execute(span).and_then(|pd| pd.into_value(span))
1183 }
1184
1185 fn as_any(&self) -> &dyn std::any::Any {
1186 self
1187 }
1188
1189 fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
1190 self
1191 }
1192
1193 fn follow_path_int(
1194 &self,
1195 _self_span: Span,
1196 index: usize,
1197 path_span: Span,
1198 optional: bool,
1199 ) -> Result<Value, ShellError> {
1200 let data = self.to_base_value(path_span)?;
1202 data.follow_cell_path(&[ast::PathMember::Int {
1203 val: index,
1204 span: path_span,
1205 optional,
1206 }])
1207 .map(|v| v.into_owned())
1208 }
1209
1210 fn follow_path_string(
1211 &self,
1212 _self_span: Span,
1213 column_name: String,
1214 path_span: Span,
1215 _optional: bool,
1216 _: Casing,
1217 ) -> Result<Value, ShellError> {
1218 let data = self.to_base_value(path_span)?;
1220 data.follow_cell_path(&[ast::PathMember::String {
1221 val: column_name,
1222 span: path_span,
1223 optional: false,
1224 casing: Casing::default(),
1225 }])
1226 .map(|v| v.into_owned())
1227 }
1228
1229 fn typetag_name(&self) -> &'static str {
1230 "SQLiteQueryBuilder"
1231 }
1232
1233 fn typetag_deserialize(&self) {
1234 unimplemented!("typetag_deserialize")
1235 }
1236
1237 fn is_iterable(&self) -> bool {
1238 true
1239 }
1240}
1241
1242#[cfg(test)]
1243mod test {
1244 use super::*;
1245 use nu_protocol::record;
1246
1247 #[test]
1248 fn can_read_empty_db() {
1249 let db = open_connection_in_memory().unwrap();
1250 let converted_db = read_entire_sqlite_db(db, Span::test_data(), &Signals::empty()).unwrap();
1251
1252 let expected = Value::test_record(Record::new());
1253
1254 assert_eq!(converted_db, expected);
1255 }
1256
1257 #[test]
1258 fn can_read_empty_table() {
1259 let db = open_connection_in_memory().unwrap();
1260
1261 db.execute(
1262 "CREATE TABLE person (
1263 id INTEGER PRIMARY KEY,
1264 name TEXT NOT NULL,
1265 data BLOB
1266 )",
1267 [],
1268 )
1269 .unwrap();
1270 let converted_db = read_entire_sqlite_db(db, Span::test_data(), &Signals::empty()).unwrap();
1271
1272 let expected = Value::test_record(record! {
1273 "person" => Value::test_list(vec![]),
1274 });
1275
1276 assert_eq!(converted_db, expected);
1277 }
1278
1279 #[test]
1280 fn can_read_null_and_non_null_data() {
1281 let span = Span::test_data();
1282 let db = open_connection_in_memory().unwrap();
1283
1284 db.execute(
1285 "CREATE TABLE item (
1286 id INTEGER PRIMARY KEY,
1287 name TEXT
1288 )",
1289 [],
1290 )
1291 .unwrap();
1292
1293 db.execute("INSERT INTO item (id, name) VALUES (123, NULL)", [])
1294 .unwrap();
1295
1296 db.execute("INSERT INTO item (id, name) VALUES (456, 'foo bar')", [])
1297 .unwrap();
1298
1299 let converted_db = read_entire_sqlite_db(db, span, &Signals::empty()).unwrap();
1300
1301 let expected = Value::test_record(record! {
1302 "item" => Value::test_list(
1303 vec![
1304 Value::test_record(record! {
1305 "id" => Value::test_int(123),
1306 "name" => Value::nothing(span),
1307 }),
1308 Value::test_record(record! {
1309 "id" => Value::test_int(456),
1310 "name" => Value::test_string("foo bar"),
1311 }),
1312 ]
1313 ),
1314 });
1315
1316 assert_eq!(converted_db, expected);
1317 }
1318
1319 #[test]
1320 fn sqlite_table_build_sql_combined() {
1321 let table = SQLiteQueryBuilder::new(
1322 PathBuf::from(":memory:"),
1323 "test".to_string(),
1324 Signals::empty(),
1325 )
1326 .with_select("col1".to_string())
1327 .with_where("col2 = ?".to_string(), vec!["val".to_string()])
1328 .with_order_by("col1".to_string())
1329 .with_limit(5);
1330 assert_eq!(
1331 table.build_sql(),
1332 "SELECT col1 FROM [test] WHERE col2 = ? ORDER BY col1 LIMIT 5"
1333 );
1334 }
1335
1336 #[test]
1337 fn sqlite_table_count_integration() {
1338 use tempfile::NamedTempFile;
1339
1340 let temp_file = NamedTempFile::new().unwrap();
1341 let db_path = temp_file.path().to_path_buf();
1342 let signals = Signals::empty();
1343
1344 {
1346 let conn = Connection::open(&db_path).unwrap();
1347 conn.execute("CREATE TABLE test (id INTEGER, name TEXT)", [])
1348 .unwrap();
1349 for i in 0..10 {
1350 conn.execute(
1351 "INSERT INTO test (id, name) VALUES (?, ?)",
1352 rusqlite::params![i, format!("name{}", i)],
1353 )
1354 .unwrap();
1355 }
1356 }
1357
1358 let table = SQLiteQueryBuilder::new(db_path, "test".to_string(), signals);
1359 let count = table.count(Span::test_data()).unwrap();
1360 assert_eq!(count, 10);
1361 }
1362
1363 #[test]
1364 fn sqlite_table_execute_integration() {
1365 use tempfile::NamedTempFile;
1366
1367 let temp_file = NamedTempFile::new().unwrap();
1368 let db_path = temp_file.path().to_path_buf();
1369 let signals = Signals::empty();
1370
1371 {
1373 let conn = Connection::open(&db_path).unwrap();
1374 conn.execute("CREATE TABLE test (id INTEGER, name TEXT)", [])
1375 .unwrap();
1376 conn.execute("INSERT INTO test (id, name) VALUES (1, 'first')", [])
1377 .unwrap();
1378 conn.execute("INSERT INTO test (id, name) VALUES (2, 'second')", [])
1379 .unwrap();
1380 }
1381
1382 let table = SQLiteQueryBuilder::new(db_path, "test".to_string(), signals);
1383 let result = table.execute(Span::test_data()).unwrap();
1384 let value = result.into_value(Span::test_data()).unwrap();
1385
1386 if let Value::List { vals, .. } = value {
1387 assert_eq!(vals.len(), 2);
1388 } else {
1389 panic!("Expected list");
1390 }
1391 }
1392
1393 #[test]
1394 fn sqlite_table_first_integration() {
1395 use tempfile::NamedTempFile;
1396
1397 let temp_file = NamedTempFile::new().unwrap();
1398 let db_path = temp_file.path().to_path_buf();
1399 let signals = Signals::empty();
1400
1401 {
1403 let conn = Connection::open(&db_path).unwrap();
1404 conn.execute("CREATE TABLE test (id INTEGER, name TEXT)", [])
1405 .unwrap();
1406 for i in 0..5 {
1407 conn.execute(
1408 "INSERT INTO test (id, name) VALUES (?, ?)",
1409 rusqlite::params![i, format!("name{}", i)],
1410 )
1411 .unwrap();
1412 }
1413 }
1414
1415 let table = SQLiteQueryBuilder::new(db_path, "test".to_string(), signals).with_limit(2);
1416 let result = table.execute(Span::test_data()).unwrap();
1417 let value = result.into_value(Span::test_data()).unwrap();
1418
1419 if let Value::List { vals, .. } = value {
1420 assert_eq!(vals.len(), 2);
1421 if let Value::Record { val: record, .. } = &vals[0] {
1423 assert_eq!(record.get("id"), Some(&Value::int(0, Span::test_data())));
1424 }
1425 } else {
1426 panic!("Expected list");
1427 }
1428 }
1429
1430 #[test]
1431 fn sqlite_table_last_integration() {
1432 use tempfile::NamedTempFile;
1433
1434 let temp_file = NamedTempFile::new().unwrap();
1435 let db_path = temp_file.path().to_path_buf();
1436 let signals = Signals::empty();
1437
1438 {
1440 let conn = Connection::open(&db_path).unwrap();
1441 conn.execute("CREATE TABLE test (id INTEGER, name TEXT)", [])
1442 .unwrap();
1443 for i in 0..5 {
1444 conn.execute(
1445 "INSERT INTO test (id, name) VALUES (?, ?)",
1446 rusqlite::params![i, format!("name{}", i)],
1447 )
1448 .unwrap();
1449 }
1450 }
1451
1452 let table = SQLiteQueryBuilder::new(db_path, "test".to_string(), signals)
1453 .with_order_by("rowid DESC".to_string())
1454 .with_limit(2);
1455 let result = table.execute(Span::test_data()).unwrap();
1456 let value = result.into_value(Span::test_data()).unwrap();
1457
1458 if let Value::List { vals, .. } = value {
1459 assert_eq!(vals.len(), 2);
1460 if let Value::Record { val: record, .. } = &vals[0] {
1462 assert_eq!(record.get("id"), Some(&Value::int(4, Span::test_data())));
1463 }
1464 } else {
1465 panic!("Expected list");
1466 }
1467 }
1468
1469 #[test]
1470 fn sqlite_table_build_sql_with_select() {
1471 let table = SQLiteQueryBuilder::new(
1472 PathBuf::from(":memory:"),
1473 "test".to_string(),
1474 Signals::empty(),
1475 )
1476 .with_select("col1, col2".to_string());
1477 assert_eq!(table.build_sql(), "SELECT col1, col2 FROM [test]");
1478 }
1479
1480 #[test]
1481 fn sqlite_table_build_sql_with_where() {
1482 let table = SQLiteQueryBuilder::new(
1483 PathBuf::from(":memory:"),
1484 "test".to_string(),
1485 Signals::empty(),
1486 )
1487 .with_where("col = ?".to_string(), vec!["val".to_string()]);
1488 assert_eq!(table.build_sql(), "SELECT * FROM [test] WHERE col = ?");
1489 }
1490
1491 #[test]
1492 fn sqlite_table_build_sql_with_order_by() {
1493 let table = SQLiteQueryBuilder::new(
1494 PathBuf::from(":memory:"),
1495 "test".to_string(),
1496 Signals::empty(),
1497 )
1498 .with_order_by("id DESC".to_string());
1499 assert_eq!(table.build_sql(), "SELECT * FROM [test] ORDER BY id DESC");
1500 }
1501
1502 #[test]
1503 fn sqlite_table_build_sql_with_limit() {
1504 let table = SQLiteQueryBuilder::new(
1505 PathBuf::from(":memory:"),
1506 "test".to_string(),
1507 Signals::empty(),
1508 )
1509 .with_limit(10);
1510 assert_eq!(table.build_sql(), "SELECT * FROM [test] LIMIT 10");
1511 }
1512
1513 #[test]
1514 fn sqlite_table_execute_with_column_adapters() {
1515 use tempfile::NamedTempFile;
1516
1517 let temp_file = NamedTempFile::new().unwrap();
1518 let db_path = temp_file.path().to_path_buf();
1519 let signals = Signals::empty();
1520
1521 {
1522 let conn = Connection::open(&db_path).unwrap();
1523 conn.execute(
1524 "CREATE TABLE history (start_timestamp INTEGER, duration INTEGER)",
1525 [],
1526 )
1527 .unwrap();
1528 conn.execute(
1529 "INSERT INTO history (start_timestamp, duration) VALUES (1736041045123, 30002)",
1530 [],
1531 )
1532 .unwrap();
1533 conn.execute(
1534 "INSERT INTO history (start_timestamp, duration) VALUES (NULL, NULL)",
1535 [],
1536 )
1537 .unwrap();
1538 }
1539
1540 let table = SQLiteQueryBuilder::new(db_path, "history".to_string(), signals)
1541 .with_select("start_timestamp, duration".to_string())
1542 .with_unix_millis_datetime_column("start_timestamp".to_string())
1543 .with_millis_duration_column("duration".to_string());
1544
1545 let result = table.execute(Span::test_data()).unwrap();
1546 let value = result.into_value(Span::test_data()).unwrap();
1547
1548 if let Value::List { vals, .. } = value {
1549 assert_eq!(vals.len(), 2);
1550
1551 if let Value::Record { val: first, .. } = &vals[0] {
1552 assert!(matches!(
1553 first.get("start_timestamp"),
1554 Some(Value::Date { .. })
1555 ));
1556 assert!(matches!(
1557 first.get("duration"),
1558 Some(Value::Duration { .. })
1559 ));
1560 } else {
1561 panic!("Expected first row to be a record");
1562 }
1563
1564 if let Value::Record { val: second, .. } = &vals[1] {
1565 assert!(matches!(
1566 second.get("start_timestamp"),
1567 Some(Value::Nothing { .. })
1568 ));
1569 assert!(matches!(
1570 second.get("duration"),
1571 Some(Value::Nothing { .. })
1572 ));
1573 } else {
1574 panic!("Expected second row to be a record");
1575 }
1576 } else {
1577 panic!("Expected list");
1578 }
1579 }
1580
1581 #[test]
1582 fn sqlite_table_project_output_columns_preserves_aliases() {
1583 let table = SQLiteQueryBuilder::new(
1584 PathBuf::from(":memory:"),
1585 "history".to_string(),
1586 Signals::empty(),
1587 )
1588 .with_select(
1589 "start_timestamp, command_line as command, cwd, duration_ms as duration, exit_status"
1590 .to_string(),
1591 );
1592
1593 let projected = table
1594 .project_output_columns(&["command".to_string(), "duration".to_string()])
1595 .expect("projection should succeed");
1596
1597 assert_eq!(
1598 projected.build_sql(),
1599 "SELECT command_line as command, duration_ms as duration FROM [history]"
1600 );
1601 }
1602
1603 #[test]
1604 fn sqlite_table_project_output_columns_returns_none_for_missing_column() {
1605 let table = SQLiteQueryBuilder::new(
1606 PathBuf::from(":memory:"),
1607 "history".to_string(),
1608 Signals::empty(),
1609 )
1610 .with_select("command_line as command".to_string());
1611
1612 assert!(
1613 table
1614 .project_output_columns(&["missing".to_string()])
1615 .is_none()
1616 );
1617 }
1618}