rusqlite/column.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
use std::str;
use crate::{Error, Result, Statement};
/// Information about a column of a SQLite query.
#[cfg(feature = "column_decltype")]
#[cfg_attr(docsrs, doc(cfg(feature = "column_decltype")))]
#[derive(Debug)]
pub struct Column<'stmt> {
name: &'stmt str,
decl_type: Option<&'stmt str>,
}
#[cfg(feature = "column_decltype")]
#[cfg_attr(docsrs, doc(cfg(feature = "column_decltype")))]
impl Column<'_> {
/// Returns the name of the column.
#[inline]
#[must_use]
pub fn name(&self) -> &str {
self.name
}
/// Returns the type of the column (`None` for expression).
#[inline]
#[must_use]
pub fn decl_type(&self) -> Option<&str> {
self.decl_type
}
}
impl Statement<'_> {
/// Get all the column names in the result set of the prepared statement.
///
/// If associated DB schema can be altered concurrently, you should make
/// sure that current statement has already been stepped once before
/// calling this method.
pub fn column_names(&self) -> Vec<&str> {
let n = self.column_count();
let mut cols = Vec::with_capacity(n);
for i in 0..n {
let s = self.column_name_unwrap(i);
cols.push(s);
}
cols
}
/// Return the number of columns in the result set returned by the prepared
/// statement.
///
/// If associated DB schema can be altered concurrently, you should make
/// sure that current statement has already been stepped once before
/// calling this method.
#[inline]
pub fn column_count(&self) -> usize {
self.stmt.column_count()
}
/// Check that column name reference lifetime is limited:
/// https://www.sqlite.org/c3ref/column_name.html
/// > The returned string pointer is valid...
///
/// `column_name` reference can become invalid if `stmt` is reprepared
/// (because of schema change) when `query_row` is called. So we assert
/// that a compilation error happens if this reference is kept alive:
/// ```compile_fail
/// use rusqlite::{Connection, Result};
/// fn main() -> Result<()> {
/// let db = Connection::open_in_memory()?;
/// let mut stmt = db.prepare("SELECT 1 as x")?;
/// let column_name = stmt.column_name(0)?;
/// let x = stmt.query_row([], |r| r.get::<_, i64>(0))?; // E0502
/// assert_eq!(1, x);
/// assert_eq!("x", column_name);
/// Ok(())
/// }
/// ```
#[inline]
pub(super) fn column_name_unwrap(&self, col: usize) -> &str {
// Just panic if the bounds are wrong for now, we never call this
// without checking first.
self.column_name(col).expect("Column out of bounds")
}
/// Returns the name assigned to a particular column in the result set
/// returned by the prepared statement.
///
/// If associated DB schema can be altered concurrently, you should make
/// sure that current statement has already been stepped once before
/// calling this method.
///
/// ## Failure
///
/// Returns an `Error::InvalidColumnIndex` if `idx` is outside the valid
/// column range for this row.
///
/// # Panics
///
/// Panics when column name is not valid UTF-8.
#[inline]
pub fn column_name(&self, col: usize) -> Result<&str> {
self.stmt
.column_name(col)
// clippy::or_fun_call (nightly) vs clippy::unnecessary-lazy-evaluations (stable)
.ok_or(Error::InvalidColumnIndex(col))
.map(|slice| {
slice
.to_str()
.expect("Invalid UTF-8 sequence in column name")
})
}
/// Returns the column index in the result set for a given column name.
///
/// If there is no AS clause then the name of the column is unspecified and
/// may change from one release of SQLite to the next.
///
/// If associated DB schema can be altered concurrently, you should make
/// sure that current statement has already been stepped once before
/// calling this method.
///
/// # Failure
///
/// Will return an `Error::InvalidColumnName` when there is no column with
/// the specified `name`.
#[inline]
pub fn column_index(&self, name: &str) -> Result<usize> {
let bytes = name.as_bytes();
let n = self.column_count();
for i in 0..n {
// Note: `column_name` is only fallible if `i` is out of bounds,
// which we've already checked.
if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).unwrap().to_bytes()) {
return Ok(i);
}
}
Err(Error::InvalidColumnName(String::from(name)))
}
/// Returns a slice describing the columns of the result of the query.
///
/// If associated DB schema can be altered concurrently, you should make
/// sure that current statement has already been stepped once before
/// calling this method.
#[cfg(feature = "column_decltype")]
#[cfg_attr(docsrs, doc(cfg(feature = "column_decltype")))]
pub fn columns(&self) -> Vec<Column> {
let n = self.column_count();
let mut cols = Vec::with_capacity(n);
for i in 0..n {
let name = self.column_name_unwrap(i);
let slice = self.stmt.column_decltype(i);
let decl_type = slice.map(|s| {
s.to_str()
.expect("Invalid UTF-8 sequence in column declaration")
});
cols.push(Column { name, decl_type });
}
cols
}
}
#[cfg(test)]
mod test {
use crate::{Connection, Result};
#[test]
#[cfg(feature = "column_decltype")]
fn test_columns() -> Result<()> {
use super::Column;
let db = Connection::open_in_memory()?;
let query = db.prepare("SELECT * FROM sqlite_master")?;
let columns = query.columns();
let column_names: Vec<&str> = columns.iter().map(Column::name).collect();
assert_eq!(
column_names.as_slice(),
&["type", "name", "tbl_name", "rootpage", "sql"]
);
let column_types: Vec<Option<String>> = columns
.iter()
.map(|col| col.decl_type().map(str::to_lowercase))
.collect();
assert_eq!(
&column_types[..3],
&[
Some("text".to_owned()),
Some("text".to_owned()),
Some("text".to_owned()),
]
);
Ok(())
}
#[test]
fn test_column_name_in_error() -> Result<()> {
use crate::{types::Type, Error};
let db = Connection::open_in_memory()?;
db.execute_batch(
"BEGIN;
CREATE TABLE foo(x INTEGER, y TEXT);
INSERT INTO foo VALUES(4, NULL);
END;",
)?;
let mut stmt = db.prepare("SELECT x as renamed, y FROM foo")?;
let mut rows = stmt.query([])?;
let row = rows.next()?.unwrap();
match row.get::<_, String>(0).unwrap_err() {
Error::InvalidColumnType(idx, name, ty) => {
assert_eq!(idx, 0);
assert_eq!(name, "renamed");
assert_eq!(ty, Type::Integer);
}
e => {
panic!("Unexpected error type: {e:?}");
}
}
match row.get::<_, String>("y").unwrap_err() {
Error::InvalidColumnType(idx, name, ty) => {
assert_eq!(idx, 1);
assert_eq!(name, "y");
assert_eq!(ty, Type::Null);
}
e => {
panic!("Unexpected error type: {e:?}");
}
}
Ok(())
}
/// `column_name` reference should stay valid until `stmt` is reprepared (or
/// reset) even if DB schema is altered (SQLite documentation is
/// ambiguous here because it says reference "is valid until (...) the next
/// call to sqlite3_column_name() or sqlite3_column_name16() on the same
/// column.". We assume that reference is valid if only
/// `sqlite3_column_name()` is used):
#[test]
#[cfg(feature = "modern_sqlite")]
fn test_column_name_reference() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE y (x);")?;
let stmt = db.prepare("SELECT x FROM y;")?;
let column_name = stmt.column_name(0)?;
assert_eq!("x", column_name);
db.execute_batch("ALTER TABLE y RENAME COLUMN x TO z;")?;
// column name is not refreshed until statement is re-prepared
let same_column_name = stmt.column_name(0)?;
assert_eq!(same_column_name, column_name);
Ok(())
}
}