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
Create `Record` type (#10103)
# Description
This PR creates a new `Record` type to reduce duplicate code and
possibly bugs as well. (This is an edited version of #9648.)
- `Record` implements `FromIterator` and `IntoIterator` and so can be
iterated over or collected into. For example, this helps with
conversions to and from (hash)maps. (Also, no more
`cols.iter().zip(vals)`!)
- `Record` has a `push(col, val)` function to help insure that the
number of columns is equal to the number of values. I caught a few
potential bugs thanks to this (e.g. in the `ls` command).
- Finally, this PR also adds a `record!` macro that helps simplify
record creation. It is used like so:
```rust
record! {
"key1" => some_value,
"key2" => Value::string("text", span),
"key3" => Value::int(optional_int.unwrap_or(0), span),
"key4" => Value::bool(config.setting, span),
}
```
Since macros hinder formatting, etc., the right hand side values should
be relatively short and sweet like the examples above.
Where possible, prefer `record!` or `.collect()` on an iterator instead
of multiple `Record::push`s, since the first two automatically set the
record capacity and do less work overall.
# User-Facing Changes
Besides the changes in `nu-protocol` the only other breaking changes are
to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
2023-08-24 19:50:29 +00:00
use nu_protocol ::{ CustomValue , PipelineData , Record , 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
} ) ,
2023-03-06 17:33:09 +00:00
None = > Err ( ShellError ::CantConvert {
to_type : " database " . into ( ) ,
from_type : " non-database " . into ( ) ,
2022-04-27 10:52:31 +00:00
span ,
2023-03-06 17:33:09 +00:00
help : None ,
} ) ,
2022-04-27 10:52:31 +00:00
} ,
2023-03-06 17:33:09 +00:00
x = > Err ( ShellError ::CantConvert {
to_type : " database " . into ( ) ,
from_type : x . get_type ( ) . to_string ( ) ,
2023-08-24 20:48:05 +00:00
span : x . span ( ) ,
2023-03-06 17:33:09 +00:00
help : None ,
} ) ,
2022-04-27 10:52:31 +00:00
}
}
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
2023-03-06 17:33:09 +00:00
Err ( ShellError ::IncompatiblePathAccess { type_name : " SQLite databases do not support integer-indexed access. Try specifying a table name instead " . into ( ) , span } )
2022-04-20 04:58:21 +00:00
}
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-07-20 16:20:56 +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 > {
Create `Record` type (#10103)
# Description
This PR creates a new `Record` type to reduce duplicate code and
possibly bugs as well. (This is an edited version of #9648.)
- `Record` implements `FromIterator` and `IntoIterator` and so can be
iterated over or collected into. For example, this helps with
conversions to and from (hash)maps. (Also, no more
`cols.iter().zip(vals)`!)
- `Record` has a `push(col, val)` function to help insure that the
number of columns is equal to the number of values. I caught a few
potential bugs thanks to this (e.g. in the `ls` command).
- Finally, this PR also adds a `record!` macro that helps simplify
record creation. It is used like so:
```rust
record! {
"key1" => some_value,
"key2" => Value::string("text", span),
"key3" => Value::int(optional_int.unwrap_or(0), span),
"key4" => Value::bool(config.setting, span),
}
```
Since macros hinder formatting, etc., the right hand side values should
be relatively short and sweet like the examples above.
Where possible, prefer `record!` or `.collect()` on an iterator instead
of multiple `Record::push`s, since the first two automatically set the
record capacity and do less work overall.
# User-Facing Changes
Besides the changes in `nu-protocol` the only other breaking changes are
to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
2023-08-24 19:50:29 +00:00
let mut tables = Record ::new ( ) ;
2022-04-20 04:58:21 +00:00
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 ? ;
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 ( ) ) ? ;
Create `Record` type (#10103)
# Description
This PR creates a new `Record` type to reduce duplicate code and
possibly bugs as well. (This is an edited version of #9648.)
- `Record` implements `FromIterator` and `IntoIterator` and so can be
iterated over or collected into. For example, this helps with
conversions to and from (hash)maps. (Also, no more
`cols.iter().zip(vals)`!)
- `Record` has a `push(col, val)` function to help insure that the
number of columns is equal to the number of values. I caught a few
potential bugs thanks to this (e.g. in the `ls` command).
- Finally, this PR also adds a `record!` macro that helps simplify
record creation. It is used like so:
```rust
record! {
"key1" => some_value,
"key2" => Value::string("text", span),
"key3" => Value::int(optional_int.unwrap_or(0), span),
"key4" => Value::bool(config.setting, span),
}
```
Since macros hinder formatting, etc., the right hand side values should
be relatively short and sweet like the examples above.
Where possible, prefer `record!` or `.collect()` on an iterator instead
of multiple `Record::push`s, since the first two automatically set the
record capacity and do less work overall.
# User-Facing Changes
Besides the changes in `nu-protocol` the only other breaking changes are
to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
2023-08-24 19:50:29 +00:00
tables . push ( table_name , rows ) ;
2022-04-20 04:58:21 +00:00
}
Create `Record` type (#10103)
# Description
This PR creates a new `Record` type to reduce duplicate code and
possibly bugs as well. (This is an edited version of #9648.)
- `Record` implements `FromIterator` and `IntoIterator` and so can be
iterated over or collected into. For example, this helps with
conversions to and from (hash)maps. (Also, no more
`cols.iter().zip(vals)`!)
- `Record` has a `push(col, val)` function to help insure that the
number of columns is equal to the number of values. I caught a few
potential bugs thanks to this (e.g. in the `ls` command).
- Finally, this PR also adds a `record!` macro that helps simplify
record creation. It is used like so:
```rust
record! {
"key1" => some_value,
"key2" => Value::string("text", span),
"key3" => Value::int(optional_int.unwrap_or(0), span),
"key4" => Value::bool(config.setting, span),
}
```
Since macros hinder formatting, etc., the right hand side values should
be relatively short and sweet like the examples above.
Where possible, prefer `record!` or `.collect()` on an iterator instead
of multiple `Record::push`s, since the first two automatically set the
record capacity and do less work overall.
# User-Facing Changes
Besides the changes in `nu-protocol` the only other breaking changes are
to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
2023-08-24 19:50:29 +00:00
Ok ( Value ::record ( tables , call_span ) )
2022-04-20 04:58:21 +00:00
}
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 ) ;
}
Create `Record` type (#10103)
# Description
This PR creates a new `Record` type to reduce duplicate code and
possibly bugs as well. (This is an edited version of #9648.)
- `Record` implements `FromIterator` and `IntoIterator` and so can be
iterated over or collected into. For example, this helps with
conversions to and from (hash)maps. (Also, no more
`cols.iter().zip(vals)`!)
- `Record` has a `push(col, val)` function to help insure that the
number of columns is equal to the number of values. I caught a few
potential bugs thanks to this (e.g. in the `ls` command).
- Finally, this PR also adds a `record!` macro that helps simplify
record creation. It is used like so:
```rust
record! {
"key1" => some_value,
"key2" => Value::string("text", span),
"key3" => Value::int(optional_int.unwrap_or(0), span),
"key4" => Value::bool(config.setting, span),
}
```
Since macros hinder formatting, etc., the right hand side values should
be relatively short and sweet like the examples above.
Where possible, prefer `record!` or `.collect()` on an iterator instead
of multiple `Record::push`s, since the first two automatically set the
record capacity and do less work overall.
# User-Facing Changes
Besides the changes in `nu-protocol` the only other breaking changes are
to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
2023-08-24 19:50:29 +00:00
Value ::record (
Record {
cols : column_names ,
vals ,
} ,
2022-04-20 04:58:21 +00:00
span ,
Create `Record` type (#10103)
# Description
This PR creates a new `Record` type to reduce duplicate code and
possibly bugs as well. (This is an edited version of #9648.)
- `Record` implements `FromIterator` and `IntoIterator` and so can be
iterated over or collected into. For example, this helps with
conversions to and from (hash)maps. (Also, no more
`cols.iter().zip(vals)`!)
- `Record` has a `push(col, val)` function to help insure that the
number of columns is equal to the number of values. I caught a few
potential bugs thanks to this (e.g. in the `ls` command).
- Finally, this PR also adds a `record!` macro that helps simplify
record creation. It is used like so:
```rust
record! {
"key1" => some_value,
"key2" => Value::string("text", span),
"key3" => Value::int(optional_int.unwrap_or(0), span),
"key4" => Value::bool(config.setting, span),
}
```
Since macros hinder formatting, etc., the right hand side values should
be relatively short and sweet like the examples above.
Where possible, prefer `record!` or `.collect()` on an iterator instead
of multiple `Record::push`s, since the first two automatically set the
record capacity and do less work overall.
# User-Facing Changes
Besides the changes in `nu-protocol` the only other breaking changes are
to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
2023-08-24 19:50:29 +00:00
)
2022-04-20 04:58:21 +00:00
}
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 {
2023-03-12 08:57:27 +00:00
error : Box ::new ( ShellError ::NonUtf8 ( span ) ) ,
2023-08-24 20:48:05 +00:00
span ,
2022-04-20 04:58:21 +00:00
}
}
} ;
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
Create `Record` type (#10103)
# Description
This PR creates a new `Record` type to reduce duplicate code and
possibly bugs as well. (This is an edited version of #9648.)
- `Record` implements `FromIterator` and `IntoIterator` and so can be
iterated over or collected into. For example, this helps with
conversions to and from (hash)maps. (Also, no more
`cols.iter().zip(vals)`!)
- `Record` has a `push(col, val)` function to help insure that the
number of columns is equal to the number of values. I caught a few
potential bugs thanks to this (e.g. in the `ls` command).
- Finally, this PR also adds a `record!` macro that helps simplify
record creation. It is used like so:
```rust
record! {
"key1" => some_value,
"key2" => Value::string("text", span),
"key3" => Value::int(optional_int.unwrap_or(0), span),
"key4" => Value::bool(config.setting, span),
}
```
Since macros hinder formatting, etc., the right hand side values should
be relatively short and sweet like the examples above.
Where possible, prefer `record!` or `.collect()` on an iterator instead
of multiple `Record::push`s, since the first two automatically set the
record capacity and do less work overall.
# User-Facing Changes
Besides the changes in `nu-protocol` the only other breaking changes are
to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
2023-08-24 19:50:29 +00:00
let expected = Value ::test_record ( Record ::new ( ) ) ;
2022-04-20 04:58:21 +00:00
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
Create `Record` type (#10103)
# Description
This PR creates a new `Record` type to reduce duplicate code and
possibly bugs as well. (This is an edited version of #9648.)
- `Record` implements `FromIterator` and `IntoIterator` and so can be
iterated over or collected into. For example, this helps with
conversions to and from (hash)maps. (Also, no more
`cols.iter().zip(vals)`!)
- `Record` has a `push(col, val)` function to help insure that the
number of columns is equal to the number of values. I caught a few
potential bugs thanks to this (e.g. in the `ls` command).
- Finally, this PR also adds a `record!` macro that helps simplify
record creation. It is used like so:
```rust
record! {
"key1" => some_value,
"key2" => Value::string("text", span),
"key3" => Value::int(optional_int.unwrap_or(0), span),
"key4" => Value::bool(config.setting, span),
}
```
Since macros hinder formatting, etc., the right hand side values should
be relatively short and sweet like the examples above.
Where possible, prefer `record!` or `.collect()` on an iterator instead
of multiple `Record::push`s, since the first two automatically set the
record capacity and do less work overall.
# User-Facing Changes
Besides the changes in `nu-protocol` the only other breaking changes are
to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
2023-08-24 19:50:29 +00:00
let expected = Value ::test_record ( Record {
2022-04-20 04:58:21 +00:00
cols : vec ! [ " person " . to_string ( ) ] ,
vals : vec ! [ Value ::List {
vals : vec ! [ ] ,
span : Span ::test_data ( ) ,
} ] ,
Create `Record` type (#10103)
# Description
This PR creates a new `Record` type to reduce duplicate code and
possibly bugs as well. (This is an edited version of #9648.)
- `Record` implements `FromIterator` and `IntoIterator` and so can be
iterated over or collected into. For example, this helps with
conversions to and from (hash)maps. (Also, no more
`cols.iter().zip(vals)`!)
- `Record` has a `push(col, val)` function to help insure that the
number of columns is equal to the number of values. I caught a few
potential bugs thanks to this (e.g. in the `ls` command).
- Finally, this PR also adds a `record!` macro that helps simplify
record creation. It is used like so:
```rust
record! {
"key1" => some_value,
"key2" => Value::string("text", span),
"key3" => Value::int(optional_int.unwrap_or(0), span),
"key4" => Value::bool(config.setting, span),
}
```
Since macros hinder formatting, etc., the right hand side values should
be relatively short and sweet like the examples above.
Where possible, prefer `record!` or `.collect()` on an iterator instead
of multiple `Record::push`s, since the first two automatically set the
record capacity and do less work overall.
# User-Facing Changes
Besides the changes in `nu-protocol` the only other breaking changes are
to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
2023-08-24 19:50:29 +00:00
} ) ;
2022-04-20 04:58:21 +00:00
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
Create `Record` type (#10103)
# Description
This PR creates a new `Record` type to reduce duplicate code and
possibly bugs as well. (This is an edited version of #9648.)
- `Record` implements `FromIterator` and `IntoIterator` and so can be
iterated over or collected into. For example, this helps with
conversions to and from (hash)maps. (Also, no more
`cols.iter().zip(vals)`!)
- `Record` has a `push(col, val)` function to help insure that the
number of columns is equal to the number of values. I caught a few
potential bugs thanks to this (e.g. in the `ls` command).
- Finally, this PR also adds a `record!` macro that helps simplify
record creation. It is used like so:
```rust
record! {
"key1" => some_value,
"key2" => Value::string("text", span),
"key3" => Value::int(optional_int.unwrap_or(0), span),
"key4" => Value::bool(config.setting, span),
}
```
Since macros hinder formatting, etc., the right hand side values should
be relatively short and sweet like the examples above.
Where possible, prefer `record!` or `.collect()` on an iterator instead
of multiple `Record::push`s, since the first two automatically set the
record capacity and do less work overall.
# User-Facing Changes
Besides the changes in `nu-protocol` the only other breaking changes are
to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
2023-08-24 19:50:29 +00:00
let expected = Value ::test_record ( Record {
2022-04-20 04:58:21 +00:00
cols : vec ! [ " item " . to_string ( ) ] ,
vals : vec ! [ Value ::List {
vals : vec ! [
Create `Record` type (#10103)
# Description
This PR creates a new `Record` type to reduce duplicate code and
possibly bugs as well. (This is an edited version of #9648.)
- `Record` implements `FromIterator` and `IntoIterator` and so can be
iterated over or collected into. For example, this helps with
conversions to and from (hash)maps. (Also, no more
`cols.iter().zip(vals)`!)
- `Record` has a `push(col, val)` function to help insure that the
number of columns is equal to the number of values. I caught a few
potential bugs thanks to this (e.g. in the `ls` command).
- Finally, this PR also adds a `record!` macro that helps simplify
record creation. It is used like so:
```rust
record! {
"key1" => some_value,
"key2" => Value::string("text", span),
"key3" => Value::int(optional_int.unwrap_or(0), span),
"key4" => Value::bool(config.setting, span),
}
```
Since macros hinder formatting, etc., the right hand side values should
be relatively short and sweet like the examples above.
Where possible, prefer `record!` or `.collect()` on an iterator instead
of multiple `Record::push`s, since the first two automatically set the
record capacity and do less work overall.
# User-Facing Changes
Besides the changes in `nu-protocol` the only other breaking changes are
to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
2023-08-24 19:50:29 +00:00
Value ::test_record ( Record {
2022-04-20 04:58:21 +00:00
cols : vec ! [ " id " . to_string ( ) , " name " . to_string ( ) ] ,
vals : vec ! [ Value ::Int { val : 123 , span } , Value ::Nothing { span } ] ,
Create `Record` type (#10103)
# Description
This PR creates a new `Record` type to reduce duplicate code and
possibly bugs as well. (This is an edited version of #9648.)
- `Record` implements `FromIterator` and `IntoIterator` and so can be
iterated over or collected into. For example, this helps with
conversions to and from (hash)maps. (Also, no more
`cols.iter().zip(vals)`!)
- `Record` has a `push(col, val)` function to help insure that the
number of columns is equal to the number of values. I caught a few
potential bugs thanks to this (e.g. in the `ls` command).
- Finally, this PR also adds a `record!` macro that helps simplify
record creation. It is used like so:
```rust
record! {
"key1" => some_value,
"key2" => Value::string("text", span),
"key3" => Value::int(optional_int.unwrap_or(0), span),
"key4" => Value::bool(config.setting, span),
}
```
Since macros hinder formatting, etc., the right hand side values should
be relatively short and sweet like the examples above.
Where possible, prefer `record!` or `.collect()` on an iterator instead
of multiple `Record::push`s, since the first two automatically set the
record capacity and do less work overall.
# User-Facing Changes
Besides the changes in `nu-protocol` the only other breaking changes are
to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
2023-08-24 19:50:29 +00:00
} ) ,
Value ::test_record ( Record {
2022-04-20 04:58:21 +00:00
cols : vec ! [ " id " . to_string ( ) , " name " . to_string ( ) ] ,
vals : vec ! [
Value ::Int { val : 456 , span } ,
Value ::String {
val : " foo bar " . to_string ( ) ,
span ,
} ,
] ,
Create `Record` type (#10103)
# Description
This PR creates a new `Record` type to reduce duplicate code and
possibly bugs as well. (This is an edited version of #9648.)
- `Record` implements `FromIterator` and `IntoIterator` and so can be
iterated over or collected into. For example, this helps with
conversions to and from (hash)maps. (Also, no more
`cols.iter().zip(vals)`!)
- `Record` has a `push(col, val)` function to help insure that the
number of columns is equal to the number of values. I caught a few
potential bugs thanks to this (e.g. in the `ls` command).
- Finally, this PR also adds a `record!` macro that helps simplify
record creation. It is used like so:
```rust
record! {
"key1" => some_value,
"key2" => Value::string("text", span),
"key3" => Value::int(optional_int.unwrap_or(0), span),
"key4" => Value::bool(config.setting, span),
}
```
Since macros hinder formatting, etc., the right hand side values should
be relatively short and sweet like the examples above.
Where possible, prefer `record!` or `.collect()` on an iterator instead
of multiple `Record::push`s, since the first two automatically set the
record capacity and do less work overall.
# User-Facing Changes
Besides the changes in `nu-protocol` the only other breaking changes are
to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
2023-08-24 19:50:29 +00:00
} ) ,
2022-04-20 04:58:21 +00:00
] ,
span ,
} ] ,
Create `Record` type (#10103)
# Description
This PR creates a new `Record` type to reduce duplicate code and
possibly bugs as well. (This is an edited version of #9648.)
- `Record` implements `FromIterator` and `IntoIterator` and so can be
iterated over or collected into. For example, this helps with
conversions to and from (hash)maps. (Also, no more
`cols.iter().zip(vals)`!)
- `Record` has a `push(col, val)` function to help insure that the
number of columns is equal to the number of values. I caught a few
potential bugs thanks to this (e.g. in the `ls` command).
- Finally, this PR also adds a `record!` macro that helps simplify
record creation. It is used like so:
```rust
record! {
"key1" => some_value,
"key2" => Value::string("text", span),
"key3" => Value::int(optional_int.unwrap_or(0), span),
"key4" => Value::bool(config.setting, span),
}
```
Since macros hinder formatting, etc., the right hand side values should
be relatively short and sweet like the examples above.
Where possible, prefer `record!` or `.collect()` on an iterator instead
of multiple `Record::push`s, since the first two automatically set the
record capacity and do less work overall.
# User-Facing Changes
Besides the changes in `nu-protocol` the only other breaking changes are
to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
2023-08-24 19:50:29 +00:00
} ) ;
2022-04-20 04:58:21 +00:00
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
}