mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-22 16:29:20 +08:00
ba73b81776
stable clippy vs nightly clippy
243 lines
8.4 KiB
Rust
243 lines
8.4 KiB
Rust
use std::str;
|
|
|
|
use crate::{Error, Result, Statement};
|
|
|
|
/// Information about a column of a SQLite query.
|
|
#[derive(Debug)]
|
|
pub struct Column<'stmt> {
|
|
name: &'stmt str,
|
|
decl_type: Option<&'stmt str>,
|
|
}
|
|
|
|
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 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| {
|
|
str::from_utf8(slice.to_bytes()).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| {
|
|
str::from_utf8(s.to_bytes()).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(())
|
|
}
|
|
}
|