Skip to main content

rusqlite/
column.rs

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