@ -6,38 +6,121 @@ use oxrdf::Variable;
use oxrdf ::{ vocab ::xsd , * } ;
use std ::io ::{ self , Read , Write } ;
use std ::str ::{ self , FromStr } ;
#[ cfg(feature = " async-tokio " ) ]
use tokio ::io ::{ AsyncWrite , AsyncWriteExt } ;
const MAX_BUFFER_SIZE : usize = 4096 * 4096 ;
pub fn write_boolean_csv_result < W : Write > ( mut sink : W , value : bool ) -> io ::Result < W > {
sink . write_all ( if value { b" true " } else { b" false " } ) ? ;
Ok ( sink )
pub fn write_boolean_csv_result < W : Write > ( mut write : W , value : bool ) -> io ::Result < W > {
write . write_all ( if value { b" true " } else { b" false " } ) ? ;
Ok ( write )
}
pub struct CsvSolutionsWriter < W : Write > {
sink : W ,
#[ cfg(feature = " async-tokio " ) ]
pub async fn tokio_async_write_boolean_csv_result < W : AsyncWrite + Unpin > (
mut write : W ,
value : bool ,
) -> io ::Result < W > {
write
. write_all ( if value { b" true " } else { b" false " } )
. await ? ;
Ok ( write )
}
pub struct ToWriteCsvSolutionsWriter < W : Write > {
inner : InnerCsvSolutionsWriter ,
write : W ,
buffer : String ,
}
impl < W : Write > ToWriteCsvSolutionsWriter < W > {
pub fn start ( mut write : W , variables : Vec < Variable > ) -> io ::Result < Self > {
let mut buffer = String ::new ( ) ;
let inner = InnerCsvSolutionsWriter ::start ( & mut buffer , variables ) ;
write . write_all ( buffer . as_bytes ( ) ) ? ;
buffer . clear ( ) ;
Ok ( Self {
inner ,
write ,
buffer ,
} )
}
pub fn write < ' a > (
& mut self ,
solution : impl IntoIterator < Item = ( VariableRef < ' a > , TermRef < ' a > ) > ,
) -> io ::Result < ( ) > {
self . inner . write ( & mut self . buffer , solution ) ;
self . write . write_all ( self . buffer . as_bytes ( ) ) ? ;
self . buffer . clear ( ) ;
Ok ( ( ) )
}
pub fn finish ( self ) -> W {
self . write
}
}
#[ cfg(feature = " async-tokio " ) ]
pub struct ToTokioAsyncWriteCsvSolutionsWriter < W : AsyncWrite + Unpin > {
inner : InnerCsvSolutionsWriter ,
write : W ,
buffer : String ,
}
#[ cfg(feature = " async-tokio " ) ]
impl < W : AsyncWrite + Unpin > ToTokioAsyncWriteCsvSolutionsWriter < W > {
pub async fn start ( mut write : W , variables : Vec < Variable > ) -> io ::Result < Self > {
let mut buffer = String ::new ( ) ;
let inner = InnerCsvSolutionsWriter ::start ( & mut buffer , variables ) ;
write . write_all ( buffer . as_bytes ( ) ) . await ? ;
buffer . clear ( ) ;
Ok ( Self {
inner ,
write ,
buffer ,
} )
}
pub async fn write < ' a > (
& mut self ,
solution : impl IntoIterator < Item = ( VariableRef < ' a > , TermRef < ' a > ) > ,
) -> io ::Result < ( ) > {
self . inner . write ( & mut self . buffer , solution ) ;
self . write . write_all ( self . buffer . as_bytes ( ) ) . await ? ;
self . buffer . clear ( ) ;
Ok ( ( ) )
}
pub fn finish ( self ) -> W {
self . write
}
}
struct InnerCsvSolutionsWriter {
variables : Vec < Variable > ,
}
impl < W : Write > CsvSolutionsWriter < W > {
pub fn start ( mut sink : W , variables : Vec < Variable > ) -> io ::Result < Self > {
impl InnerCsvSolutionsWriter {
fn start ( output : & mut String , variables : Vec < Variable > ) -> Self {
let mut start_vars = true ;
for variable in & variables {
if start_vars {
start_vars = false ;
} else {
sink . write_all ( b" , " ) ? ;
output . push ( ',' ) ;
}
sink . write_all ( variable . as_str ( ) . as_bytes ( ) ) ? ;
output . push_str ( variable . as_str ( ) ) ;
}
sink . write_all ( b" \r \n " ) ? ;
Ok ( Self { sink , variables } )
output . push_str ( "\r\n" ) ;
Self { variables }
}
pub fn write < ' a > (
& mut self ,
fn write < ' a > (
& self ,
output : & mut String ,
solution : impl IntoIterator < Item = ( VariableRef < ' a > , TermRef < ' a > ) > ,
) -> io ::Result < ( ) > {
) {
let mut values = vec! [ None ; self . variables . len ( ) ] ;
for ( variable , value ) in solution {
if let Some ( position ) = self . variables . iter ( ) . position ( | v | * v = = variable ) {
@ -49,85 +132,147 @@ impl<W: Write> CsvSolutionsWriter<W> {
if start_binding {
start_binding = false ;
} else {
self . sink . write_all ( b" , " ) ? ;
output . push ( ',' ) ;
}
if let Some ( value ) = value {
write_csv_term ( value , & mut self . sink ) ? ;
write_csv_term ( output , value ) ;
}
}
self . sink . write_all ( b" \r \n " )
}
pub fn finish ( self ) -> W {
self . sink
output . push_str ( "\r\n" ) ;
}
}
fn write_csv_term < ' a > ( term : impl Into < TermRef < ' a > > , sink : & mut impl Write ) -> io ::Result < ( ) > {
fn write_csv_term < ' a > ( output : & mut String , term : impl Into < TermRef < ' a > > ) {
match term . into ( ) {
TermRef ::NamedNode ( uri ) = > sink . write_all ( uri . as_str ( ) . as_byte s ( ) ) ,
TermRef ::NamedNode ( uri ) = > output . push_str ( uri . as_str ( ) ) ,
TermRef ::BlankNode ( bnode ) = > {
sink . write_all ( b" _: " ) ? ;
sink . write_all ( bnode . as_str ( ) . as_bytes ( ) )
output . push_str ( "_:" ) ;
output . push_str ( bnode . as_str ( ) )
}
TermRef ::Literal ( literal ) = > write_escaped_csv_string ( literal . value ( ) , sink ) ,
TermRef ::Literal ( literal ) = > write_escaped_csv_string ( output , literal . value ( ) ) ,
#[ cfg(feature = " rdf-star " ) ]
TermRef ::Triple ( triple ) = > {
write_csv_term ( & triple . subject , sink ) ? ;
sink . write_all ( b" " ) ? ;
write_csv_term ( & triple . predicate , sink ) ? ;
sink . write_all ( b" " ) ? ;
write_csv_term ( & triple . object , sink )
write_csv_term ( output , & triple . subject ) ;
output . push ( ' ' ) ;
write_csv_term ( output , & triple . predicate ) ;
output . push ( ' ' ) ;
write_csv_term ( output , & triple . object )
}
}
}
fn write_escaped_csv_string ( s : & str , sink : & mut impl Write ) -> io ::Result < ( ) > {
fn write_escaped_csv_string ( output : & mut String , s : & str ) {
if s . bytes ( ) . any ( | c | matches! ( c , b'"' | b',' | b'\n' | b'\r' ) ) {
sink . write_all ( b" \" " ) ? ;
for c in s . bytes ( ) {
if c = = b'\"' {
sink . write_all ( b" \" \" " )
output . push ( '"' ) ;
for c in s . chars ( ) {
if c = = '"' {
output . push ( '"' ) ;
output . push ( '"' ) ;
} else {
sink . write_all ( & [ c ] )
} ? ;
output . push ( c )
} ;
}
sink . write_all ( b" \" " )
output . push ( '"' ) ;
} else {
sink . write_all ( s . as_bytes ( ) )
output . push_str ( s )
}
}
pub fn write_boolean_tsv_result < W : Write > ( mut sink : W , value : bool ) -> io ::Result < W > {
sink . write_all ( if value { b" true " } else { b" false " } ) ? ;
Ok ( sink )
pub struct ToWriteTsvSolutionsWriter < W : Write > {
inner : InnerTsvSolutionsWriter ,
write : W ,
buffer : String ,
}
pub struct TsvSolutionsWriter < W : Write > {
sink : W ,
impl < W : Write > ToWriteTsvSolutionsWriter < W > {
pub fn start ( mut write : W , variables : Vec < Variable > ) -> io ::Result < Self > {
let mut buffer = String ::new ( ) ;
let inner = InnerTsvSolutionsWriter ::start ( & mut buffer , variables ) ;
write . write_all ( buffer . as_bytes ( ) ) ? ;
buffer . clear ( ) ;
Ok ( Self {
inner ,
write ,
buffer ,
} )
}
pub fn write < ' a > (
& mut self ,
solution : impl IntoIterator < Item = ( VariableRef < ' a > , TermRef < ' a > ) > ,
) -> io ::Result < ( ) > {
self . inner . write ( & mut self . buffer , solution ) ;
self . write . write_all ( self . buffer . as_bytes ( ) ) ? ;
self . buffer . clear ( ) ;
Ok ( ( ) )
}
pub fn finish ( self ) -> W {
self . write
}
}
#[ cfg(feature = " async-tokio " ) ]
pub struct ToTokioAsyncWriteTsvSolutionsWriter < W : AsyncWrite + Unpin > {
inner : InnerTsvSolutionsWriter ,
write : W ,
buffer : String ,
}
#[ cfg(feature = " async-tokio " ) ]
impl < W : AsyncWrite + Unpin > ToTokioAsyncWriteTsvSolutionsWriter < W > {
pub async fn start ( mut write : W , variables : Vec < Variable > ) -> io ::Result < Self > {
let mut buffer = String ::new ( ) ;
let inner = InnerTsvSolutionsWriter ::start ( & mut buffer , variables ) ;
write . write_all ( buffer . as_bytes ( ) ) . await ? ;
buffer . clear ( ) ;
Ok ( Self {
inner ,
write ,
buffer ,
} )
}
pub async fn write < ' a > (
& mut self ,
solution : impl IntoIterator < Item = ( VariableRef < ' a > , TermRef < ' a > ) > ,
) -> io ::Result < ( ) > {
self . inner . write ( & mut self . buffer , solution ) ;
self . write . write_all ( self . buffer . as_bytes ( ) ) . await ? ;
self . buffer . clear ( ) ;
Ok ( ( ) )
}
pub fn finish ( self ) -> W {
self . write
}
}
struct InnerTsvSolutionsWriter {
variables : Vec < Variable > ,
}
impl < W : Write > TsvSolutionsWriter < W > {
pub fn start ( mut sink : W , variables : Vec < Variable > ) -> io ::Result < Self > {
impl InnerTsvSolutionsWriter {
fn start ( output : & mut String , variables : Vec < Variable > ) -> Self {
let mut start_vars = true ;
for variable in & variables {
if start_vars {
start_vars = false ;
} else {
sink . write_all ( b" \t " ) ? ;
output . push ( '\t' ) ;
}
sink . write_all ( b" ? " ) ? ;
sink . write_all ( variable . as_str ( ) . as_bytes ( ) ) ? ;
output . push ( '?' ) ;
output . push_str ( variable . as_str ( ) ) ;
}
sink . write_all ( b" \n " ) ? ;
Ok ( Self { sink , variables } )
output . push ( '\n' ) ;
Self { variables }
}
pub fn write < ' a > (
& mut self ,
fn write < ' a > (
& self ,
output : & mut String ,
solution : impl IntoIterator < Item = ( VariableRef < ' a > , TermRef < ' a > ) > ,
) -> io ::Result < ( ) > {
) {
let mut values = vec! [ None ; self . variables . len ( ) ] ;
for ( variable , value ) in solution {
if let Some ( position ) = self . variables . iter ( ) . position ( | v | * v = = variable ) {
@ -139,70 +284,74 @@ impl<W: Write> TsvSolutionsWriter<W> {
if start_binding {
start_binding = false ;
} else {
self . sink . write_all ( b" \t " ) ? ;
output . push ( '\t' ) ;
}
if let Some ( value ) = value {
write_tsv_term ( value , & mut self . sink ) ? ;
write_tsv_term ( output , value ) ;
}
}
self . sink . write_all ( b" \n " )
}
pub fn finish ( self ) -> W {
self . sink
output . push ( '\n' ) ;
}
}
fn write_tsv_term < ' a > ( term : impl Into < TermRef < ' a > > , sink : & mut impl Write ) -> io ::Result < ( ) > {
fn write_tsv_term < ' a > ( output : & mut String , term : impl Into < TermRef < ' a > > ) {
match term . into ( ) {
TermRef ::NamedNode ( node ) = > write! ( sink , "<{}>" , node . as_str ( ) ) ,
TermRef ::BlankNode ( node ) = > write! ( sink , "_:{}" , node . as_str ( ) ) ,
TermRef ::NamedNode ( node ) = > {
output . push ( '<' ) ;
output . push_str ( node . as_str ( ) ) ;
output . push ( '>' ) ;
}
TermRef ::BlankNode ( node ) = > {
output . push_str ( "_:" ) ;
output . push_str ( node . as_str ( ) ) ;
}
TermRef ::Literal ( literal ) = > {
let value = literal . value ( ) ;
if let Some ( language ) = literal . language ( ) {
write_tsv_quoted_str ( value , sink ) ? ;
write! ( sink , "@{language}" )
write_tsv_quoted_str ( output , value ) ;
output . push ( '@' ) ;
output . push_str ( language ) ;
} else {
match literal . datatype ( ) {
xsd ::BOOLEAN if is_turtle_boolean ( value ) = > sink . write_all ( value . as_bytes ( ) ) ,
xsd ::INTEGER if is_turtle_integer ( value ) = > sink . write_all ( value . as_bytes ( ) ) ,
xsd ::DECIMAL if is_turtle_decimal ( value ) = > sink . write_all ( value . as_bytes ( ) ) ,
xsd ::DOUBLE if is_turtle_double ( value ) = > sink . write_all ( value . as_bytes ( ) ) ,
xsd ::STRING = > write_tsv_quoted_str ( value , sink ) ,
xsd ::BOOLEAN if is_turtle_boolean ( value ) = > output . push_str ( value ) ,
xsd ::INTEGER if is_turtle_integer ( value ) = > output . push_str ( value ) ,
xsd ::DECIMAL if is_turtle_decimal ( value ) = > output . push_str ( value ) ,
xsd ::DOUBLE if is_turtle_double ( value ) = > output . push_str ( value ) ,
xsd ::STRING = > write_tsv_quoted_str ( output , value ) ,
datatype = > {
write_tsv_quoted_str ( value , sink ) ? ;
write! ( sink , "^^<{}>" , datatype . as_str ( ) )
write_tsv_quoted_str ( output , value ) ;
output . push_str ( "^^" ) ;
write_tsv_term ( output , datatype ) ;
}
}
}
}
#[ cfg(feature = " rdf-star " ) ]
TermRef ::Triple ( triple ) = > {
sink . write_all ( b" << " ) ? ;
write_tsv_term ( & triple . subject , sink ) ? ;
sink . write_all ( b" " ) ? ;
write_tsv_term ( & triple . predicate , sink ) ? ;
sink . write_all ( b" " ) ? ;
write_tsv_term ( & triple . object , sink ) ? ;
sink . write_all ( b" >> " ) ? ;
Ok ( ( ) )
output . push_str ( "<< " ) ;
write_tsv_term ( output , & triple . subject ) ;
output . push ( ' ' ) ;
write_tsv_term ( output , & triple . predicate ) ;
output . push ( ' ' ) ;
write_tsv_term ( output , & triple . object ) ;
output . push_str ( " >>" ) ;
}
}
}
fn write_tsv_quoted_str ( string : & str , f : & mut impl Write ) -> io ::Result < ( ) > {
f . write_all ( b" \" " ) ? ;
for c in string . byte s( ) {
fn write_tsv_quoted_str ( output : & mut String , string : & str ) {
output . push ( '"' ) ;
for c in string . char s( ) {
match c {
b '\t' = > f . write_all ( b" \\ t " ) ,
b '\n' = > f . write_all ( b" \\ n " ) ,
b '\r' = > f . write_all ( b" \\ r " ) ,
b '"' = > f . write_all ( b" \\ \" " ) ,
b '\\' = > f . write_all ( b" \\ \\ " ) ,
_ = > f . write_all ( & [ c ] ) ,
} ? ;
}
f . write_all ( b" \" " )
'\t' = > output . push_str ( "\\t ") ,
'\n' = > output . push_str ( "\\n ") ,
'\r' = > output . push_str ( "\\r ") ,
'"' = > output . push_str ( "\\\" ") ,
'\\' = > output . push_str ( "\\\\ ") ,
_ = > output . push ( c ) ,
} ;
}
output . push ( '"' ) ;
}
fn is_turtle_boolean ( value : & str ) -> bool {
@ -439,7 +588,7 @@ impl LineReader {
if let Some ( eol ) = memchr ( b'\n' , & buffer [ self . buffer_start .. self . buffer_end ] ) {
break self . buffer_start + eol + 1 ;
}
if self . buffer_start > buffer . len ( ) / 2 {
if self . buffer_start > 0 {
buffer . copy_within ( self . buffer_start .. self . buffer_end , 0 ) ;
self . buffer_end - = self . buffer_start ;
self . buffer_start = 0 ;
@ -480,7 +629,6 @@ mod tests {
use super ::* ;
use std ::error ::Error ;
use std ::rc ::Rc ;
use std ::str ;
fn build_example ( ) -> ( Vec < Variable > , Vec < Vec < Option < Term > > > ) {
(
@ -530,21 +678,21 @@ mod tests {
}
#[ test ]
fn test_csv_serialization ( ) -> io ::Result < ( ) > {
fn test_csv_serialization ( ) {
let ( variables , solutions ) = build_example ( ) ;
let mut writer = CsvSolutionsWriter ::start ( Vec ::new ( ) , variables . clone ( ) ) ? ;
let mut buffer = String ::new ( ) ;
let writer = InnerCsvSolutionsWriter ::start ( & mut buffer , variables . clone ( ) ) ;
let variables = Rc ::new ( variables ) ;
for solution in solutions {
writer . write (
& mut buffer ,
variables
. iter ( )
. zip ( & solution )
. filter_map ( | ( v , s ) | s . as_ref ( ) . map ( | s | ( v . as_ref ( ) , s . as_ref ( ) ) ) ) ,
) ? ;
) ;
}
let result = writer . finish ( ) ;
assert_eq! ( str ::from_utf8 ( & result ) . unwrap ( ) , "x,literal\r\nhttp://example/x,String\r\nhttp://example/x,\"String-with-dquote\"\"\"\r\n_:b0,Blank node\r\n,Missing 'x'\r\n,\r\nhttp://example/x,\r\n_:b1,String-with-lang\r\n_:b1,123\r\n,\"escape,\t\r\n\"\r\n" ) ;
Ok ( ( ) )
assert_eq! ( buffer , "x,literal\r\nhttp://example/x,String\r\nhttp://example/x,\"String-with-dquote\"\"\"\r\n_:b0,Blank node\r\n,Missing 'x'\r\n,\r\nhttp://example/x,\r\n_:b1,String-with-lang\r\n_:b1,123\r\n,\"escape,\t\r\n\"\r\n" ) ;
}
#[ test ]
@ -552,24 +700,25 @@ mod tests {
let ( variables , solutions ) = build_example ( ) ;
// Write
let mut writer = TsvSolutionsWriter ::start ( Vec ::new ( ) , variables . clone ( ) ) ? ;
let mut buffer = String ::new ( ) ;
let writer = InnerTsvSolutionsWriter ::start ( & mut buffer , variables . clone ( ) ) ;
let variables = Rc ::new ( variables ) ;
for solution in & solutions {
writer . write (
& mut buffer ,
variables
. iter ( )
. zip ( solution )
. filter_map ( | ( v , s ) | s . as_ref ( ) . map ( | s | ( v . as_ref ( ) , s . as_ref ( ) ) ) ) ,
) ? ;
) ;
}
let result = writer . finish ( ) ;
assert_eq! ( str ::from_utf8 ( & result ) . unwrap ( ) , "?x\t?literal\n<http://example/x>\t\"String\"\n<http://example/x>\t\"String-with-dquote\\\"\"\n_:b0\t\"Blank node\"\n\t\"Missing 'x'\"\n\t\n<http://example/x>\t\n_:b1\t\"String-with-lang\"@en\n_:b1\t123\n\t\"escape,\\t\\r\\n\"\n" ) ;
assert_eq! ( buffer , "?x\t?literal\n<http://example/x>\t\"String\"\n<http://example/x>\t\"String-with-dquote\\\"\"\n_:b0\t\"Blank node\"\n\t\"Missing 'x'\"\n\t\n<http://example/x>\t\n_:b1\t\"String-with-lang\"@en\n_:b1\t123\n\t\"escape,\\t\\r\\n\"\n" ) ;
// Read
if let TsvQueryResultsReader ::Solutions {
solutions : mut solutions_iter ,
variables : actual_variables ,
} = TsvQueryResultsReader ::read ( result . as_slice ( ) ) ?
} = TsvQueryResultsReader ::read ( buffer . as_bytes ( ) ) ?
{
assert_eq! ( actual_variables . as_slice ( ) , variables . as_slice ( ) ) ;
let mut rows = Vec ::new ( ) ;
@ -610,21 +759,19 @@ mod tests {
}
#[ test ]
fn test_no_columns_csv_serialization ( ) -> io ::Result < ( ) > {
let mut writer = CsvSolutionsWriter ::start ( Vec ::new ( ) , Vec ::new ( ) ) ? ;
writer . write ( [ ] ) ? ;
let result = writer . finish ( ) ;
assert_eq! ( str ::from_utf8 ( & result ) . unwrap ( ) , "\r\n\r\n" ) ;
Ok ( ( ) )
fn test_no_columns_csv_serialization ( ) {
let mut buffer = String ::new ( ) ;
let writer = InnerCsvSolutionsWriter ::start ( & mut buffer , Vec ::new ( ) ) ;
writer . write ( & mut buffer , [ ] ) ;
assert_eq! ( buffer , "\r\n\r\n" ) ;
}
#[ test ]
fn test_no_columns_tsv_serialization ( ) -> io ::Result < ( ) > {
let mut writer = TsvSolutionsWriter ::start ( Vec ::new ( ) , Vec ::new ( ) ) ? ;
writer . write ( [ ] ) ? ;
let result = writer . finish ( ) ;
assert_eq! ( str ::from_utf8 ( & result ) . unwrap ( ) , "\n\n" ) ;
Ok ( ( ) )
fn test_no_columns_tsv_serialization ( ) {
let mut buffer = String ::new ( ) ;
let writer = InnerTsvSolutionsWriter ::start ( & mut buffer , Vec ::new ( ) ) ;
writer . write ( & mut buffer , [ ] ) ;
assert_eq! ( buffer , "\n\n" ) ;
}
#[ test ]
@ -644,19 +791,17 @@ mod tests {
}
#[ test ]
fn test_no_results_csv_serialization ( ) -> io ::Result < ( ) > {
let result =
CsvSolutionsWriter ::start ( Vec ::new ( ) , vec! [ Variable ::new_unchecked ( "a" ) ] ) ? . finish ( ) ;
assert_eq! ( str ::from_utf8 ( & result ) . unwrap ( ) , "a\r\n" ) ;
Ok ( ( ) )
fn test_no_results_csv_serialization ( ) {
let mut buffer = String ::new ( ) ;
InnerCsvSolutionsWriter ::start ( & mut buffer , vec! [ Variable ::new_unchecked ( "a" ) ] ) ;
assert_eq! ( buffer , "a\r\n" ) ;
}
#[ test ]
fn test_no_results_tsv_serialization ( ) -> io ::Result < ( ) > {
let result =
TsvSolutionsWriter ::start ( Vec ::new ( ) , vec! [ Variable ::new_unchecked ( "a" ) ] ) ? . finish ( ) ;
assert_eq! ( str ::from_utf8 ( & result ) . unwrap ( ) , "?a\n" ) ;
Ok ( ( ) )
fn test_no_results_tsv_serialization ( ) {
let mut buffer = String ::new ( ) ;
InnerTsvSolutionsWriter ::start ( & mut buffer , vec! [ Variable ::new_unchecked ( "a" ) ] ) ;
assert_eq! ( buffer , "?a\n" ) ;
}
#[ test ]