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    #[allow(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    #[allow(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    use crate::{Connection, Result};
399
400    #[test]
401    #[cfg(feature = "column_decltype")]
402    fn test_columns() -> Result<()> {
403        use super::Column;
404
405        let db = Connection::open_in_memory()?;
406        let query = db.prepare("SELECT * FROM sqlite_master")?;
407        let columns = query.columns();
408        let column_names: Vec<&str> = columns.iter().map(Column::name).collect();
409        assert_eq!(
410            column_names.as_slice(),
411            &["type", "name", "tbl_name", "rootpage", "sql"]
412        );
413        let column_types: Vec<Option<String>> = columns
414            .iter()
415            .map(|col| col.decl_type().map(str::to_lowercase))
416            .collect();
417        assert_eq!(
418            &column_types[..3],
419            &[
420                Some("text".to_owned()),
421                Some("text".to_owned()),
422                Some("text".to_owned()),
423            ]
424        );
425        Ok(())
426    }
427
428    #[test]
429    #[cfg(feature = "column_metadata")]
430    fn test_columns_with_metadata() -> Result<()> {
431        let db = Connection::open_in_memory()?;
432        let query = db.prepare("SELECT *, 1 FROM sqlite_master")?;
433
434        let col_mets = query.columns_with_metadata();
435
436        assert_eq!(col_mets.len(), 6);
437
438        for col in col_mets.iter().take(5) {
439            assert_eq!(&col.database_name(), &Some("main"));
440            assert_eq!(&col.table_name(), &Some("sqlite_master"));
441        }
442
443        assert!(col_mets[5].database_name().is_none());
444        assert!(col_mets[5].table_name().is_none());
445        assert!(col_mets[5].origin_name().is_none());
446
447        let col_origins: Vec<Option<&str>> = col_mets.iter().map(|col| col.origin_name()).collect();
448
449        assert_eq!(
450            &col_origins[..5],
451            &[
452                Some("type"),
453                Some("name"),
454                Some("tbl_name"),
455                Some("rootpage"),
456                Some("sql"),
457            ]
458        );
459
460        Ok(())
461    }
462
463    #[test]
464    fn test_column_name_in_error() -> Result<()> {
465        use crate::{types::Type, Error};
466        let db = Connection::open_in_memory()?;
467        db.execute_batch(
468            "BEGIN;
469             CREATE TABLE foo(x INTEGER, y TEXT);
470             INSERT INTO foo VALUES(4, NULL);
471             END;",
472        )?;
473        let mut stmt = db.prepare("SELECT x as renamed, y FROM foo")?;
474        let mut rows = stmt.query([])?;
475        let row = rows.next()?.unwrap();
476        match row.get::<_, String>(0).unwrap_err() {
477            Error::InvalidColumnType(idx, name, ty) => {
478                assert_eq!(idx, 0);
479                assert_eq!(name, "renamed");
480                assert_eq!(ty, Type::Integer);
481            }
482            e => {
483                panic!("Unexpected error type: {e:?}");
484            }
485        }
486        match row.get::<_, String>("y").unwrap_err() {
487            Error::InvalidColumnType(idx, name, ty) => {
488                assert_eq!(idx, 1);
489                assert_eq!(name, "y");
490                assert_eq!(ty, Type::Null);
491            }
492            e => {
493                panic!("Unexpected error type: {e:?}");
494            }
495        }
496        Ok(())
497    }
498
499    /// `column_name` reference should stay valid until `stmt` is reprepared (or
500    /// reset) even if DB schema is altered (SQLite documentation is
501    /// ambiguous here because it says reference "is valid until (...) the next
502    /// call to `sqlite3_column_name()` or `sqlite3_column_name16()` on the same
503    /// column.". We assume that reference is valid if only
504    /// `sqlite3_column_name()` is used):
505    #[test]
506    #[cfg(feature = "modern_sqlite")]
507    fn test_column_name_reference() -> Result<()> {
508        let db = Connection::open_in_memory()?;
509        db.execute_batch("CREATE TABLE y (x);")?;
510        let stmt = db.prepare("SELECT x FROM y;")?;
511        let column_name = stmt.column_name(0)?;
512        assert_eq!("x", column_name);
513        db.execute_batch("ALTER TABLE y RENAME COLUMN x TO z;")?;
514        // column name is not refreshed until statement is re-prepared
515        let same_column_name = stmt.column_name(0)?;
516        assert_eq!(same_column_name, column_name);
517        Ok(())
518    }
519
520    #[test]
521    #[cfg(feature = "column_metadata")]
522    fn stmt_column_metadata() -> Result<()> {
523        let db = Connection::open_in_memory()?;
524        let query = db.prepare("SELECT *, 1 FROM sqlite_schema")?;
525        let (db_name, table_name, col_name, data_type, coll_seq, not_null, primary_key, auto_inc) =
526            query.column_metadata(0)?.unwrap();
527        assert_eq!(db_name, crate::MAIN_DB);
528        assert_eq!(table_name, c"sqlite_master");
529        assert_eq!(col_name, c"type");
530        assert_eq!(data_type, Some(c"TEXT"));
531        assert_eq!(coll_seq, Some(c"BINARY"));
532        assert!(!not_null);
533        assert!(!primary_key);
534        assert!(!auto_inc);
535        assert!(query.column_metadata(5)?.is_none());
536        Ok(())
537    }
538
539    #[test]
540    fn column_exists() -> Result<()> {
541        let db = Connection::open_in_memory()?;
542        assert!(db.column_exists(None, c"sqlite_schema", c"type")?);
543        assert!(db.column_exists(Some(crate::TEMP_DB), c"sqlite_schema", c"type")?);
544        assert!(!db.column_exists(Some(crate::MAIN_DB), c"sqlite_temp_schema", c"type")?);
545        Ok(())
546    }
547
548    #[test]
549    fn table_exists() -> Result<()> {
550        let db = Connection::open_in_memory()?;
551        assert!(db.table_exists(None, c"sqlite_schema")?);
552        assert!(db.table_exists(Some(crate::TEMP_DB), c"sqlite_schema")?);
553        assert!(!db.table_exists(Some(crate::MAIN_DB), c"sqlite_temp_schema")?);
554        Ok(())
555    }
556
557    #[test]
558    fn column_metadata() -> Result<()> {
559        let db = Connection::open_in_memory()?;
560        let (data_type, coll_seq, not_null, primary_key, auto_inc) =
561            db.column_metadata(None, c"sqlite_schema", c"type")?;
562        assert_eq!(data_type, Some(c"TEXT"));
563        assert_eq!(coll_seq, Some(c"BINARY"));
564        assert!(!not_null);
565        assert!(!primary_key);
566        assert!(!auto_inc);
567        assert!(db.column_metadata(None, c"sqlite_schema", c"foo").is_err());
568        Ok(())
569    }
570}