2022-11-09 22:14:48 +00:00
|
|
|
use super::definitions::{
|
2022-12-04 02:16:57 +00:00
|
|
|
db_column::DbColumn, db_constraint::DbConstraint, db_foreignkey::DbForeignKey,
|
2022-04-26 19:20:59 +00:00
|
|
|
db_index::DbIndex, db_table::DbTable,
|
2022-04-24 09:29:21 +00:00
|
|
|
};
|
2022-11-09 22:14:48 +00:00
|
|
|
|
2022-04-24 09:29:21 +00:00
|
|
|
use nu_protocol::{CustomValue, PipelineData, ShellError, Span, Spanned, Value};
|
2022-04-20 04:58:21 +00:00
|
|
|
use rusqlite::{types::ValueRef, Connection, Row};
|
2022-04-27 10:52:31 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2022-04-26 19:20:59 +00:00
|
|
|
use std::{
|
|
|
|
fs::File,
|
|
|
|
io::Read,
|
|
|
|
path::{Path, PathBuf},
|
2022-12-15 17:39:24 +00:00
|
|
|
sync::{atomic::AtomicBool, Arc},
|
2022-04-26 19:20:59 +00:00
|
|
|
};
|
2022-04-24 09:29:21 +00:00
|
|
|
|
|
|
|
const SQLITE_MAGIC_BYTES: &[u8] = "SQLite format 3\0".as_bytes();
|
|
|
|
|
2022-04-27 10:52:31 +00:00
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
2022-04-20 04:58:21 +00:00
|
|
|
pub struct SQLiteDatabase {
|
|
|
|
// I considered storing a SQLite connection here, but decided against it because
|
|
|
|
// 1) YAGNI, 2) it's not obvious how cloning a connection could work, 3) state
|
|
|
|
// management gets tricky quick. Revisit this approach if we find a compelling use case.
|
2022-11-09 22:14:48 +00:00
|
|
|
pub path: PathBuf,
|
2022-12-05 00:49:47 +00:00
|
|
|
#[serde(skip)]
|
|
|
|
// this understandably can't be serialized. think that's OK, I'm not aware of a
|
|
|
|
// reason why a CustomValue would be serialized outside of a plugin
|
|
|
|
ctrlc: Option<Arc<AtomicBool>>,
|
2022-04-24 09:29:21 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 04:58:21 +00:00
|
|
|
impl SQLiteDatabase {
|
2022-12-05 00:49:47 +00:00
|
|
|
pub fn new(path: &Path, ctrlc: Option<Arc<AtomicBool>>) -> Self {
|
2022-04-24 09:29:21 +00:00
|
|
|
Self {
|
2022-11-09 22:14:48 +00:00
|
|
|
path: PathBuf::from(path),
|
2022-12-05 00:49:47 +00:00
|
|
|
ctrlc,
|
2022-04-20 04:58:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-05 00:49:47 +00:00
|
|
|
pub fn try_from_path(
|
|
|
|
path: &Path,
|
|
|
|
span: Span,
|
|
|
|
ctrlc: Option<Arc<AtomicBool>>,
|
|
|
|
) -> Result<Self, ShellError> {
|
2022-04-27 10:52:31 +00:00
|
|
|
let mut file =
|
|
|
|
File::open(path).map_err(|e| ShellError::ReadingFile(e.to_string(), span))?;
|
2022-04-24 09:29:21 +00:00
|
|
|
|
|
|
|
let mut buf: [u8; 16] = [0; 16];
|
|
|
|
file.read_exact(&mut buf)
|
2022-04-27 10:52:31 +00:00
|
|
|
.map_err(|e| ShellError::ReadingFile(e.to_string(), span))
|
2022-04-24 09:29:21 +00:00
|
|
|
.and_then(|_| {
|
|
|
|
if buf == SQLITE_MAGIC_BYTES {
|
2022-12-05 00:49:47 +00:00
|
|
|
Ok(SQLiteDatabase::new(path, ctrlc))
|
2022-04-24 09:29:21 +00:00
|
|
|
} else {
|
2022-04-27 10:52:31 +00:00
|
|
|
Err(ShellError::ReadingFile("Not a SQLite file".into(), span))
|
2022-04-24 09:29:21 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-04-27 10:52:31 +00:00
|
|
|
pub fn try_from_value(value: Value) -> Result<Self, ShellError> {
|
|
|
|
match value {
|
|
|
|
Value::CustomValue { val, span } => match val.as_any().downcast_ref::<Self>() {
|
|
|
|
Some(db) => Ok(Self {
|
2022-11-09 22:14:48 +00:00
|
|
|
path: db.path.clone(),
|
2022-12-05 00:49:47 +00:00
|
|
|
ctrlc: db.ctrlc.clone(),
|
2022-04-27 10:52:31 +00:00
|
|
|
}),
|
|
|
|
None => Err(ShellError::CantConvert(
|
|
|
|
"database".into(),
|
|
|
|
"non-database".into(),
|
|
|
|
span,
|
|
|
|
None,
|
|
|
|
)),
|
|
|
|
},
|
|
|
|
x => Err(ShellError::CantConvert(
|
|
|
|
"database".into(),
|
|
|
|
x.get_type().to_string(),
|
|
|
|
x.span()?,
|
|
|
|
None,
|
|
|
|
)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn try_from_pipeline(input: PipelineData, span: Span) -> Result<Self, ShellError> {
|
|
|
|
let value = input.into_value(span);
|
|
|
|
Self::try_from_value(value)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn into_value(self, span: Span) -> Value {
|
|
|
|
Value::CustomValue {
|
|
|
|
val: Box::new(self),
|
|
|
|
span,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-20 04:58:21 +00:00
|
|
|
pub fn query(&self, sql: &Spanned<String>, call_span: Span) -> Result<Value, ShellError> {
|
2022-11-09 22:14:48 +00:00
|
|
|
let db = open_sqlite_db(&self.path, call_span)?;
|
2022-12-05 00:49:47 +00:00
|
|
|
|
|
|
|
let stream = run_sql_query(db, sql, self.ctrlc.clone()).map_err(|e| {
|
2022-04-24 09:29:21 +00:00
|
|
|
ShellError::GenericError(
|
|
|
|
"Failed to query SQLite database".into(),
|
|
|
|
e.to_string(),
|
|
|
|
Some(sql.span),
|
|
|
|
None,
|
|
|
|
Vec::new(),
|
|
|
|
)
|
2022-12-05 00:49:47 +00:00
|
|
|
})?;
|
|
|
|
|
|
|
|
Ok(stream)
|
2022-04-24 09:29:21 +00:00
|
|
|
}
|
|
|
|
|
2022-04-26 19:20:59 +00:00
|
|
|
pub fn open_connection(&self) -> Result<Connection, rusqlite::Error> {
|
2023-01-24 11:23:42 +00:00
|
|
|
Connection::open(&self.path)
|
2022-04-26 19:20:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_tables(&self, conn: &Connection) -> Result<Vec<DbTable>, rusqlite::Error> {
|
|
|
|
let mut table_names =
|
|
|
|
conn.prepare("SELECT name FROM sqlite_master WHERE type = 'table'")?;
|
|
|
|
let rows = table_names.query_map([], |row| row.get(0))?;
|
|
|
|
let mut tables = Vec::new();
|
|
|
|
|
|
|
|
for row in rows {
|
|
|
|
let table_name: String = row?;
|
|
|
|
tables.push(DbTable {
|
|
|
|
name: table_name,
|
|
|
|
create_time: None,
|
|
|
|
update_time: None,
|
|
|
|
engine: None,
|
|
|
|
schema: None,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(tables.into_iter().collect())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_column_info(&self, row: &Row) -> Result<DbColumn, rusqlite::Error> {
|
|
|
|
let dbc = DbColumn {
|
|
|
|
cid: row.get("cid")?,
|
|
|
|
name: row.get("name")?,
|
|
|
|
r#type: row.get("type")?,
|
|
|
|
notnull: row.get("notnull")?,
|
|
|
|
default: row.get("dflt_value")?,
|
|
|
|
pk: row.get("pk")?,
|
|
|
|
};
|
|
|
|
Ok(dbc)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_columns(
|
|
|
|
&self,
|
|
|
|
conn: &Connection,
|
|
|
|
table: &DbTable,
|
|
|
|
) -> Result<Vec<DbColumn>, rusqlite::Error> {
|
|
|
|
let mut column_names = conn.prepare(&format!(
|
|
|
|
"SELECT * FROM pragma_table_info('{}');",
|
|
|
|
table.name
|
|
|
|
))?;
|
|
|
|
|
|
|
|
let mut columns: Vec<DbColumn> = Vec::new();
|
|
|
|
let rows = column_names.query_and_then([], |row| self.get_column_info(row))?;
|
|
|
|
|
|
|
|
for row in rows {
|
|
|
|
columns.push(row?);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(columns)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_constraint_info(&self, row: &Row) -> Result<DbConstraint, rusqlite::Error> {
|
|
|
|
let dbc = DbConstraint {
|
|
|
|
name: row.get("index_name")?,
|
|
|
|
column_name: row.get("column_name")?,
|
|
|
|
origin: row.get("origin")?,
|
|
|
|
};
|
|
|
|
Ok(dbc)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_constraints(
|
|
|
|
&self,
|
|
|
|
conn: &Connection,
|
|
|
|
table: &DbTable,
|
|
|
|
) -> Result<Vec<DbConstraint>, rusqlite::Error> {
|
|
|
|
let mut column_names = conn.prepare(&format!(
|
|
|
|
"
|
|
|
|
SELECT
|
|
|
|
p.origin,
|
|
|
|
s.name AS index_name,
|
|
|
|
i.name AS column_name
|
|
|
|
FROM
|
|
|
|
sqlite_master s
|
|
|
|
JOIN pragma_index_list(s.tbl_name) p ON s.name = p.name,
|
|
|
|
pragma_index_info(s.name) i
|
|
|
|
WHERE
|
|
|
|
s.type = 'index'
|
|
|
|
AND tbl_name = '{}'
|
|
|
|
AND NOT p.origin = 'c'
|
|
|
|
",
|
|
|
|
&table.name
|
|
|
|
))?;
|
|
|
|
|
|
|
|
let mut constraints: Vec<DbConstraint> = Vec::new();
|
|
|
|
let rows = column_names.query_and_then([], |row| self.get_constraint_info(row))?;
|
|
|
|
|
|
|
|
for row in rows {
|
|
|
|
constraints.push(row?);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(constraints)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_foreign_keys_info(&self, row: &Row) -> Result<DbForeignKey, rusqlite::Error> {
|
|
|
|
let dbc = DbForeignKey {
|
|
|
|
column_name: row.get("from")?,
|
|
|
|
ref_table: row.get("table")?,
|
|
|
|
ref_column: row.get("to")?,
|
|
|
|
};
|
|
|
|
Ok(dbc)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_foreign_keys(
|
|
|
|
&self,
|
|
|
|
conn: &Connection,
|
|
|
|
table: &DbTable,
|
|
|
|
) -> Result<Vec<DbForeignKey>, rusqlite::Error> {
|
|
|
|
let mut column_names = conn.prepare(&format!(
|
|
|
|
"SELECT p.`from`, p.`to`, p.`table` FROM pragma_foreign_key_list('{}') p",
|
|
|
|
&table.name
|
|
|
|
))?;
|
|
|
|
|
|
|
|
let mut foreign_keys: Vec<DbForeignKey> = Vec::new();
|
|
|
|
let rows = column_names.query_and_then([], |row| self.get_foreign_keys_info(row))?;
|
|
|
|
|
|
|
|
for row in rows {
|
|
|
|
foreign_keys.push(row?);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(foreign_keys)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_index_info(&self, row: &Row) -> Result<DbIndex, rusqlite::Error> {
|
|
|
|
let dbc = DbIndex {
|
|
|
|
name: row.get("index_name")?,
|
|
|
|
column_name: row.get("name")?,
|
|
|
|
seqno: row.get("seqno")?,
|
|
|
|
};
|
|
|
|
Ok(dbc)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_indexes(
|
|
|
|
&self,
|
|
|
|
conn: &Connection,
|
|
|
|
table: &DbTable,
|
|
|
|
) -> Result<Vec<DbIndex>, rusqlite::Error> {
|
|
|
|
let mut column_names = conn.prepare(&format!(
|
|
|
|
"
|
|
|
|
SELECT
|
|
|
|
m.name AS index_name,
|
|
|
|
p.*
|
|
|
|
FROM
|
|
|
|
sqlite_master m,
|
|
|
|
pragma_index_info(m.name) p
|
|
|
|
WHERE
|
|
|
|
m.type = 'index'
|
|
|
|
AND m.tbl_name = '{}'
|
|
|
|
",
|
|
|
|
&table.name,
|
|
|
|
))?;
|
|
|
|
|
|
|
|
let mut indexes: Vec<DbIndex> = Vec::new();
|
|
|
|
let rows = column_names.query_and_then([], |row| self.get_index_info(row))?;
|
|
|
|
|
|
|
|
for row in rows {
|
|
|
|
indexes.push(row?);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(indexes)
|
|
|
|
}
|
2022-04-20 04:58:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl CustomValue for SQLiteDatabase {
|
|
|
|
fn clone_value(&self, span: Span) -> Value {
|
|
|
|
let cloned = SQLiteDatabase {
|
2022-11-09 22:14:48 +00:00
|
|
|
path: self.path.clone(),
|
2022-12-05 00:49:47 +00:00
|
|
|
ctrlc: self.ctrlc.clone(),
|
2022-04-20 04:58:21 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Value::CustomValue {
|
|
|
|
val: Box::new(cloned),
|
|
|
|
span,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn value_string(&self) -> String {
|
|
|
|
self.typetag_name().to_string()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
|
2022-11-09 22:14:48 +00:00
|
|
|
let db = open_sqlite_db(&self.path, span)?;
|
2022-12-05 00:49:47 +00:00
|
|
|
read_entire_sqlite_db(db, span, self.ctrlc.clone()).map_err(|e| {
|
2022-11-09 22:14:48 +00:00
|
|
|
ShellError::GenericError(
|
|
|
|
"Failed to read from SQLite database".into(),
|
|
|
|
e.to_string(),
|
|
|
|
Some(span),
|
|
|
|
None,
|
|
|
|
Vec::new(),
|
|
|
|
)
|
|
|
|
})
|
2022-04-20 04:58:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn as_any(&self) -> &dyn std::any::Any {
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
fn follow_path_int(&self, _count: usize, span: Span) -> Result<Value, ShellError> {
|
|
|
|
// In theory we could support this, but tables don't have an especially well-defined order
|
|
|
|
Err(ShellError::IncompatiblePathAccess("SQLite databases do not support integer-indexed access. Try specifying a table name instead".into(), span))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn follow_path_string(&self, _column_name: String, span: Span) -> Result<Value, ShellError> {
|
2022-11-09 22:14:48 +00:00
|
|
|
let db = open_sqlite_db(&self.path, span)?;
|
2022-04-20 04:58:21 +00:00
|
|
|
|
2022-12-05 00:49:47 +00:00
|
|
|
read_single_table(db, _column_name, span, self.ctrlc.clone()).map_err(|e| {
|
2022-04-24 09:29:21 +00:00
|
|
|
ShellError::GenericError(
|
|
|
|
"Failed to read from SQLite database".into(),
|
|
|
|
e.to_string(),
|
|
|
|
Some(span),
|
|
|
|
None,
|
|
|
|
Vec::new(),
|
|
|
|
)
|
|
|
|
})
|
2022-04-20 04:58:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn typetag_name(&self) -> &'static str {
|
|
|
|
"SQLiteDatabase"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn typetag_deserialize(&self) {
|
|
|
|
unimplemented!("typetag_deserialize")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-08 19:12:42 +00:00
|
|
|
pub fn open_sqlite_db(path: &Path, call_span: Span) -> Result<Connection, nu_protocol::ShellError> {
|
2022-04-24 09:29:21 +00:00
|
|
|
let path = path.to_string_lossy().to_string();
|
|
|
|
|
|
|
|
Connection::open(path).map_err(|e| {
|
|
|
|
ShellError::GenericError(
|
|
|
|
"Failed to open SQLite database".into(),
|
|
|
|
e.to_string(),
|
|
|
|
Some(call_span),
|
2022-04-20 04:58:21 +00:00
|
|
|
None,
|
|
|
|
Vec::new(),
|
2022-04-24 09:29:21 +00:00
|
|
|
)
|
|
|
|
})
|
2022-04-20 04:58:21 +00:00
|
|
|
}
|
|
|
|
|
2022-12-05 00:49:47 +00:00
|
|
|
fn run_sql_query(
|
|
|
|
conn: Connection,
|
|
|
|
sql: &Spanned<String>,
|
|
|
|
ctrlc: Option<Arc<AtomicBool>>,
|
|
|
|
) -> Result<Value, rusqlite::Error> {
|
2022-08-01 06:09:03 +00:00
|
|
|
let stmt = conn.prepare(&sql.item)?;
|
2022-12-05 00:49:47 +00:00
|
|
|
prepared_statement_to_nu_list(stmt, sql.span, ctrlc)
|
2022-04-24 09:29:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn read_single_table(
|
|
|
|
conn: Connection,
|
|
|
|
table_name: String,
|
|
|
|
call_span: Span,
|
2022-12-05 00:49:47 +00:00
|
|
|
ctrlc: Option<Arc<AtomicBool>>,
|
2022-04-24 09:29:21 +00:00
|
|
|
) -> Result<Value, rusqlite::Error> {
|
2023-01-30 01:37:54 +00:00
|
|
|
let stmt = conn.prepare(&format!("SELECT * FROM {table_name}"))?;
|
2022-12-05 00:49:47 +00:00
|
|
|
prepared_statement_to_nu_list(stmt, call_span, ctrlc)
|
2022-08-01 06:09:03 +00:00
|
|
|
}
|
2022-04-24 09:29:21 +00:00
|
|
|
|
2022-08-01 06:09:03 +00:00
|
|
|
fn prepared_statement_to_nu_list(
|
|
|
|
mut stmt: rusqlite::Statement,
|
|
|
|
call_span: Span,
|
2022-12-05 00:49:47 +00:00
|
|
|
ctrlc: Option<Arc<AtomicBool>>,
|
2022-08-01 06:09:03 +00:00
|
|
|
) -> Result<Value, rusqlite::Error> {
|
|
|
|
let column_names = stmt
|
|
|
|
.column_names()
|
|
|
|
.iter()
|
|
|
|
.map(|c| c.to_string())
|
|
|
|
.collect::<Vec<String>>();
|
2022-12-05 00:49:47 +00:00
|
|
|
|
|
|
|
let row_results = stmt.query_map([], |row| {
|
|
|
|
Ok(convert_sqlite_row_to_nu_value(
|
|
|
|
row,
|
|
|
|
call_span,
|
|
|
|
column_names.clone(),
|
|
|
|
))
|
|
|
|
})?;
|
|
|
|
|
|
|
|
// we collect all rows before returning them. Not ideal but it's hard/impossible to return a stream from a CustomValue
|
|
|
|
let mut row_values = vec![];
|
|
|
|
|
|
|
|
for row_result in row_results {
|
2022-12-15 17:39:24 +00:00
|
|
|
if nu_utils::ctrl_c::was_pressed(&ctrlc) {
|
|
|
|
// return whatever we have so far, let the caller decide whether to use it
|
|
|
|
return Ok(Value::List {
|
|
|
|
vals: row_values,
|
|
|
|
span: call_span,
|
|
|
|
});
|
2022-12-05 00:49:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if let Ok(row_value) = row_result {
|
|
|
|
row_values.push(row_value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-24 09:29:21 +00:00
|
|
|
Ok(Value::List {
|
2022-12-05 00:49:47 +00:00
|
|
|
vals: row_values,
|
2022-04-24 09:29:21 +00:00
|
|
|
span: call_span,
|
|
|
|
})
|
2022-04-20 04:58:21 +00:00
|
|
|
}
|
|
|
|
|
2022-12-05 00:49:47 +00:00
|
|
|
fn read_entire_sqlite_db(
|
|
|
|
conn: Connection,
|
|
|
|
call_span: Span,
|
|
|
|
ctrlc: Option<Arc<AtomicBool>>,
|
|
|
|
) -> Result<Value, rusqlite::Error> {
|
2022-04-20 04:58:21 +00:00
|
|
|
let mut table_names: Vec<String> = Vec::new();
|
|
|
|
let mut tables: Vec<Value> = Vec::new();
|
|
|
|
|
|
|
|
let mut get_table_names =
|
|
|
|
conn.prepare("SELECT name FROM sqlite_master WHERE type = 'table'")?;
|
|
|
|
let rows = get_table_names.query_map([], |row| row.get(0))?;
|
|
|
|
|
|
|
|
for row in rows {
|
|
|
|
let table_name: String = row?;
|
|
|
|
table_names.push(table_name.clone());
|
|
|
|
|
2023-01-30 01:37:54 +00:00
|
|
|
let table_stmt = conn.prepare(&format!("select * from [{table_name}]"))?;
|
2022-12-05 00:49:47 +00:00
|
|
|
let rows = prepared_statement_to_nu_list(table_stmt, call_span, ctrlc.clone())?;
|
2022-08-01 06:09:03 +00:00
|
|
|
tables.push(rows);
|
2022-04-20 04:58:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Value::Record {
|
|
|
|
cols: table_names,
|
|
|
|
vals: tables,
|
|
|
|
span: call_span,
|
|
|
|
})
|
|
|
|
}
|
2022-04-27 10:52:31 +00:00
|
|
|
|
2022-08-01 06:09:03 +00:00
|
|
|
pub fn convert_sqlite_row_to_nu_value(row: &Row, span: Span, column_names: Vec<String>) -> Value {
|
|
|
|
let mut vals = Vec::with_capacity(column_names.len());
|
2022-04-20 04:58:21 +00:00
|
|
|
|
2022-08-01 06:09:03 +00:00
|
|
|
for i in 0..column_names.len() {
|
2022-04-20 04:58:21 +00:00
|
|
|
let val = convert_sqlite_value_to_nu_value(row.get_ref_unwrap(i), span);
|
|
|
|
vals.push(val);
|
|
|
|
}
|
|
|
|
|
|
|
|
Value::Record {
|
2022-08-01 06:09:03 +00:00
|
|
|
cols: column_names,
|
2022-04-20 04:58:21 +00:00
|
|
|
vals,
|
|
|
|
span,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-26 19:20:59 +00:00
|
|
|
pub fn convert_sqlite_value_to_nu_value(value: ValueRef, span: Span) -> Value {
|
2022-04-20 04:58:21 +00:00
|
|
|
match value {
|
|
|
|
ValueRef::Null => Value::Nothing { span },
|
|
|
|
ValueRef::Integer(i) => Value::Int { val: i, span },
|
|
|
|
ValueRef::Real(f) => Value::Float { val: f, span },
|
|
|
|
ValueRef::Text(buf) => {
|
|
|
|
let s = match std::str::from_utf8(buf) {
|
|
|
|
Ok(v) => v,
|
|
|
|
Err(_) => {
|
|
|
|
return Value::Error {
|
|
|
|
error: ShellError::NonUtf8(span),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Value::String {
|
|
|
|
val: s.to_string(),
|
|
|
|
span,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ValueRef::Blob(u) => Value::Binary {
|
|
|
|
val: u.to_vec(),
|
|
|
|
span,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn can_read_empty_db() {
|
2022-05-01 12:44:29 +00:00
|
|
|
let db = open_connection_in_memory().unwrap();
|
2022-12-05 00:49:47 +00:00
|
|
|
let converted_db = read_entire_sqlite_db(db, Span::test_data(), None).unwrap();
|
2022-04-20 04:58:21 +00:00
|
|
|
|
|
|
|
let expected = Value::Record {
|
|
|
|
cols: vec![],
|
|
|
|
vals: vec![],
|
|
|
|
span: Span::test_data(),
|
|
|
|
};
|
|
|
|
|
|
|
|
assert_eq!(converted_db, expected);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn can_read_empty_table() {
|
2022-05-01 12:44:29 +00:00
|
|
|
let db = open_connection_in_memory().unwrap();
|
2022-04-20 04:58:21 +00:00
|
|
|
|
|
|
|
db.execute(
|
|
|
|
"CREATE TABLE person (
|
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
name TEXT NOT NULL,
|
|
|
|
data BLOB
|
|
|
|
)",
|
|
|
|
[],
|
|
|
|
)
|
|
|
|
.unwrap();
|
2022-12-05 00:49:47 +00:00
|
|
|
let converted_db = read_entire_sqlite_db(db, Span::test_data(), None).unwrap();
|
2022-04-20 04:58:21 +00:00
|
|
|
|
|
|
|
let expected = Value::Record {
|
|
|
|
cols: vec!["person".to_string()],
|
|
|
|
vals: vec![Value::List {
|
|
|
|
vals: vec![],
|
|
|
|
span: Span::test_data(),
|
|
|
|
}],
|
|
|
|
span: Span::test_data(),
|
|
|
|
};
|
|
|
|
|
|
|
|
assert_eq!(converted_db, expected);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn can_read_null_and_non_null_data() {
|
|
|
|
let span = Span::test_data();
|
2022-05-01 12:44:29 +00:00
|
|
|
let db = open_connection_in_memory().unwrap();
|
2022-04-20 04:58:21 +00:00
|
|
|
|
|
|
|
db.execute(
|
|
|
|
"CREATE TABLE item (
|
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
name TEXT
|
|
|
|
)",
|
|
|
|
[],
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
db.execute("INSERT INTO item (id, name) VALUES (123, NULL)", [])
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
db.execute("INSERT INTO item (id, name) VALUES (456, 'foo bar')", [])
|
|
|
|
.unwrap();
|
|
|
|
|
2022-12-05 00:49:47 +00:00
|
|
|
let converted_db = read_entire_sqlite_db(db, span, None).unwrap();
|
2022-04-20 04:58:21 +00:00
|
|
|
|
|
|
|
let expected = Value::Record {
|
|
|
|
cols: vec!["item".to_string()],
|
|
|
|
vals: vec![Value::List {
|
|
|
|
vals: vec![
|
|
|
|
Value::Record {
|
|
|
|
cols: vec!["id".to_string(), "name".to_string()],
|
|
|
|
vals: vec![Value::Int { val: 123, span }, Value::Nothing { span }],
|
|
|
|
span,
|
|
|
|
},
|
|
|
|
Value::Record {
|
|
|
|
cols: vec!["id".to_string(), "name".to_string()],
|
|
|
|
vals: vec![
|
|
|
|
Value::Int { val: 456, span },
|
|
|
|
Value::String {
|
|
|
|
val: "foo bar".to_string(),
|
|
|
|
span,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
span,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
span,
|
|
|
|
}],
|
|
|
|
span,
|
|
|
|
};
|
|
|
|
|
|
|
|
assert_eq!(converted_db, expected);
|
|
|
|
}
|
|
|
|
}
|
2022-04-26 19:20:59 +00:00
|
|
|
|
|
|
|
pub fn open_connection_in_memory() -> Result<Connection, ShellError> {
|
2023-01-24 11:23:42 +00:00
|
|
|
Connection::open_in_memory().map_err(|err| {
|
|
|
|
ShellError::GenericError(
|
|
|
|
"Failed to open SQLite connection in memory".into(),
|
|
|
|
err.to_string(),
|
|
|
|
Some(Span::test_data()),
|
|
|
|
None,
|
|
|
|
Vec::new(),
|
|
|
|
)
|
|
|
|
})
|
2022-04-26 19:20:59 +00:00
|
|
|
}
|