1717
1818use std:: collections:: HashMap ;
1919use std:: ffi:: c_void;
20+ use std:: ptr;
2021
2122use arrow_array:: ffi:: { FFI_ArrowArray , FFI_ArrowSchema } ;
2223use arrow_array:: { Array , StructArray } ;
@@ -27,8 +28,9 @@ use paimon::Plan;
2728
2829use crate :: error:: { check_non_null, paimon_error, validate_cstr, PaimonErrorCode } ;
2930use crate :: result:: {
30- paimon_result_new_read, paimon_result_next_batch, paimon_result_plan, paimon_result_predicate,
31- paimon_result_read_builder, paimon_result_record_batch_reader, paimon_result_table_scan,
31+ paimon_result_get_table, paimon_result_new_read, paimon_result_next_batch, paimon_result_plan,
32+ paimon_result_predicate, paimon_result_read_builder, paimon_result_record_batch_reader,
33+ paimon_result_table_scan,
3234} ;
3335use crate :: runtime;
3436use crate :: types:: * ;
@@ -61,12 +63,96 @@ unsafe fn box_table_read_state(state: TableReadState) -> *mut paimon_table_read
6163/// Free a paimon_table.
6264///
6365/// # Safety
64- /// Only call with a table returned from `paimon_catalog_get_table`.
66+ /// Only call with a table returned from `paimon_catalog_get_table` or
67+ /// `paimon_table_from_descriptor`.
6568#[ no_mangle]
6669pub unsafe extern "C" fn paimon_table_free ( table : * mut paimon_table ) {
6770 free_table_wrapper ( table, |t| t. inner ) ;
6871}
6972
73+ /// Build a catalog-free `paimon_table` (intended for scan/read) from an
74+ /// FE-provided `TableDescriptor` JSON, without any catalog lookup.
75+ ///
76+ /// `descriptor_json` is a NUL-terminated UTF-8 JSON string (see the Rust
77+ /// `paimon::table::TableDescriptor`). `options` is an array of `options_len`
78+ /// storage/runtime `paimon_option` values (credentials, endpoints), or null
79+ /// when `options_len` is 0; they configure `FileIO` only and never change table
80+ /// semantics. The returned table is intended for scan/read.
81+ ///
82+ /// On success `table` is non-null and `error` is null; free it with
83+ /// `paimon_table_free`. On failure `table` is null and `error` is non-null.
84+ ///
85+ /// # Safety
86+ /// `descriptor_json` must be a valid C string. `options` must point to
87+ /// `options_len` valid `paimon_option` values, or be null when `options_len` is 0.
88+ #[ no_mangle]
89+ pub unsafe extern "C" fn paimon_table_from_descriptor (
90+ descriptor_json : * const std:: ffi:: c_char ,
91+ options : * const paimon_option ,
92+ options_len : usize ,
93+ ) -> paimon_result_get_table {
94+ let json = match validate_cstr ( descriptor_json, "descriptor_json" ) {
95+ Ok ( s) => s,
96+ Err ( e) => {
97+ return paimon_result_get_table {
98+ table : ptr:: null_mut ( ) ,
99+ error : e,
100+ }
101+ }
102+ } ;
103+
104+ if options. is_null ( ) && options_len > 0 {
105+ return paimon_result_get_table {
106+ table : ptr:: null_mut ( ) ,
107+ error : paimon_error:: new (
108+ PaimonErrorCode :: InvalidInput ,
109+ "null options pointer with non-zero length" . to_string ( ) ,
110+ ) ,
111+ } ;
112+ }
113+ let mut opts: HashMap < String , String > = HashMap :: new ( ) ;
114+ if options_len > 0 {
115+ let options_slice = std:: slice:: from_raw_parts ( options, options_len) ;
116+ for opt in options_slice {
117+ let key = match validate_cstr ( opt. key , "option key" ) {
118+ Ok ( s) => s,
119+ Err ( e) => {
120+ return paimon_result_get_table {
121+ table : ptr:: null_mut ( ) ,
122+ error : e,
123+ }
124+ }
125+ } ;
126+ let value = match validate_cstr ( opt. value , "option value" ) {
127+ Ok ( s) => s,
128+ Err ( e) => {
129+ return paimon_result_get_table {
130+ table : ptr:: null_mut ( ) ,
131+ error : e,
132+ }
133+ }
134+ } ;
135+ opts. insert ( key, value) ;
136+ }
137+ }
138+
139+ match Table :: from_descriptor_json ( & json, opts) {
140+ Ok ( table) => {
141+ let wrapper = Box :: new ( paimon_table {
142+ inner : Box :: into_raw ( Box :: new ( table) ) as * mut c_void ,
143+ } ) ;
144+ paimon_result_get_table {
145+ table : Box :: into_raw ( wrapper) ,
146+ error : ptr:: null_mut ( ) ,
147+ }
148+ }
149+ Err ( e) => paimon_result_get_table {
150+ table : ptr:: null_mut ( ) ,
151+ error : paimon_error:: from_paimon ( e) ,
152+ } ,
153+ }
154+ }
155+
70156/// Time-travel selector option names, in the core's resolution priority order.
71157const TIME_TRAVEL_SELECTORS : [ & str ; 4 ] = [
72158 "scan.timestamp-millis" ,
@@ -1787,6 +1873,16 @@ const _: unsafe extern "C" fn(
17871873const _: unsafe extern "C" fn ( * const u8 , usize ) -> paimon_result_plan =
17881874 paimon_plan_from_split_bytes;
17891875
1876+ // Descriptor table constructor ABI signature guard. Pins the catalog-free table
1877+ // constructor so an accidental signature change fails to compile rather than
1878+ // silently breaking header consumers. To add behavior, introduce a new symbol
1879+ // instead of changing this one.
1880+ const _: unsafe extern "C" fn (
1881+ * const std:: ffi:: c_char ,
1882+ * const paimon_option ,
1883+ usize ,
1884+ ) -> paimon_result_get_table = paimon_table_from_descriptor;
1885+
17901886#[ cfg( test) ]
17911887mod tests {
17921888 use super :: * ;
0 commit comments