Skip to content

Commit f69804a

Browse files
authored
feat(rust): implement getProcedures, getProcedureColumns (#366)
## Summary - Implement `getProcedures`, `getProcedureColumns` metadata operations - Follow the existing `getTables`/`getColumns` pattern with trait methods, SQL builders, SeaClient impl, FFI functions, and C header declarations - Add comprehensive E2E metadata tests covering all metadata operations ### getProcedures - Queries `information_schema.routines` filtered by `routine_type = 'PROCEDURE'` - Catalog resolution: `NULL` → `system` (cross-catalog), specific value → that catalog, empty string → empty result - Pattern filtering via SQL `LIKE` on `routine_schema` and `routine_name` ### getProcedureColumns - Queries `information_schema.parameters` joined with `information_schema.routines` - Same catalog resolution and pattern filtering as getProcedures - Selects all columns needed for ODBC `SQLProcedureColumns`: parameter_name, parameter_mode, is_result, data_type, full_data_type, numeric_precision/scale, character_maximum/octet_length, ordinal_position, parameter_default, comment ### getCrossReferences - Uses `SHOW FOREIGN KEYS` (same query as `getForeignKeys`) - Takes all 6 ODBC `SQLCrossReferences` parameters (pk_catalog/schema/table + fk_catalog/schema/table) - Parent table filtering is done client-side by the ODBC C++ layer ### Design references - JDBC implementation: databricks/databricks-jdbc#1238 - Design doc: [SQLProcedures and SQLProcedureColumns Support](https://docs.google.com/document/d/1WV1hOiJA8Obs9q3o47u7cvZCwnRvKg8kWI8gZhuFFaY) ## Test plan - [x] 12 unit tests for SQL builders (catalog resolution, pattern filters, escaping, column selection) - [x] 12 E2E metadata tests (`#[ignore]`, require real Databricks connection) covering all operations: catalogs, schemas, tables, columns, primary keys, foreign keys, cross-references, procedures, procedure columns - [x] `cargo test` — 237 tests pass - [x] `cargo clippy --all-targets -- -D warnings` — clean - [x] `cargo +stable fmt --all` — clean - [ ] E2E validation against live workspace (blocked on token — tests compile and are properly `#[ignore]`d) This pull request was AI-assisted by Isaac.
1 parent 7c2e308 commit f69804a

File tree

7 files changed

+964
-2
lines changed

7 files changed

+964
-2
lines changed

rust/include/databricks_metadata_ffi.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,52 @@ FfiStatus metadata_get_foreign_keys(
194194
struct ArrowArrayStream* out
195195
);
196196

197+
/**
198+
* List procedures matching the given filter criteria.
199+
*
200+
* Queries information_schema.routines filtered by routine_type = 'PROCEDURE'.
201+
* When catalog is NULL, queries system.information_schema (cross-catalog).
202+
* When catalog is empty string, returns an empty result set.
203+
*
204+
* @param conn Pointer to the Rust Connection.
205+
* @param catalog Catalog name filter (NULL = cross-catalog via system).
206+
* @param schema_pattern Schema name pattern (NULL = no filter).
207+
* @param procedure_pattern Procedure name pattern (NULL = no filter).
208+
* @param[out] out Arrow stream to populate.
209+
* @return FfiStatus code.
210+
*/
211+
FfiStatus metadata_get_procedures(
212+
const void* conn,
213+
const char* catalog,
214+
const char* schema_pattern,
215+
const char* procedure_pattern,
216+
struct ArrowArrayStream* out
217+
);
218+
219+
/**
220+
* List procedure columns (parameters) matching the given filter criteria.
221+
*
222+
* Queries information_schema.parameters joined with information_schema.routines.
223+
* When catalog is NULL, queries system.information_schema (cross-catalog).
224+
* When catalog is empty string, returns an empty result set.
225+
*
226+
* @param conn Pointer to the Rust Connection.
227+
* @param catalog Catalog name filter (NULL = cross-catalog via system).
228+
* @param schema_pattern Schema name pattern (NULL = no filter).
229+
* @param procedure_pattern Procedure name pattern (NULL = no filter).
230+
* @param column_pattern Column/parameter name pattern (NULL = no filter).
231+
* @param[out] out Arrow stream to populate.
232+
* @return FfiStatus code.
233+
*/
234+
FfiStatus metadata_get_procedure_columns(
235+
const void* conn,
236+
const char* catalog,
237+
const char* schema_pattern,
238+
const char* procedure_pattern,
239+
const char* column_pattern,
240+
struct ArrowArrayStream* out
241+
);
242+
197243
#ifdef __cplusplus
198244
}
199245
#endif

rust/src/client/mod.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,34 @@ pub trait DatabricksClient: Send + Sync + std::fmt::Debug {
215215
column_pattern: Option<&str>,
216216
) -> Result<ExecuteResult>;
217217

218+
/// List procedures, optionally filtered by catalog, schema pattern, and procedure name pattern.
219+
///
220+
/// Uses `information_schema.routines` filtered by `routine_type = 'PROCEDURE'`.
221+
/// When catalog is None, queries `system.information_schema.routines` (cross-catalog).
222+
/// When catalog is empty string, returns empty result.
223+
async fn list_procedures(
224+
&self,
225+
session_id: &str,
226+
catalog: Option<&str>,
227+
schema_pattern: Option<&str>,
228+
procedure_pattern: Option<&str>,
229+
) -> Result<ExecuteResult>;
230+
231+
/// List procedure columns (parameters), optionally filtered.
232+
///
233+
/// Uses `information_schema.parameters` joined with `information_schema.routines`
234+
/// filtered by `routine_type = 'PROCEDURE'`.
235+
/// When catalog is None, queries `system.information_schema` (cross-catalog).
236+
/// When catalog is empty string, returns empty result.
237+
async fn list_procedure_columns(
238+
&self,
239+
session_id: &str,
240+
catalog: Option<&str>,
241+
schema_pattern: Option<&str>,
242+
procedure_pattern: Option<&str>,
243+
column_pattern: Option<&str>,
244+
) -> Result<ExecuteResult>;
245+
218246
/// List supported table types (static, no SQL executed).
219247
fn list_table_types(&self) -> Vec<String>;
220248
}

rust/src/client/sea.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,39 @@ impl DatabricksClient for SeaClient {
605605
.await
606606
}
607607

608+
async fn list_procedures(
609+
&self,
610+
session_id: &str,
611+
catalog: Option<&str>,
612+
schema_pattern: Option<&str>,
613+
procedure_pattern: Option<&str>,
614+
) -> Result<ExecuteResult> {
615+
let sql =
616+
SqlCommandBuilder::build_get_procedures(catalog, schema_pattern, procedure_pattern);
617+
debug!("list_procedures: {}", sql);
618+
self.execute_statement(session_id, &sql, &ExecuteParams::default())
619+
.await
620+
}
621+
622+
async fn list_procedure_columns(
623+
&self,
624+
session_id: &str,
625+
catalog: Option<&str>,
626+
schema_pattern: Option<&str>,
627+
procedure_pattern: Option<&str>,
628+
column_pattern: Option<&str>,
629+
) -> Result<ExecuteResult> {
630+
let sql = SqlCommandBuilder::build_get_procedure_columns(
631+
catalog,
632+
schema_pattern,
633+
procedure_pattern,
634+
column_pattern,
635+
);
636+
debug!("list_procedure_columns: {}", sql);
637+
self.execute_statement(session_id, &sql, &ExecuteParams::default())
638+
.await
639+
}
640+
608641
fn list_table_types(&self) -> Vec<String> {
609642
vec![
610643
"SYSTEM TABLE".to_string(),

rust/src/ffi/metadata.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,121 @@ pub unsafe extern "C" fn metadata_get_foreign_keys(
397397
.unwrap_or_else(handle_panic)
398398
}
399399

400+
/// List procedures matching the given filter criteria.
401+
///
402+
/// Uses `information_schema.routines` filtered by `routine_type = 'PROCEDURE'`.
403+
/// When catalog is NULL, queries `system.information_schema` (cross-catalog).
404+
/// When catalog is empty string, returns an empty result set.
405+
///
406+
/// # Safety
407+
///
408+
/// - `conn` must be a valid pointer to a `Connection`
409+
/// - String arguments may be null (treated as no filter)
410+
/// - `out` must point to a valid, writable `FFI_ArrowArrayStream`
411+
#[no_mangle]
412+
pub unsafe extern "C" fn metadata_get_procedures(
413+
conn: *const c_void,
414+
catalog: *const c_char,
415+
schema_pattern: *const c_char,
416+
procedure_pattern: *const c_char,
417+
out: *mut FFI_ArrowArrayStream,
418+
) -> FfiStatus {
419+
clear_last_error();
420+
std::panic::catch_unwind(AssertUnwindSafe(|| {
421+
let conn = get_connection!(conn);
422+
let Ok(catalog) = (unsafe { c_str_to_option(catalog) }) else {
423+
return FfiStatus::Error;
424+
};
425+
let Ok(schema_pattern) = (unsafe { c_str_to_option(schema_pattern) }) else {
426+
return FfiStatus::Error;
427+
};
428+
let Ok(procedure_pattern) = (unsafe { c_str_to_option(procedure_pattern) }) else {
429+
return FfiStatus::Error;
430+
};
431+
432+
// Empty catalog → return empty result (no server round-trip).
433+
// ODBC layer constructs proper schema for the empty result set.
434+
if catalog == Some("") {
435+
let reader: Box<dyn ResultReader + Send> =
436+
Box::new(EmptyReader::new(Arc::new(Schema::empty())));
437+
return export_reader(reader, None, out);
438+
}
439+
440+
match conn
441+
.runtime_handle()
442+
.block_on(conn.client().list_procedures(
443+
conn.session_id(),
444+
catalog,
445+
schema_pattern,
446+
procedure_pattern,
447+
)) {
448+
Ok(result) => export_reader(result.reader, result.manifest.as_ref(), out),
449+
Err(e) => set_error_from_result(&e),
450+
}
451+
}))
452+
.unwrap_or_else(handle_panic)
453+
}
454+
455+
/// List procedure columns (parameters) matching the given filter criteria.
456+
///
457+
/// Uses `information_schema.parameters` joined with `information_schema.routines`.
458+
/// When catalog is NULL, queries `system.information_schema` (cross-catalog).
459+
/// When catalog is empty string, returns empty result with correct column schema.
460+
///
461+
/// # Safety
462+
///
463+
/// - `conn` must be a valid pointer to a `Connection`
464+
/// - String arguments may be null (treated as no filter)
465+
/// - `out` must point to a valid, writable `FFI_ArrowArrayStream`
466+
#[no_mangle]
467+
pub unsafe extern "C" fn metadata_get_procedure_columns(
468+
conn: *const c_void,
469+
catalog: *const c_char,
470+
schema_pattern: *const c_char,
471+
procedure_pattern: *const c_char,
472+
column_pattern: *const c_char,
473+
out: *mut FFI_ArrowArrayStream,
474+
) -> FfiStatus {
475+
clear_last_error();
476+
std::panic::catch_unwind(AssertUnwindSafe(|| {
477+
let conn = get_connection!(conn);
478+
let Ok(catalog) = (unsafe { c_str_to_option(catalog) }) else {
479+
return FfiStatus::Error;
480+
};
481+
let Ok(schema_pattern) = (unsafe { c_str_to_option(schema_pattern) }) else {
482+
return FfiStatus::Error;
483+
};
484+
let Ok(procedure_pattern) = (unsafe { c_str_to_option(procedure_pattern) }) else {
485+
return FfiStatus::Error;
486+
};
487+
let Ok(column_pattern) = (unsafe { c_str_to_option(column_pattern) }) else {
488+
return FfiStatus::Error;
489+
};
490+
491+
// Empty catalog → return empty result (no server round-trip).
492+
// ODBC layer constructs proper schema for the empty result set.
493+
if catalog == Some("") {
494+
let reader: Box<dyn ResultReader + Send> =
495+
Box::new(EmptyReader::new(Arc::new(Schema::empty())));
496+
return export_reader(reader, None, out);
497+
}
498+
499+
match conn
500+
.runtime_handle()
501+
.block_on(conn.client().list_procedure_columns(
502+
conn.session_id(),
503+
catalog,
504+
schema_pattern,
505+
procedure_pattern,
506+
column_pattern,
507+
)) {
508+
Ok(result) => export_reader(result.reader, result.manifest.as_ref(), out),
509+
Err(e) => set_error_from_result(&e),
510+
}
511+
}))
512+
.unwrap_or_else(handle_panic)
513+
}
514+
400515
#[cfg(test)]
401516
mod tests {
402517
use super::*;

0 commit comments

Comments
 (0)