Skip to main content

rusqlite/
column.rs

1use std::ffi::{c_char, CStr};
2use std::ptr;
3use std::str;
4
5use crate::ffi;
6use crate::{Connection, Error, Name, Result, Statement};
7
8/// Information about a column of a SQLite query.
9#[cfg(feature = "column_decltype")]
10#[derive(Debug)]
11pub struct Column<'stmt> {
12    name: &'stmt str,
13    decl_type: Option<&'stmt str>,
14}
15
16#[cfg(feature = "column_decltype")]
17impl Column<'_> {
18    /// Returns the name of the column.
19    #[inline]
20    #[must_use]
21    pub fn name(&self) -> &str {
22        self.name
23    }
24
25    /// Returns the type of the column (`None` for expression).
26    #[inline]
27    #[must_use]
28    pub fn decl_type(&self) -> Option<&str> {
29        self.decl_type
30    }
31}
32
33/// Metadata about the origin of a column of a SQLite query
34#[cfg(feature = "column_metadata")]
35#[derive(Debug)]
36pub struct ColumnMetadata<'stmt> {
37    name: &'stmt str,
38    database_name: Option<&'stmt str>,
39    table_name: Option<&'stmt str>,
40    origin_name: Option<&'stmt str>,
41}
42
43#[cfg(feature = "column_metadata")]
44impl ColumnMetadata<'_> {
45    #[inline]
46    #[must_use]
47    /// Returns the name of the column in the query results
48    pub fn name(&self) -> &str {
49        self.name
50    }
51
52    #[inline]
53    #[must_use]
54    /// Returns the database name from which the column originates
55    pub fn database_name(&self) -> Option<&str> {
56        self.database_name
57    }
58
59    #[inline]
60    #[must_use]
61    /// Returns the table name from which the column originates
62    pub fn table_name(&self) -> Option<&str> {
63        self.table_name
64    }
65
66    #[inline]
67    #[must_use]
68    /// Returns the column name from which the column originates
69    pub fn origin_name(&self) -> Option<&str> {
70        self.origin_name
71    }
72}
73
74impl Statement<'_> {
75    /// Get all the column names in the result set of the prepared statement.
76    ///
77    /// If associated DB schema can be altered concurrently, you should make
78    /// sure that current statement has already been stepped once before
79    /// calling this method.
80    pub fn column_names(&self) -> Vec<&str> {
81        let n = self.column_count();
82        let mut cols = Vec::with_capacity(n);
83        for i in 0..n {
84            let s = self.column_name_unwrap(i);
85            cols.push(s);
86        }
87        cols
88    }
89
90    /// Return the number of columns in the result set returned by the prepared
91    /// statement.
92    ///
93    /// If associated DB schema can be altered concurrently, you should make
94    /// sure that current statement has already been stepped once before
95    /// calling this method.
96    #[inline]
97    pub fn column_count(&self) -> usize {
98        self.stmt.column_count()
99    }
100
101    /// Check that column name reference lifetime is limited:
102    /// <https://www.sqlite.org/c3ref/column_name.html>
103    /// > The returned string pointer is valid...
104    ///
105    /// `column_name` reference can become invalid if `stmt` is reprepared
106    /// (because of schema change) when `query_row` is called. So we assert
107    /// that a compilation error happens if this reference is kept alive:
108    /// ```compile_fail
109    /// use rusqlite::{Connection, Result};
110    /// fn main() -> Result<()> {
111    ///     let db = Connection::open_in_memory()?;
112    ///     let mut stmt = db.prepare("SELECT 1 as x")?;
113    ///     let column_name = stmt.column_name(0)?;
114    ///     let x = stmt.query_row([], |r| r.get::<_, i64>(0))?; // E0502
115    ///     assert_eq!(1, x);
116    ///     assert_eq!("x", column_name);
117    ///     Ok(())
118    /// }
119    /// ```
120    #[inline]
121    pub(super) fn column_name_unwrap(&self, col: usize) -> &str {
122        // Just panic if the bounds are wrong for now, we never call this
123        // without checking first.
124        self.column_name(col).expect("Column out of bounds")
125    }
126
127    /// Returns the name assigned to a particular column in the result set
128    /// returned by the prepared statement.
129    ///
130    /// If associated DB schema can be altered concurrently, you should make
131    /// sure that current statement has already been stepped once before
132    /// calling this method.
133    ///
134    /// ## Failure
135    ///
136    /// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid
137    /// column range for this row.
138    ///
139    /// # Panics
140    ///
141    /// Panics when column name is not valid UTF-8.
142    #[inline]
143    pub fn column_name(&self, col: usize) -> Result<&str> {
144        self.stmt
145            .column_name(col)
146            // clippy::or_fun_call (nightly) vs clippy::unnecessary-lazy-evaluations (stable)
147            .ok_or(Error::InvalidColumnIndex(col))
148            .map(|slice| {
149                slice
150                    .to_str()
151                    .expect("Invalid UTF-8 sequence in column name")
152            })
153    }
154
155    /// Returns the column index in the result set for a given column name.
156    ///
157    /// If there is no AS clause then the name of the column is unspecified and
158    /// may change from one release of SQLite to the next.
159    ///
160    /// If associated DB schema can be altered concurrently, you should make
161    /// sure that current statement has already been stepped once before
162    /// calling this method.
163    ///
164    /// # Failure
165    ///
166    /// Will return an `Error::InvalidColumnName` when there is no column with
167    /// the specified `name`.
168    #[inline]
169    pub fn column_index(&self, name: &str) -> Result<usize> {
170        let bytes = name.as_bytes();
171        let n = self.column_count();
172        for i in 0..n {
173            // Note: `column_name` is only fallible if `i` is out of bounds,
174            // which we've already checked.
175            if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).unwrap().to_bytes()) {
176                return Ok(i);
177            }
178        }
179        Err(Error::InvalidColumnName(String::from(name)))
180    }
181
182    /// Returns a slice describing the columns of the result of the query.
183    ///
184    /// If associated DB schema can be altered concurrently, you should make
185    /// sure that current statement has already been stepped once before
186    /// calling this method.
187    #[cfg(feature = "column_decltype")]
188    pub fn columns(&self) -> Vec<Column<'_>> {
189        let n = self.column_count();
190        let mut cols = Vec::with_capacity(n);
191        for i in 0..n {
192            let name = self.column_name_unwrap(i);
193            let slice = self.stmt.column_decltype(i);
194            let decl_type = slice.map(|s| {
195                s.to_str()
196                    .expect("Invalid UTF-8 sequence in column declaration")
197            });
198            cols.push(Column { name, decl_type });
199        }
200        cols
201    }
202
203    /// Returns the names of the database, table, and row from which
204    /// each column of this query's results originate.
205    ///
206    /// Computed or otherwise derived columns will have None values for these fields.
207    #[cfg(feature = "column_metadata")]
208    pub fn columns_with_metadata(&self) -> Vec<ColumnMetadata<'_>> {
209        let n = self.column_count();
210        let mut col_mets = Vec::with_capacity(n);
211        for i in 0..n {
212            let name = self.column_name_unwrap(i);
213            let db_slice = self.stmt.column_database_name(i);
214            let tbl_slice = self.stmt.column_table_name(i);
215            let origin_slice = self.stmt.column_origin_name(i);
216            col_mets.push(ColumnMetadata {
217                name,
218                database_name: db_slice.map(|s| {
219                    s.to_str()
220                        .expect("Invalid UTF-8 sequence in column db name")
221                }),
222                table_name: tbl_slice.map(|s| {
223                    s.to_str()
224                        .expect("Invalid UTF-8 sequence in column table name")
225                }),
226                origin_name: origin_slice.map(|s| {
227                    s.to_str()
228                        .expect("Invalid UTF-8 sequence in column origin name")
229                }),
230            })
231        }
232        col_mets
233    }
234
235    /// Extract metadata of column at specified index
236    ///
237    /// Returns:
238    /// - database name
239    /// - table name
240    /// - original column name
241    /// - declared data type
242    /// - name of default collation sequence
243    /// - True if column has a NOT NULL constraint
244    /// - True if column is part of the PRIMARY KEY
245    /// - True if column is AUTOINCREMENT
246    ///
247    /// See [Connection::column_metadata]
248    #[cfg(feature = "column_metadata")]
249    #[expect(clippy::type_complexity)]
250    pub fn column_metadata(
251        &self,
252        col: usize,
253    ) -> Result<
254        Option<(
255            &CStr,
256            &CStr,
257            &CStr,
258            Option<&CStr>,
259            Option<&CStr>,
260            bool,
261            bool,
262            bool,
263        )>,
264    > {
265        let db_name = self.stmt.column_database_name(col);
266        let table_name = self.stmt.column_table_name(col);
267        let origin_name = self.stmt.column_origin_name(col);
268        if db_name.is_none() || table_name.is_none() || origin_name.is_none() {
269            return Ok(None);
270        }
271        let (data_type, coll_seq, not_null, primary_key, auto_inc) =
272            self.conn
273                .column_metadata(db_name, table_name.unwrap(), origin_name.unwrap())?;
274        Ok(Some((
275            db_name.unwrap(),
276            table_name.unwrap(),
277            origin_name.unwrap(),
278            data_type,
279            coll_seq,
280            not_null,
281            primary_key,
282            auto_inc,
283        )))
284    }
285}
286
287impl Connection {
288    /// Check if `table_name`.`column_name` exists.
289    ///
290    /// `db_name` is main, temp, the name in ATTACH, or `None` to search all databases.
291    pub fn column_exists<N: Name>(
292        &self,
293        db_name: Option<N>,
294        table_name: N,
295        column_name: N,
296    ) -> Result<bool> {
297        self.exists(db_name, table_name, Some(column_name))
298    }
299
300    /// Check if `table_name` exists.
301    ///
302    /// `db_name` is main, temp, the name in ATTACH, or `None` to search all databases.
303    pub fn table_exists<N: Name>(&self, db_name: Option<N>, table_name: N) -> Result<bool> {
304        self.exists(db_name, table_name, None)
305    }
306
307    /// Extract metadata of column at specified index
308    ///
309    /// Returns:
310    /// - declared data type
311    /// - name of default collation sequence
312    /// - True if column has a NOT NULL constraint
313    /// - True if column is part of the PRIMARY KEY
314    /// - True if column is AUTOINCREMENT
315    #[expect(clippy::type_complexity)]
316    pub fn column_metadata<N: Name>(
317        &self,
318        db_name: Option<N>,
319        table_name: N,
320        column_name: N,
321    ) -> Result<(Option<&CStr>, Option<&CStr>, bool, bool, bool)> {
322        let cs = db_name.as_ref().map(N::as_cstr).transpose()?;
323        let db_name = cs.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null());
324        let table_name = table_name.as_cstr()?;
325        let column_name = column_name.as_cstr()?;
326
327        let mut data_type: *const c_char = ptr::null_mut();
328        let mut coll_seq: *const c_char = ptr::null_mut();
329        let mut not_null = 0;
330        let mut primary_key = 0;
331        let mut auto_inc = 0;
332
333        self.decode_result(unsafe {
334            ffi::sqlite3_table_column_metadata(
335                self.handle(),
336                db_name,
337                table_name.as_ptr(),
338                column_name.as_ptr(),
339                &mut data_type,
340                &mut coll_seq,
341                &mut not_null,
342                &mut primary_key,
343                &mut auto_inc,
344            )
345        })?;
346
347        Ok((
348            if data_type.is_null() {
349                None
350            } else {
351                Some(unsafe { CStr::from_ptr(data_type) })
352            },
353            if coll_seq.is_null() {
354                None
355            } else {
356                Some(unsafe { CStr::from_ptr(coll_seq) })
357            },
358            not_null != 0,
359            primary_key != 0,
360            auto_inc != 0,
361        ))
362    }
363
364    fn exists<N: Name>(
365        &self,
366        db_name: Option<N>,
367        table_name: N,
368        column_name: Option<N>,
369    ) -> Result<bool> {
370        let cs = db_name.as_ref().map(N::as_cstr).transpose()?;
371        let db_name = cs.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null());
372        let table_name = table_name.as_cstr()?;
373        let cn = column_name.as_ref().map(N::as_cstr).transpose()?;
374        let column_name = cn.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null());
375        let r = unsafe {
376            ffi::sqlite3_table_column_metadata(
377                self.handle(),
378                db_name,
379                table_name.as_ptr(),
380                column_name,
381                ptr::null_mut(),
382                ptr::null_mut(),
383                ptr::null_mut(),
384                ptr::null_mut(),
385                ptr::null_mut(),
386            )
387        };
388        match r {
389            ffi::SQLITE_OK => Ok(true),
390            ffi::SQLITE_ERROR => Ok(false),
391            _ => self.db.borrow().decode_result(r).map(|_| false),
392        }
393    }
394}
395
396#[cfg(test)]
397mod test {
398    #[cfg(all(target_family = "wasm", target_os = "unknown"))]
399    use wasm_bindgen_test::wasm_bindgen_test as test;
400
401    use crate::{Connection, Result};
402
403    #[test]
404    #[cfg(feature = "column_decltype")]
405    fn test_columns() -> Result<()> {
406        use super::Column;
407
408        let db = Connection::open_in_memory()?;
409        let query = db.prepare("SELECT * FROM sqlite_master")?;
410        let columns = query.columns();
411        let column_names: Vec<&str> = columns.iter().map(Column::name).collect();
412        assert_eq!(
413            column_names.as_slice(),
414            &["type", "name", "tbl_name", "rootpage", "sql"]
415        );
416        let column_types: Vec<Option<String>> = columns
417            .iter()
418            .map(|col| col.decl_type().map(str::to_lowercase))
419            .collect();
420        assert_eq!(
421            &column_types[..3],
422            &[
423                Some("text".to_owned()),
424                Some("text".to_owned()),
425                Some("text".to_owned()),
426            ]
427        );
428        Ok(())
429    }
430
431    #[test]
432    #[cfg(feature = "column_metadata")]
433    fn test_columns_with_metadata() -> Result<()> {
434        let db = Connection::open_in_memory()?;
435        let query = db.prepare("SELECT *, 1 FROM sqlite_master")?;
436
437        let col_mets = query.columns_with_metadata();
438
439        assert_eq!(col_mets.len(), 6);
440
441        for col in col_mets.iter().take(5) {
442            assert_eq!(&col.database_name(), &Some("main"));
443            assert_eq!(&col.table_name(), &Some("sqlite_master"));
444        }
445
446        assert!(col_mets[5].database_name().is_none());
447        assert!(col_mets[5].table_name().is_none());
448        assert!(col_mets[5].origin_name().is_none());
449
450        let col_origins: Vec<Option<&str>> = col_mets.iter().map(|col| col.origin_name()).collect();
451
452        assert_eq!(
453            &col_origins[..5],
454            &[
455                Some("type"),
456                Some("name"),
457                Some("tbl_name"),
458                Some("rootpage"),
459                Some("sql"),
460            ]
461        );
462
463        Ok(())
464    }
465
466    #[test]
467    fn test_column_name_in_error() -> Result<()> {
468        use crate::{types::Type, Error};
469        let db = Connection::open_in_memory()?;
470        db.execute_batch(
471            "BEGIN;
472             CREATE TABLE foo(x INTEGER, y TEXT);
473             INSERT INTO foo VALUES(4, NULL);
474             END;",
475        )?;
476        let mut stmt = db.prepare("SELECT x as renamed, y FROM foo")?;
477        let mut rows = stmt.query([])?;
478        let row = rows.next()?.unwrap();
479        match row.get::<_, String>(0).unwrap_err() {
480            Error::InvalidColumnType(idx, name, ty) => {
481                assert_eq!(idx, 0);
482                assert_eq!(name, "renamed");
483                assert_eq!(ty, Type::Integer);
484            }
485            e => {
486                panic!("Unexpected error type: {e:?}");
487            }
488        }
489        match row.get::<_, String>("y").unwrap_err() {
490            Error::InvalidColumnType(idx, name, ty) => {
491                assert_eq!(idx, 1);
492                assert_eq!(name, "y");
493                assert_eq!(ty, Type::Null);
494            }
495            e => {
496                panic!("Unexpected error type: {e:?}");
497            }
498        }
499        Ok(())
500    }
501
502    /// `column_name` reference should stay valid until `stmt` is reprepared (or
503    /// reset) even if DB schema is altered (SQLite documentation is
504    /// ambiguous here because it says reference "is valid until (...) the next
505    /// call to `sqlite3_column_name()` or `sqlite3_column_name16()` on the same
506    /// column.". We assume that reference is valid if only
507    /// `sqlite3_column_name()` is used):
508    #[test]
509    #[cfg(feature = "modern_sqlite")]
510    fn test_column_name_reference() -> Result<()> {
511        let db = Connection::open_in_memory()?;
512        db.execute_batch("CREATE TABLE y (x);")?;
513        let stmt = db.prepare("SELECT x FROM y;")?;
514        let column_name = stmt.column_name(0)?;
515        assert_eq!("x", column_name);
516        db.execute_batch("ALTER TABLE y RENAME COLUMN x TO z;")?;
517        // column name is not refreshed until statement is re-prepared
518        let same_column_name = stmt.column_name(0)?;
519        assert_eq!(same_column_name, column_name);
520        Ok(())
521    }
522
523    #[test]
524    #[cfg(feature = "column_metadata")]
525    fn stmt_column_metadata() -> Result<()> {
526        let db = Connection::open_in_memory()?;
527        let query = db.prepare("SELECT *, 1 FROM sqlite_master")?;
528        let (db_name, table_name, col_name, data_type, coll_seq, not_null, primary_key, auto_inc) =
529            query.column_metadata(0)?.unwrap();
530        assert_eq!(db_name, crate::MAIN_DB);
531        assert_eq!(table_name, c"sqlite_master");
532        assert_eq!(col_name, c"type");
533        assert_eq!(data_type, Some(c"TEXT"));
534        assert_eq!(coll_seq, Some(c"BINARY"));
535        assert!(!not_null);
536        assert!(!primary_key);
537        assert!(!auto_inc);
538        assert!(query.column_metadata(5)?.is_none());
539        Ok(())
540    }
541
542    #[test]
543    fn column_exists() -> Result<()> {
544        let db = Connection::open_in_memory()?;
545        assert!(db.column_exists(None, c"sqlite_master", c"type")?);
546        assert!(db.column_exists(Some(crate::TEMP_DB), c"sqlite_master", c"type")?);
547        assert!(!db.column_exists(Some(crate::MAIN_DB), c"sqlite_temp_master", c"type")?);
548        Ok(())
549    }
550
551    #[test]
552    fn table_exists() -> Result<()> {
553        let db = Connection::open_in_memory()?;
554        assert!(db.table_exists(None, c"sqlite_master")?);
555        assert!(db.table_exists(Some(crate::TEMP_DB), c"sqlite_master")?);
556        assert!(!db.table_exists(Some(crate::MAIN_DB), c"sqlite_temp_master")?);
557        Ok(())
558    }
559
560    #[test]
561    fn column_metadata() -> Result<()> {
562        let db = Connection::open_in_memory()?;
563        let (data_type, coll_seq, not_null, primary_key, auto_inc) =
564            db.column_metadata(None, c"sqlite_master", c"type")?;
565        assert_eq!(
566            data_type.map(|cs| cs.to_str().unwrap().to_ascii_uppercase()),
567            Some("TEXT".to_owned())
568        );
569        assert_eq!(coll_seq, Some(c"BINARY"));
570        assert!(!not_null);
571        assert!(!primary_key);
572        assert!(!auto_inc);
573        assert!(db.column_metadata(None, c"sqlite_master", c"foo").is_err());
574        Ok(())
575    }
576}