@@ -13,7 +13,11 @@ use crate::odbc::{OdbcStatement, OdbcStatementMetadata};
1313use futures_core:: future:: BoxFuture ;
1414use futures_util:: future;
1515use odbc_api:: ConnectionTransitions ;
16- use odbc_api:: { handles:: StatementConnection , Prepared , ResultSetMetadata , SharedConnection } ;
16+ use odbc_api:: Error as OdbcApiError ;
17+ use odbc_api:: {
18+ handles:: { slice_to_cow_utf8, StatementConnection } ,
19+ Prepared , ResultSetMetadata , SharedConnection ,
20+ } ;
1721use odbc_bridge:: { establish_connection, execute_sql} ;
1822use std:: borrow:: Cow ;
1923use std:: sync:: { Arc , Mutex } ;
@@ -23,20 +27,28 @@ mod executor;
2327type PreparedStatement = Prepared < StatementConnection < SharedConnection < ' static > > > ;
2428type SharedPreparedStatement = Arc < Mutex < PreparedStatement > > ;
2529
30+ struct CollectedColumns {
31+ columns : Vec < OdbcColumn > ,
32+ deferred : bool ,
33+ }
34+
2635fn collect_columns (
2736 prepared : & mut PreparedStatement ,
2837 parameter_count : usize ,
2938 allow_deferred_result_columns : bool ,
30- ) -> Result < Vec < OdbcColumn > , Error > {
39+ ) -> Result < CollectedColumns , Error > {
3140 let count = match prepared. num_result_cols ( ) {
3241 Ok ( count) => count,
33- Err ( error) if allow_deferred_result_columns && parameter_count > 0 => {
34- // Some ODBC drivers only expose result columns for parameterized
35- // statements after values are bound and the statement is executed.
36- // Row execution still gets authoritative metadata from the cursor;
37- // describe() uses the strict path and will surface this error.
38- log:: debug!( "ODBC prepare did not expose result columns: {error}" ) ;
39- return Ok ( Vec :: new ( ) ) ;
42+ Err ( error)
43+ if allow_deferred_result_columns
44+ && parameter_count > 0
45+ && is_unbound_parameter_metadata_error ( & error) =>
46+ {
47+ log:: debug!( "ODBC prepare deferred result columns until execution: {error}" ) ;
48+ return Ok ( CollectedColumns {
49+ columns : Vec :: new ( ) ,
50+ deferred : true ,
51+ } ) ;
4052 }
4153 Err ( error) => return Err ( error. into ( ) ) ,
4254 } ;
@@ -45,19 +57,38 @@ fn collect_columns(
4557 for i in 1 ..=count {
4658 columns. push ( describe_column ( prepared, i as u16 ) ?) ;
4759 }
48- Ok ( columns)
60+ Ok ( CollectedColumns {
61+ columns,
62+ deferred : false ,
63+ } )
4964}
5065
5166fn collect_statement_metadata (
5267 prepared : & mut PreparedStatement ,
5368 allow_deferred_result_columns : bool ,
54- ) -> Result < OdbcStatementMetadata , Error > {
69+ ) -> Result < ( OdbcStatementMetadata , bool ) , Error > {
5570 let parameters = usize:: from ( prepared. num_params ( ) ?) ;
71+ let collected = collect_columns ( prepared, parameters, allow_deferred_result_columns) ?;
72+ let metadata_complete =
73+ !collected. deferred && !( parameters > 0 && collected. columns . is_empty ( ) ) ;
74+
75+ Ok ( (
76+ OdbcStatementMetadata {
77+ columns : collected. columns ,
78+ parameters,
79+ } ,
80+ metadata_complete,
81+ ) )
82+ }
5683
57- Ok ( OdbcStatementMetadata {
58- columns : collect_columns ( prepared, parameters, allow_deferred_result_columns) ?,
59- parameters,
60- } )
84+ fn is_unbound_parameter_metadata_error ( error : & OdbcApiError ) -> bool {
85+ match error {
86+ OdbcApiError :: Diagnostics { record, .. } if record. state . as_str ( ) == "01000" => {
87+ let message = slice_to_cow_utf8 ( & record. message ) . to_ascii_lowercase ( ) ;
88+ message. contains ( "parameter" ) && message. contains ( "bound" )
89+ }
90+ _ => false ,
91+ }
6192}
6293
6394pub ( super ) fn describe_column < S > ( stmt : & mut S , index : u16 ) -> Result < OdbcColumn , Error >
@@ -241,6 +272,7 @@ impl OdbcConnection {
241272 store_to_cache : bool ,
242273 allow_deferred_result_columns : bool ,
243274 ) -> Result < OdbcStatement < ' a > , Error > {
275+ let sql_owned = sql. to_string ( ) ;
244276 let cached = self
245277 . stmt_cache
246278 . get_mut ( sql)
@@ -252,6 +284,7 @@ impl OdbcConnection {
252284 Error :: Protocol ( "ODBC prepare: failed to lock prepared statement" . into ( ) )
253285 } ) ?;
254286 collect_statement_metadata ( & mut prepared, allow_deferred_result_columns)
287+ . map ( |( metadata, _) | metadata)
255288 } )
256289 . await ?;
257290
@@ -262,17 +295,16 @@ impl OdbcConnection {
262295 }
263296
264297 let conn = Arc :: clone ( & self . conn ) ;
265- let sql_owned = sql. to_string ( ) ;
266298 let sql_clone = sql_owned. clone ( ) ;
267- let ( prepared, metadata) = spawn_blocking ( move || {
299+ let ( prepared, metadata, metadata_complete ) = spawn_blocking ( move || {
268300 let mut prepared = conn. into_prepared ( & sql_clone) ?;
269301 let metadata =
270302 collect_statement_metadata ( & mut prepared, allow_deferred_result_columns) ?;
271- Ok :: < _ , Error > ( ( prepared, metadata) )
303+ Ok :: < _ , Error > ( ( prepared, metadata. 0 , metadata . 1 ) )
272304 } )
273305 . await ?;
274306
275- if store_to_cache && self . stmt_cache . is_enabled ( ) {
307+ if store_to_cache && metadata_complete && self . stmt_cache . is_enabled ( ) {
276308 self . stmt_cache
277309 . insert ( & sql_owned, Arc :: new ( Mutex :: new ( prepared) ) ) ;
278310 }
0 commit comments