1616// under the License.
1717
1818pub mod instrumented;
19+ pub ( crate ) mod stdin;
1920
2021use async_trait:: async_trait;
2122use aws_config:: BehaviorVersion ;
@@ -28,22 +29,18 @@ use datafusion::{
2829 config:: ExtensionOptions , config:: TableOptions , config:: Visit , config_err,
2930 exec_datafusion_err, exec_err,
3031 } ,
31- config:: ConfigFileType ,
3232 error:: { DataFusionError , Result } ,
3333 execution:: context:: SessionState ,
3434} ;
3535use log:: debug;
3636use object_store:: {
3737 ClientOptions , CredentialProvider ,
3838 Error :: Generic ,
39- ObjectStore , ObjectStoreExt ,
39+ ObjectStore ,
4040 aws:: { AmazonS3Builder , AmazonS3ConfigKey , AwsCredential } ,
4141 gcp:: GoogleCloudStorageBuilder ,
4242 http:: HttpBuilder ,
43- memory:: InMemory ,
44- path:: Path as ObjectStorePath ,
4543} ;
46- use std:: io:: Read ;
4744use std:: {
4845 any:: Any ,
4946 error:: Error ,
@@ -568,7 +565,9 @@ pub(crate) async fn get_object_store(
568565 . with_url ( url. origin ( ) . ascii_serialization ( ) )
569566 . build ( ) ?,
570567 ) ,
571- STDIN_SCHEME => stdin_object_store ( url) . await ?,
568+ _ if scheme == stdin:: StdinUtils :: SCHEME => {
569+ stdin:: StdinUtils :: object_store ( url) . await ?
570+ }
572571 _ => {
573572 // For other types, try to get from `object_store_registry`:
574573 state
@@ -583,75 +582,6 @@ pub(crate) async fn get_object_store(
583582 Ok ( store)
584583}
585584
586- /// The URL scheme used to expose the process's standard input as an object
587- /// store, mirroring how `s3`, `gs`, `http`, etc. are addressed.
588- const STDIN_SCHEME : & str = "stdin" ;
589-
590- /// Filesystem paths that refer to the process's standard input.
591- ///
592- /// These are intentionally limited to the well known pseudo-files exposed by
593- /// the operating system so that ordinary files are never accidentally treated
594- /// as stdin.
595- const STDIN_LOCATIONS : [ & str ; 3 ] = [ "/dev/stdin" , "/dev/fd/0" , "/proc/self/fd/0" ] ;
596-
597- /// Returns `true` if `location` refers to the process's standard input.
598- fn is_stdin_location ( location : & str ) -> bool {
599- STDIN_LOCATIONS . contains ( & location)
600- }
601-
602- /// Rewrites the well known stdin pseudo-paths (e.g. `/dev/stdin`) to a canonical
603- /// `stdin://` URL so that reading from standard input flows through the same
604- /// object-store/listing code path as any other scheme. Non-stdin locations are
605- /// returned unchanged.
606- ///
607- /// The listing layer filters candidate files by extension, so the canonical
608- /// object is named with the extension matching the declared `STORED AS` format.
609- pub ( crate ) fn rewrite_stdin_location (
610- location : & str ,
611- format : Option < & ConfigFileType > ,
612- ) -> String {
613- if !is_stdin_location ( location) {
614- return location. to_string ( ) ;
615- }
616-
617- let object_name = match format {
618- Some ( ConfigFileType :: CSV ) => "stdin.csv" ,
619- Some ( ConfigFileType :: JSON ) => "stdin.json" ,
620- Some ( ConfigFileType :: PARQUET ) => "stdin.parquet" ,
621- _ => "stdin" ,
622- } ;
623- format ! ( "{STDIN_SCHEME}:///{object_name}" )
624- }
625-
626- /// Builds the object store backing the `stdin://` scheme by reading all of
627- /// standard input into memory.
628- ///
629- /// A pipe (e.g. `cat data.csv | datafusion-cli`) is not seekable and reports a
630- /// size of `0`, so it cannot be read directly by the file based formats (CSV
631- /// requires seeking, Parquet needs the footer at the end of the file). Buffering
632- /// the whole input up front sidesteps these limitations and lets the data be
633- /// read like any other object, including being scanned more than once.
634- async fn stdin_object_store ( url : & Url ) -> Result < Arc < dyn ObjectStore > > {
635- let mut buffer = Vec :: new ( ) ;
636- std:: io:: stdin ( )
637- . lock ( )
638- . read_to_end ( & mut buffer)
639- . map_err ( |e| exec_datafusion_err ! ( "Failed to read from stdin: {e}" ) ) ?;
640- in_memory_object_store ( url, buffer) . await
641- }
642-
643- /// Stores `data` at the path referenced by `url` in a fresh [`InMemory`] store.
644- async fn in_memory_object_store (
645- url : & Url ,
646- data : Vec < u8 > ,
647- ) -> Result < Arc < dyn ObjectStore > > {
648- let store = InMemory :: new ( ) ;
649- store
650- . put ( & ObjectStorePath :: from_url_path ( url. path ( ) ) ?, data. into ( ) )
651- . await ?;
652- Ok ( Arc :: new ( store) )
653- }
654-
655585#[ cfg( test) ]
656586mod tests {
657587 use crate :: cli_context:: CliSessionContext ;
@@ -991,107 +921,4 @@ mod tests {
991921 }
992922 Ok ( ( ) )
993923 }
994-
995- #[ test]
996- fn rewrites_stdin_locations ( ) {
997- // stdin pseudo-paths are rewritten to a `stdin://` URL carrying the
998- // extension that matches the declared format.
999- assert_eq ! (
1000- rewrite_stdin_location( "/dev/stdin" , Some ( & ConfigFileType :: CSV ) ) ,
1001- "stdin:///stdin.csv"
1002- ) ;
1003- assert_eq ! (
1004- rewrite_stdin_location( "/dev/fd/0" , Some ( & ConfigFileType :: JSON ) ) ,
1005- "stdin:///stdin.json"
1006- ) ;
1007- assert_eq ! (
1008- rewrite_stdin_location( "/proc/self/fd/0" , Some ( & ConfigFileType :: PARQUET ) ) ,
1009- "stdin:///stdin.parquet"
1010- ) ;
1011- assert_eq ! ( rewrite_stdin_location( "/dev/stdin" , None ) , "stdin:///stdin" ) ;
1012-
1013- // Ordinary locations are left untouched.
1014- for location in [ "/dev/stdout" , "data/stdin.csv" , "stdin" , "s3://b/f.csv" ] {
1015- assert_eq ! (
1016- rewrite_stdin_location( location, Some ( & ConfigFileType :: CSV ) ) ,
1017- location
1018- ) ;
1019- }
1020- }
1021-
1022- /// Buffers `data` into the `stdin://` object store and reads it back through
1023- /// a `CREATE EXTERNAL TABLE`, returning the number of rows in the table.
1024- ///
1025- /// This exercises the full path used for `/dev/stdin` short of the actual
1026- /// stdin read, which cannot be driven from a unit test.
1027- async fn count_stdin_rows (
1028- data : Vec < u8 > ,
1029- stored_as : & str ,
1030- format : Option < ConfigFileType > ,
1031- options : & str ,
1032- ) -> Result < usize > {
1033- let location = rewrite_stdin_location ( "/dev/stdin" , format. as_ref ( ) ) ;
1034- let url = Url :: parse ( & location) . unwrap ( ) ;
1035- let store = in_memory_object_store ( & url, data) . await ?;
1036-
1037- let ctx = SessionContext :: new ( ) ;
1038- ctx. register_object_store ( & url, store) ;
1039- ctx. sql ( & format ! (
1040- "CREATE EXTERNAL TABLE t STORED AS {stored_as} LOCATION '{location}' {options}"
1041- ) )
1042- . await ?
1043- . collect ( )
1044- . await ?;
1045-
1046- ctx. sql ( "SELECT * FROM t" ) . await ?. count ( ) . await
1047- }
1048-
1049- #[ tokio:: test]
1050- async fn stdin_object_store_reads_csv ( ) -> Result < ( ) > {
1051- let data = b"a,b\n 1,foo\n 2,bar\n " . to_vec ( ) ;
1052- let rows = count_stdin_rows (
1053- data,
1054- "CSV" ,
1055- Some ( ConfigFileType :: CSV ) ,
1056- "OPTIONS ('format.has_header' 'true')" ,
1057- )
1058- . await ?;
1059- assert_eq ! ( rows, 2 ) ;
1060- Ok ( ( ) )
1061- }
1062-
1063- #[ tokio:: test]
1064- async fn stdin_object_store_reads_json ( ) -> Result < ( ) > {
1065- let data = b"{\" a\" : 1, \" b\" : \" foo\" }\n {\" a\" : 2, \" b\" : \" bar\" }\n " . to_vec ( ) ;
1066- let rows = count_stdin_rows ( data, "JSON" , Some ( ConfigFileType :: JSON ) , "" ) . await ?;
1067- assert_eq ! ( rows, 2 ) ;
1068- Ok ( ( ) )
1069- }
1070-
1071- #[ tokio:: test]
1072- async fn stdin_object_store_reads_parquet ( ) -> Result < ( ) > {
1073- use datafusion:: arrow:: array:: Int32Array ;
1074- use datafusion:: arrow:: datatypes:: { DataType , Field , Schema } ;
1075- use datafusion:: arrow:: record_batch:: RecordBatch ;
1076- use parquet:: arrow:: ArrowWriter ;
1077-
1078- // Parquet requires random access to the footer, which a real pipe cannot
1079- // provide; the in-memory buffer makes this work.
1080- let schema = Arc :: new ( Schema :: new ( vec ! [ Field :: new( "a" , DataType :: Int32 , false ) ] ) ) ;
1081- let batch = RecordBatch :: try_new (
1082- Arc :: clone ( & schema) ,
1083- vec ! [ Arc :: new( Int32Array :: from( vec![ 1 , 2 , 3 ] ) ) ] ,
1084- )
1085- . unwrap ( ) ;
1086-
1087- let mut data = Vec :: new ( ) ;
1088- let mut writer = ArrowWriter :: try_new ( & mut data, schema, None ) . unwrap ( ) ;
1089- writer. write ( & batch) . unwrap ( ) ;
1090- writer. close ( ) . unwrap ( ) ;
1091-
1092- let rows =
1093- count_stdin_rows ( data, "PARQUET" , Some ( ConfigFileType :: PARQUET ) , "" ) . await ?;
1094- assert_eq ! ( rows, 3 ) ;
1095- Ok ( ( ) )
1096- }
1097924}
0 commit comments