Skip to main content

odbc_api/
preallocated.rs

1use crate::{
2    BlockCursorIterator, Cursor, CursorImpl, Error, ParameterCollectionRef, PreallocatedPolling,
3    TruncationInfo,
4    buffers::{FetchRow, FetchRowMember as _, RowVec},
5    execute::execute_with_parameters,
6    handles::{AsStatementRef, SqlText, Statement, StatementRef},
7    parameter::VarCharArray,
8};
9
10/// A preallocated SQL statement handle intended for sequential execution of different queries. See
11/// [`crate::Connection::preallocate`].
12///
13/// # Example
14///
15/// ```
16/// use odbc_api::{Connection, Error};
17/// use std::io::{self, stdin, Read};
18///
19/// fn interactive(conn: &Connection<'_>) -> io::Result<()>{
20///     let mut statement = conn.preallocate().unwrap();
21///     let mut query = String::new();
22///     stdin().read_line(&mut query)?;
23///     while !query.is_empty() {
24///         match statement.execute(&query, ()) {
25///             Err(e) => println!("{}", e),
26///             Ok(None) => println!("No results set generated."),
27///             Ok(Some(cursor)) => {
28///                 // ...print cursor contents...
29///             },
30///         }
31///         stdin().read_line(&mut query)?;
32///     }
33///     Ok(())
34/// }
35/// ```
36pub struct Preallocated<S> {
37    /// A valid statement handle.
38    statement: S,
39}
40
41impl<S> Preallocated<S>
42where
43    S: AsStatementRef,
44{
45    /// Users which intend to write their application in safe Rust should prefer using
46    /// [`crate::Connection::preallocate`] as opposed to this constructor.
47    ///
48    /// # Safety
49    ///
50    /// `statement` must be an allocated handled with no pointers bound for either results or
51    /// arguments. The statement must not be prepared, but in the state of a "freshly" allocated
52    /// handle.
53    pub unsafe fn new(statement: S) -> Self {
54        Self { statement }
55    }
56
57    /// Executes a statement. This is the fastest way to sequentially execute different SQL
58    /// Statements.
59    ///
60    /// This method produces a cursor which borrowes the statement handle. If you want to take
61    /// ownership you can use the sibling [`Self::into_cursor`].
62    ///
63    /// # Parameters
64    ///
65    /// * `query`: The text representation of the SQL statement. E.g. "SELECT * FROM my_table;".
66    /// * `params`: `?` may be used as a placeholder in the statement text. You can use `()` to
67    ///   represent no parameters. Check the [`crate::parameter`] module level documentation for
68    ///   more information on how to pass parameters.
69    ///
70    /// # Return
71    ///
72    /// Returns `Some` if a cursor is created. If `None` is returned no cursor has been created (
73    /// e.g. the query came back empty). Note that an empty query may also create a cursor with zero
74    /// rows. Since we want to reuse the statement handle a returned cursor will not take ownership
75    /// of it and instead borrow it.
76    ///
77    /// # Example
78    ///
79    /// ```
80    /// use odbc_api::{Connection, Error};
81    /// use std::io::{self, stdin, Read};
82    ///
83    /// fn interactive(conn: &Connection) -> io::Result<()>{
84    ///     let mut statement = conn.preallocate().unwrap();
85    ///     let mut query = String::new();
86    ///     stdin().read_line(&mut query)?;
87    ///     while !query.is_empty() {
88    ///         match statement.execute(&query, ()) {
89    ///             Err(e) => println!("{}", e),
90    ///             Ok(None) => println!("No results set generated."),
91    ///             Ok(Some(cursor)) => {
92    ///                 // ...print cursor contents...
93    ///             },
94    ///         }
95    ///         stdin().read_line(&mut query)?;
96    ///     }
97    ///     Ok(())
98    /// }
99    /// ```
100    pub fn execute(
101        &mut self,
102        query: &str,
103        params: impl ParameterCollectionRef,
104    ) -> Result<Option<CursorImpl<StatementRef<'_>>>, Error> {
105        let query = SqlText::new(query);
106        let stmt = self.statement.as_stmt_ref();
107        execute_with_parameters(stmt, Some(&query), params)
108    }
109
110    /// Similar to [`Self::execute`], but transfers ownership of the statement handle to the
111    /// resulting cursor if any is created. This makes this method not suitable to repeatedly
112    /// execute statements. In most situations you may want to call [`crate::Connection::execute`]
113    /// instead of this method, yet this method is useful if you have some time in your application
114    /// until the query is known, and once you have it want to execute it as fast as possible.
115    pub fn into_cursor(
116        self,
117        query: &str,
118        params: impl ParameterCollectionRef,
119    ) -> Result<Option<CursorImpl<S>>, Error> {
120        let query = SqlText::new(query);
121        execute_with_parameters(self.statement, Some(&query), params)
122    }
123
124    /// Transfer ownership to the underlying statement handle.
125    ///
126    /// The resulting type is one level of indirection away from the raw pointer of the ODBC API. It
127    /// no longer has any guarantees about bound buffers, but is still guaranteed to be a valid
128    /// allocated statement handle. This serves together with
129    /// [`crate::handles::StatementImpl::into_sys`] or [`crate::handles::Statement::as_sys`] this
130    /// serves as an escape hatch to access the functionality provided by `crate::sys` not yet
131    /// accessible through safe abstractions.
132    pub fn into_handle(self) -> S {
133        self.statement
134    }
135
136    /// List tables, schemas, views and catalogs of a datasource.
137    ///
138    /// # Parameters
139    ///
140    /// * `catalog_name`: Filter result by catalog name. Accept search patterns. Use `%` to match
141    ///   any number of characters. Use `_` to match exactly on character. Use `\` to escape
142    ///   characeters.
143    /// * `schema_name`: Filter result by schema. Accepts patterns in the same way as
144    ///   `catalog_name`.
145    /// * `table_name`: Filter result by table. Accepts patterns in the same way as `catalog_name`.
146    /// * `table_type`: Filters results by table type. E.g: 'TABLE', 'VIEW'. This argument accepts a
147    ///   comma separeted list of table types. Omit it to not filter the result by table type at
148    ///   all.
149    pub fn tables_cursor(
150        &mut self,
151        catalog_name: &str,
152        schema_name: &str,
153        table_name: &str,
154        table_type: &str,
155    ) -> Result<CursorImpl<StatementRef<'_>>, Error> {
156        let stmt = self.statement.as_stmt_ref();
157        execute_tables(stmt, catalog_name, schema_name, table_name, table_type)
158    }
159
160    /// Same as [`Self::tables_cursor`] but the cursor takes ownership of the statement handle.
161    pub fn into_tables_cursor(
162        self,
163        catalog_name: &str,
164        schema_name: &str,
165        table_name: &str,
166        table_type: &str,
167    ) -> Result<CursorImpl<S>, Error> {
168        execute_tables(
169            self.statement,
170            catalog_name,
171            schema_name,
172            table_name,
173            table_type,
174        )
175    }
176
177    /// A cursor describing columns of all tables matching the patterns. Patterns support as
178    /// placeholder `%` for multiple characters or `_` for a single character. Use `\` to escape.The
179    /// returned cursor has the columns:
180    /// `TABLE_CAT`, `TABLE_SCHEM`, `TABLE_NAME`, `COLUMN_NAME`, `DATA_TYPE`, `TYPE_NAME`,
181    /// `COLUMN_SIZE`, `BUFFER_LENGTH`, `DECIMAL_DIGITS`, `NUM_PREC_RADIX`, `NULLABLE`,
182    /// `REMARKS`, `COLUMN_DEF`, `SQL_DATA_TYPE`, `SQL_DATETIME_SUB`, `CHAR_OCTET_LENGTH`,
183    /// `ORDINAL_POSITION`, `IS_NULLABLE`.
184    ///
185    /// In addition to that there may be a number of columns specific to the data source.
186    pub fn columns_cursor(
187        &mut self,
188        catalog_name: &str,
189        schema_name: &str,
190        table_name: &str,
191        column_name: &str,
192    ) -> Result<CursorImpl<StatementRef<'_>>, Error> {
193        let stmt = self.statement.as_stmt_ref();
194        execute_columns(
195            stmt,
196            &SqlText::new(catalog_name),
197            &SqlText::new(schema_name),
198            &SqlText::new(table_name),
199            &SqlText::new(column_name),
200        )
201    }
202
203    /// Same as [`Self::columns_cursor`], but the cursor takes ownership of the statement handle.
204    pub fn into_columns_cursor(
205        self,
206        catalog_name: &str,
207        schema_name: &str,
208        table_name: &str,
209        column_name: &str,
210    ) -> Result<CursorImpl<S>, Error> {
211        execute_columns(
212            self.statement,
213            &SqlText::new(catalog_name),
214            &SqlText::new(schema_name),
215            &SqlText::new(table_name),
216            &SqlText::new(column_name),
217        )
218    }
219
220    /// Create a result set which contains the column names that make up the primary key for the
221    /// table. Same as [`Self::into_primary_keys_cursor`] but the cursor borrowes the statement
222    /// handle instead of taking ownership of it. This allows you to reuse the statement handle for
223    /// multiple calls to [`Self::primary_keys_cursor`] or other queries, without the need to ask
224    /// the driver for repeated allocations of new handles.
225    ///
226    /// # Parameters
227    ///
228    /// * `catalog_name`: Catalog name. If a driver supports catalogs for some tables but not for
229    ///   others, such as when the driver retrieves data from different DBMSs, an empty string ("")
230    ///   denotes those tables that do not have catalogs. `catalog_name` must not contain a string
231    ///   search pattern.
232    /// * `schema_name`: Schema name. If a driver supports schemas for some tables but not for
233    ///   others, such as when the driver retrieves data from different DBMSs, an empty string ("")
234    ///   denotes those tables that do not have schemas. `schema_name` must not contain a string
235    ///   search pattern.
236    /// * `table_name`: Table name. `table_name` must not contain a string search pattern.
237    ///
238    /// The resulting result set contains the following columns:
239    ///
240    /// * `TABLE_CAT`: Primary key table catalog name. NULL if not applicable to the data source. If
241    ///   a driver supports catalogs for some tables but not for others, such as when the driver
242    ///   retrieves data from different DBMSs, it returns an empty string ("") for those tables that
243    ///   do not have catalogs. `VARCHAR`
244    /// * `TABLE_SCHEM`: Primary key table schema name; NULL if not applicable to the data source.
245    ///   If a driver supports schemas for some tables but not for others, such as when the driver
246    ///   retrieves data from different DBMSs, it returns an empty string ("") for those tables that
247    ///   do not have schemas. `VARCHAR`
248    /// * `TABLE_NAME`: Primary key table name. `VARCHAR NOT NULL`
249    /// * `COLUMN_NAME`: Primary key column name. The driver returns an empty string for a column
250    ///   that does not have a name. `VARCHAR NOT NULL`
251    /// * `KEY_SEQ`: Column sequence number in key (starting with 1). `SMALLINT NOT NULL`
252    /// * `PK_NAME`: Primary key name. NULL if not applicable to the data source. `VARCHAR`
253    ///
254    /// The maximum length of the VARCHAR columns is driver specific.
255    ///
256    /// If [`crate::sys::StatementAttribute::MetadataId`] statement attribute is set to true,
257    /// catalog, schema and table name parameters are treated as an identifiers and their case is
258    /// not significant. If it is false, they are ordinary arguments. As such they treated literally
259    /// and their case is significant.
260    ///
261    /// See: <https://learn.microsoft.com/sql/odbc/reference/syntax/sqlprimarykeys-function>
262    pub fn primary_keys_cursor(
263        &mut self,
264        catalog_name: Option<&str>,
265        schema_name: Option<&str>,
266        table_name: &str,
267    ) -> Result<CursorImpl<StatementRef<'_>>, Error> {
268        execute_primary_keys(
269            self.statement.as_stmt_ref(),
270            catalog_name,
271            schema_name,
272            table_name,
273        )
274    }
275
276    /// An iterator whose items contains the column names that make up the primary key for the
277    /// table. Same as [`Self::into_primary_keys`] but the cursor borrowes the statement handle
278    /// instead of taking ownership of it. This allows you to reuse the statement handle for
279    /// multiple calls to [`Self::primary_keys`] or other queries, without the need to ask the
280    /// driver for repeated allocations of new handles.
281    ///
282    /// # Parameters
283    ///
284    /// * `catalog_name`: Catalog name. If a driver supports catalogs for some tables but not for
285    ///   others, such as when the driver retrieves data from different DBMSs, an empty string ("")
286    ///   denotes those tables that do not have catalogs. `catalog_name` must not contain a string
287    ///   search pattern.
288    /// * `schema_name`: Schema name. If a driver supports schemas for some tables but not for
289    ///   others, such as when the driver retrieves data from different DBMSs, an empty string ("")
290    ///   denotes those tables that do not have schemas. `schema_name` must not contain a string
291    ///   search pattern.
292    /// * `table_name`: Table name. `table_name` must not contain a string search pattern.
293    ///
294    /// If [`crate::sys::StatementAttribute::MetadataId`] statement attribute is set to true,
295    /// catalog, schema and table name parameters are treated as an identifiers and their case is
296    /// not significant. If it is false, they are ordinary arguments. As such they treated literally
297    /// and their case is significant.
298    ///
299    /// See: <https://learn.microsoft.com/sql/odbc/reference/syntax/sqlprimarykeys-function>
300    pub fn primary_keys(
301        &mut self,
302        catalog_name: Option<&str>,
303        schema_name: Option<&str>,
304        table_name: &str,
305    ) -> Result<BlockCursorIterator<CursorImpl<StatementRef<'_>>, PrimaryKeysRow>, Error> {
306        let cursor = self.primary_keys_cursor(catalog_name, schema_name, table_name)?;
307        // 5 seems like a senisble soft upper bound for the number of columns in a primary key. If
308        // it is more than that, we need an extra roundtrip
309        let buffer = RowVec::<PrimaryKeysRow>::new(5);
310        Ok(cursor.bind_buffer(buffer)?.into_iter())
311    }
312
313    /// Same as [`Self::primary_keys_cursor`] but the cursor takes ownership of the statement handle.
314    pub fn into_primary_keys_cursor(
315        self,
316        catalog_name: Option<&str>,
317        schema_name: Option<&str>,
318        table_name: &str,
319    ) -> Result<CursorImpl<S>, Error> {
320        execute_primary_keys(self.statement, catalog_name, schema_name, table_name)
321    }
322
323    /// Same as [`Self::primary_keys`] but the cursor takes ownership of the statement handle.
324    pub fn into_primary_keys(
325        self,
326        catalog_name: Option<&str>,
327        schema_name: Option<&str>,
328        table_name: &str,
329    ) -> Result<BlockCursorIterator<CursorImpl<S>, PrimaryKeysRow>, Error> {
330        let cursor = self.into_primary_keys_cursor(catalog_name, schema_name, table_name)?;
331        // 5 seems like a senisble soft upper bound for the number of columns in a primary key. If
332        // it is more than that, we need an extra roundtrip
333        let buffer = RowVec::<PrimaryKeysRow>::new(5);
334        Ok(cursor.bind_buffer(buffer)?.into_iter())
335    }
336
337    /// This can be used to retrieve either a list of foreign keys in the specified table or a list
338    /// of foreign keys in other table that refer to the primary key of the specified table.
339    ///
340    /// See: <https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlforeignkeys-function>
341    pub fn foreign_keys_cursor(
342        &mut self,
343        pk_catalog_name: &str,
344        pk_schema_name: &str,
345        pk_table_name: &str,
346        fk_catalog_name: &str,
347        fk_schema_name: &str,
348        fk_table_name: &str,
349    ) -> Result<CursorImpl<StatementRef<'_>>, Error> {
350        let stmt = self.statement.as_stmt_ref();
351        execute_foreign_keys(
352            stmt,
353            pk_catalog_name,
354            pk_schema_name,
355            pk_table_name,
356            fk_catalog_name,
357            fk_schema_name,
358            fk_table_name,
359        )
360    }
361
362    /// This can be used to retrieve either a list of foreign keys in the specified table or a list
363    /// of foreign keys in other table that refer to the primary key of the specified table.
364    ///
365    /// See: <https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlforeignkeys-function>
366    pub fn into_foreign_keys_cursor(
367        self,
368        pk_catalog_name: &str,
369        pk_schema_name: &str,
370        pk_table_name: &str,
371        fk_catalog_name: &str,
372        fk_schema_name: &str,
373        fk_table_name: &str,
374    ) -> Result<CursorImpl<S>, Error> {
375        execute_foreign_keys(
376            self.statement,
377            pk_catalog_name,
378            pk_schema_name,
379            pk_table_name,
380            fk_catalog_name,
381            fk_schema_name,
382            fk_table_name,
383        )
384    }
385
386    /// Number of rows affected by the last `INSERT`, `UPDATE` or `DELETE` statment. May return
387    /// `None` if row count is not available. Some drivers may also allow to use this to determine
388    /// how many rows have been fetched using `SELECT`. Most drivers however only know how many rows
389    /// have been fetched after they have been fetched.
390    ///
391    /// ```
392    /// use odbc_api::{Connection, Error};
393    ///
394    /// /// Make everyone rich and return how many colleagues are happy now.
395    /// fn raise_minimum_salary(
396    ///     conn: &Connection<'_>,
397    ///     new_min_salary: i32
398    /// ) -> Result<usize, Error> {
399    ///     // We won't use conn.execute directly, because we need a handle to ask about the number
400    ///     // of changed rows. So let's allocate the statement explicitly.
401    ///     let mut stmt = conn.preallocate()?;
402    ///     stmt.execute(
403    ///         "UPDATE Employees SET salary = ? WHERE salary < ?",
404    ///         (&new_min_salary, &new_min_salary),
405    ///     )?;
406    ///     let number_of_updated_rows = stmt
407    ///         .row_count()?
408    ///         .expect("For UPDATE statements row count must always be available.");
409    ///     Ok(number_of_updated_rows)
410    /// }
411    /// ```
412    pub fn row_count(&mut self) -> Result<Option<usize>, Error> {
413        let mut stmt = self.statement.as_stmt_ref();
414        stmt.row_count().into_result(&stmt).map(|count| {
415            // ODBC returns -1 in case a row count is not available
416            if count == -1 {
417                None
418            } else {
419                Some(count.try_into().unwrap())
420            }
421        })
422    }
423
424    /// Use this to limit the time the query is allowed to take, before responding with data to the
425    /// application. The driver may replace the number of seconds you provide with a minimum or
426    /// maximum value. You can specify ``0``, to deactivate the timeout, this is the default. For
427    /// this to work the driver must support this feature. E.g. PostgreSQL, and Microsoft SQL Server
428    /// do, but SQLite or MariaDB do not.
429    ///
430    /// This corresponds to `SQL_ATTR_QUERY_TIMEOUT` in the ODBC C API.
431    ///
432    /// See:
433    /// <https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetstmtattr-function>
434    pub fn set_query_timeout_sec(&mut self, timeout_sec: usize) -> Result<(), Error> {
435        let mut stmt = self.statement.as_stmt_ref();
436        stmt.as_stmt_ref()
437            .set_query_timeout_sec(timeout_sec)
438            .into_result(&stmt)
439    }
440
441    /// The number of seconds to wait for a SQL statement to execute before returning to the
442    /// application. If `timeout_sec` is equal to 0 (default), there is no timeout.
443    ///
444    /// This corresponds to `SQL_ATTR_QUERY_TIMEOUT` in the ODBC C API.
445    ///
446    /// See:
447    /// <https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetstmtattr-function>
448    pub fn query_timeout_sec(&mut self) -> Result<usize, Error> {
449        let mut stmt = self.statement.as_stmt_ref();
450        stmt.query_timeout_sec().into_result(&stmt)
451    }
452
453    /// Call this method to enable asynchronous polling mode on the statement.
454    ///
455    /// ⚠️**Attention**⚠️: Please read
456    /// [Asynchronous execution using polling
457    /// mode](crate::guide#asynchronous-execution-using-polling-mode)
458    pub fn into_polling(mut self) -> Result<PreallocatedPolling<S>, Error> {
459        let mut stmt = self.statement.as_stmt_ref();
460        stmt.set_async_enable(true).into_result(&stmt)?;
461        Ok(PreallocatedPolling::new(self.statement))
462    }
463}
464
465impl<S> AsStatementRef for Preallocated<S>
466where
467    S: AsStatementRef,
468{
469    fn as_stmt_ref(&mut self) -> StatementRef<'_> {
470        self.statement.as_stmt_ref()
471    }
472}
473
474/// A row returned by the iterator returned by [`Preallocated::primary_keys`]. This members are
475/// associated with the columns of the result set returned by [`Preallocated::primary_keys_cursor`].
476///
477/// See: <https://learn.microsoft.com/sql/odbc/reference/syntax/sqlprimarykeys-function>
478#[derive(Clone, Copy, Default)]
479pub struct PrimaryKeysRow {
480    // The maximum length of the VARCHAR columns is driver specific. Since this type needs to be
481    // `Copy` in order to be suitable for row wise bulk fetchings sensible upper bounds have been
482    // chosen. They have been determined by using the maximum lengths reported by PostgreSQL and
483    // Microsoft SQL Server, MariaDB and SQLite.
484    //
485    /// Binds to the `TABLE_CAT` column. Primary key table catalog name. NULL if not applicable to
486    /// the data source. If a driver supports catalogs for some tables but not for others, such as
487    /// when the driver retrieves data from different DBMSs, it returns an empty string ("") for
488    /// those tables that do not have catalogs.
489    pub catalog: VarCharArray<128>,
490    /// Binds to `TABLE_SCHEM`. Primary key table schema name; NULL if not applicable to the data
491    /// source. If a driver supports schemas for some tables but not for others, such as when the
492    /// driver retrieves data from different DBMSs, it returns an empty string ("") for those tables
493    /// that do not have schemas.
494    pub schema: VarCharArray<128>,
495    /// Binds to the `TABLE_NAME` column. Primary key table name. Drivers must not return NULL.
496    pub table: VarCharArray<255>,
497    /// Binds to the `COLUMN_NAME` column. Primary key column name. The driver returns an empty
498    /// string for a column that does not have a name. Drivers must not return NULL.
499    pub column: VarCharArray<255>,
500    /// Binds to the `KEY_SEQ` column. Column sequence number in key (starting with 1).
501    pub key_seq: i16,
502    /// Binds to the `PK_NAME` column. Primary key name. NULL if not applicable to the data source.
503    pub pk_name: VarCharArray<128>,
504}
505
506unsafe impl FetchRow for PrimaryKeysRow {
507    unsafe fn bind_columns_to_cursor(&mut self, mut cursor: StatementRef<'_>) -> Result<(), Error> {
508        unsafe {
509            self.catalog.bind_to_col(1, &mut cursor)?;
510            self.schema.bind_to_col(2, &mut cursor)?;
511            self.table.bind_to_col(3, &mut cursor)?;
512            self.column.bind_to_col(4, &mut cursor)?;
513            self.key_seq.bind_to_col(5, &mut cursor)?;
514            self.pk_name.bind_to_col(6, &mut cursor)?;
515            Ok(())
516        }
517    }
518
519    fn find_truncation(&self) -> Option<TruncationInfo> {
520        if let Some(t) = self.catalog.find_truncation(0) {
521            return Some(t);
522        }
523        if let Some(t) = self.schema.find_truncation(1) {
524            return Some(t);
525        }
526        if let Some(t) = self.table.find_truncation(2) {
527            return Some(t);
528        }
529        if let Some(t) = self.column.find_truncation(3) {
530            return Some(t);
531        }
532        if let Some(t) = self.key_seq.find_truncation(4) {
533            return Some(t);
534        }
535        if let Some(t) = self.pk_name.find_truncation(5) {
536            return Some(t);
537        }
538        None
539    }
540}
541
542/// Shared implementation for executing a columns query between [`crate::Preallocated::columns`] and
543/// [`crate::Preallocated::into_columns`].
544fn execute_columns<S>(
545    mut statement: S,
546    catalog_name: &SqlText,
547    schema_name: &SqlText,
548    table_name: &SqlText,
549    column_name: &SqlText,
550) -> Result<CursorImpl<S>, Error>
551where
552    S: AsStatementRef,
553{
554    let mut stmt = statement.as_stmt_ref();
555
556    stmt.columns(catalog_name, schema_name, table_name, column_name)
557        .into_result(&stmt)?;
558
559    // We assume columns always creates a result set, since it works like a SELECT statement.
560    debug_assert_ne!(stmt.num_result_cols().unwrap(), 0);
561
562    // Safe: `statement` is in cursor state
563    let cursor = unsafe { CursorImpl::new(statement) };
564    Ok(cursor)
565}
566
567fn execute_primary_keys<S>(
568    mut statement: S,
569    catalog_name: Option<&str>,
570    schema_name: Option<&str>,
571    table_name: &str,
572) -> Result<CursorImpl<S>, Error>
573where
574    S: AsStatementRef,
575{
576    let mut stmt = statement.as_stmt_ref();
577    stmt.primary_keys(
578        catalog_name.map(SqlText::new).as_ref(),
579        schema_name.map(SqlText::new).as_ref(),
580        &SqlText::new(table_name),
581    )
582    .into_result(&stmt)?;
583    // SAFETY: primary_keys puts stmt into cursor state.
584    let cursor = unsafe { CursorImpl::new(statement) };
585    Ok(cursor)
586}
587
588/// Shared implementation for executing a foreign keys query between [`Preallocated::foreign_keys`]
589/// and [`Preallocated::into_foreign_keys`].
590fn execute_foreign_keys<S>(
591    mut statement: S,
592    pk_catalog_name: &str,
593    pk_schema_name: &str,
594    pk_table_name: &str,
595    fk_catalog_name: &str,
596    fk_schema_name: &str,
597    fk_table_name: &str,
598) -> Result<CursorImpl<S>, Error>
599where
600    S: AsStatementRef,
601{
602    let mut stmt = statement.as_stmt_ref();
603
604    stmt.foreign_keys(
605        &SqlText::new(pk_catalog_name),
606        &SqlText::new(pk_schema_name),
607        &SqlText::new(pk_table_name),
608        &SqlText::new(fk_catalog_name),
609        &SqlText::new(fk_schema_name),
610        &SqlText::new(fk_table_name),
611    )
612    .into_result(&stmt)?;
613
614    // We assume foreign keys always creates a result set, since it works like a SELECT statement.
615    debug_assert_ne!(stmt.num_result_cols().unwrap(), 0);
616
617    // Safe: `statement` is in Cursor state.
618    let cursor = unsafe { CursorImpl::new(statement) };
619
620    Ok(cursor)
621}
622
623fn execute_tables<S>(
624    mut statement: S,
625    catalog_name: &str,
626    schema_name: &str,
627    table_name: &str,
628    column_name: &str,
629) -> Result<CursorImpl<S>, Error>
630where
631    S: AsStatementRef,
632{
633    let mut stmt = statement.as_stmt_ref();
634
635    stmt.tables(
636        &SqlText::new(catalog_name),
637        &SqlText::new(schema_name),
638        &SqlText::new(table_name),
639        &SqlText::new(column_name),
640    )
641    .into_result(&stmt)?;
642
643    // We assume tables always creates a result set, since it works like a SELECT statement.
644    debug_assert_ne!(stmt.num_result_cols().unwrap(), 0);
645
646    // Safe: `statement` is in Cursor state.
647    let cursor = unsafe { CursorImpl::new(statement) };
648
649    Ok(cursor)
650}