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