@@ -10,6 +10,7 @@ use duckdb::vtab::arrow::{arrow_recordbatch_to_query_params, ArrowVTab};
1010use duckdb:: { params, Connection } ;
1111use polars:: io:: SerWriter ;
1212use polars:: prelude:: * ;
13+ use std:: collections:: HashSet ;
1314use std:: io:: Cursor ;
1415
1516/// DuckDB database reader
@@ -32,6 +33,7 @@ use std::io::Cursor;
3233/// ```
3334pub struct DuckDBReader {
3435 conn : Connection ,
36+ registered_tables : HashSet < String > ,
3537}
3638
3739impl DuckDBReader {
@@ -75,7 +77,10 @@ impl DuckDBReader {
7577 GgsqlError :: ReaderError ( format ! ( "Failed to register arrow function: {}" , e) )
7678 } ) ?;
7779
78- Ok ( Self { conn } )
80+ Ok ( Self {
81+ conn,
82+ registered_tables : HashSet :: new ( ) ,
83+ } )
7984 }
8085
8186 /// Get a reference to the underlying DuckDB connection
@@ -523,6 +528,30 @@ impl Reader for DuckDBReader {
523528 GgsqlError :: ReaderError ( format ! ( "Failed to register table '{}': {}" , name, e) )
524529 } ) ?;
525530
531+ // Track the table so we can unregister it later
532+ self . registered_tables . insert ( name. to_string ( ) ) ;
533+
534+ Ok ( ( ) )
535+ }
536+
537+ fn unregister ( & mut self , name : & str ) -> Result < ( ) > {
538+ // Only allow unregistering tables we created via register()
539+ if !self . registered_tables . contains ( name) {
540+ return Err ( GgsqlError :: ReaderError ( format ! (
541+ "Table '{}' was not registered via this reader" ,
542+ name
543+ ) ) ) ;
544+ }
545+
546+ // Drop the temp table
547+ let sql = format ! ( "DROP TABLE IF EXISTS \" {}\" " , name) ;
548+ self . conn . execute ( & sql, [ ] ) . map_err ( |e| {
549+ GgsqlError :: ReaderError ( format ! ( "Failed to unregister table '{}': {}" , name, e) )
550+ } ) ?;
551+
552+ // Remove from tracking
553+ self . registered_tables . remove ( name) ;
554+
526555 Ok ( ( ) )
527556 }
528557
@@ -704,4 +733,54 @@ mod tests {
704733 assert_eq ! ( result. shape( ) , ( 0 , 2 ) ) ;
705734 assert_eq ! ( result. get_column_names( ) , vec![ "x" , "y" ] ) ;
706735 }
736+
737+ #[ test]
738+ fn test_unregister ( ) {
739+ let mut reader = DuckDBReader :: from_connection_string ( "duckdb://memory" ) . unwrap ( ) ;
740+ let df = DataFrame :: new ( vec ! [ Column :: new( "x" . into( ) , vec![ 1i32 , 2 , 3 ] ) ] ) . unwrap ( ) ;
741+
742+ reader. register ( "test_data" , df) . unwrap ( ) ;
743+
744+ // Should be queryable
745+ let result = reader. execute_sql ( "SELECT * FROM test_data" ) . unwrap ( ) ;
746+ assert_eq ! ( result. height( ) , 3 ) ;
747+
748+ // Unregister
749+ reader. unregister ( "test_data" ) . unwrap ( ) ;
750+
751+ // Should no longer exist
752+ let result = reader. execute_sql ( "SELECT * FROM test_data" ) ;
753+ assert ! ( result. is_err( ) ) ;
754+ }
755+
756+ #[ test]
757+ fn test_unregister_not_registered ( ) {
758+ let mut reader = DuckDBReader :: from_connection_string ( "duckdb://memory" ) . unwrap ( ) ;
759+
760+ // Create a table directly (not via register)
761+ reader
762+ . connection ( )
763+ . execute ( "CREATE TABLE user_table (x INT)" , params ! [ ] )
764+ . unwrap ( ) ;
765+
766+ // Should fail - we didn't register this via register()
767+ let result = reader. unregister ( "user_table" ) ;
768+ assert ! ( result. is_err( ) ) ;
769+ let err = result. unwrap_err ( ) . to_string ( ) ;
770+ assert ! ( err. contains( "was not registered via this reader" ) ) ;
771+ }
772+
773+ #[ test]
774+ fn test_reregister_after_unregister ( ) {
775+ let mut reader = DuckDBReader :: from_connection_string ( "duckdb://memory" ) . unwrap ( ) ;
776+ let df = DataFrame :: new ( vec ! [ Column :: new( "x" . into( ) , vec![ 1i32 , 2 , 3 ] ) ] ) . unwrap ( ) ;
777+
778+ reader. register ( "data" , df. clone ( ) ) . unwrap ( ) ;
779+ reader. unregister ( "data" ) . unwrap ( ) ;
780+
781+ // Should be able to register again
782+ reader. register ( "data" , df) . unwrap ( ) ;
783+ let result = reader. execute_sql ( "SELECT * FROM data" ) . unwrap ( ) ;
784+ assert_eq ! ( result. height( ) , 3 ) ;
785+ }
707786}
0 commit comments