use std::ffi::{CStr, c_char};
use std::ptr;
use std::str;
use crate::ffi;
use crate::{Connection, Error, Name, Result, Statement};
#[cfg(feature = "column_decltype")]
#[derive(Debug)]
pub struct Column<'stmt> {
name: &'stmt str,
decl_type: Option<&'stmt str>,
}
#[cfg(feature = "column_decltype")]
impl Column<'_> {
#[inline]
#[must_use]
pub fn name(&self) -> &str {
self.name
}
#[inline]
#[must_use]
pub fn decl_type(&self) -> Option<&str> {
self.decl_type
}
}
#[cfg(feature = "column_metadata")]
#[derive(Debug)]
pub struct ColumnMetadata<'stmt> {
name: &'stmt str,
database_name: Option<&'stmt str>,
table_name: Option<&'stmt str>,
origin_name: Option<&'stmt str>,
}
#[cfg(feature = "column_metadata")]
impl ColumnMetadata<'_> {
#[inline]
#[must_use]
pub fn name(&self) -> &str {
self.name
}
#[inline]
#[must_use]
pub fn database_name(&self) -> Option<&str> {
self.database_name
}
#[inline]
#[must_use]
pub fn table_name(&self) -> Option<&str> {
self.table_name
}
#[inline]
#[must_use]
pub fn origin_name(&self) -> Option<&str> {
self.origin_name
}
}
impl Statement<'_> {
pub fn column_names(&self) -> impl DoubleEndedIterator<Item = &str> + ExactSizeIterator {
ColumnsIter::new(self, Statement::column_name_unwrap)
}
#[inline]
pub fn column_count(&self) -> usize {
self.stmt.column_count()
}
#[inline]
pub(super) fn column_name_unwrap(&self, col: usize) -> &str {
self.column_name(col).expect("Column out of bounds")
}
#[inline]
pub fn column_name(&self, col: usize) -> Result<&str> {
self.stmt
.column_name(col)
.ok_or(Error::InvalidColumnIndex(col))
.map(|slice| {
slice
.to_str()
.expect("Invalid UTF-8 sequence in column 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 {
if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).unwrap().to_bytes()) {
return Ok(i);
}
}
Err(Error::InvalidColumnName(String::from(name)))
}
#[cfg(feature = "column_decltype")]
pub fn columns(&self) -> impl DoubleEndedIterator<Item = Column<'_>> + ExactSizeIterator {
ColumnsIter::new(self, |s, i| Column {
name: s.column_name_unwrap(i),
decl_type: s.stmt.column_decltype(i).map(|s| {
s.to_str()
.expect("Invalid UTF-8 sequence in column declaration")
}),
})
}
#[cfg(feature = "column_metadata")]
pub fn columns_with_metadata(
&self,
) -> impl DoubleEndedIterator<Item = ColumnMetadata<'_>> + ExactSizeIterator {
ColumnsIter::new(self, |s, i| ColumnMetadata {
name: s.column_name_unwrap(i),
database_name: s.stmt.column_database_name(i).map(|s| {
s.to_str()
.expect("Invalid UTF-8 sequence in column db name")
}),
table_name: s.stmt.column_table_name(i).map(|s| {
s.to_str()
.expect("Invalid UTF-8 sequence in column table name")
}),
origin_name: s.stmt.column_origin_name(i).map(|s| {
s.to_str()
.expect("Invalid UTF-8 sequence in column origin name")
}),
})
}
#[cfg(feature = "column_metadata")]
#[expect(clippy::type_complexity)]
pub fn column_metadata(
&self,
col: usize,
) -> Result<
Option<(
&CStr,
&CStr,
&CStr,
Option<&CStr>,
Option<&CStr>,
bool,
bool,
bool,
)>,
> {
let db_name = self.stmt.column_database_name(col);
let table_name = self.stmt.column_table_name(col);
let origin_name = self.stmt.column_origin_name(col);
if db_name.is_none() || table_name.is_none() || origin_name.is_none() {
return Ok(None);
}
let (data_type, coll_seq, not_null, primary_key, auto_inc) =
self.conn
.column_metadata(db_name, table_name.unwrap(), origin_name.unwrap())?;
Ok(Some((
db_name.unwrap(),
table_name.unwrap(),
origin_name.unwrap(),
data_type,
coll_seq,
not_null,
primary_key,
auto_inc,
)))
}
}
impl Connection {
pub fn column_exists<N: Name>(
&self,
db_name: Option<N>,
table_name: N,
column_name: N,
) -> Result<bool> {
self.exists(db_name, table_name, Some(column_name))
}
pub fn table_exists<N: Name>(&self, db_name: Option<N>, table_name: N) -> Result<bool> {
self.exists(db_name, table_name, None)
}
#[expect(clippy::type_complexity)]
pub fn column_metadata<N: Name>(
&self,
db_name: Option<N>,
table_name: N,
column_name: N,
) -> Result<(Option<&CStr>, Option<&CStr>, bool, bool, bool)> {
let cs = db_name.as_ref().map(N::as_cstr).transpose()?;
let db_name = cs.as_ref().map_or(ptr::null(), |s| s.as_ptr());
let table_name = table_name.as_cstr()?;
let column_name = column_name.as_cstr()?;
let mut data_type: *const c_char = ptr::null_mut();
let mut coll_seq: *const c_char = ptr::null_mut();
let mut not_null = 0;
let mut primary_key = 0;
let mut auto_inc = 0;
self.decode_result(unsafe {
ffi::sqlite3_table_column_metadata(
self.handle(),
db_name,
table_name.as_ptr(),
column_name.as_ptr(),
&raw mut data_type,
&raw mut coll_seq,
&raw mut not_null,
&raw mut primary_key,
&raw mut auto_inc,
)
})?;
Ok((
if data_type.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(data_type) })
},
if coll_seq.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(coll_seq) })
},
not_null != 0,
primary_key != 0,
auto_inc != 0,
))
}
fn exists<N: Name>(
&self,
db_name: Option<N>,
table_name: N,
column_name: Option<N>,
) -> Result<bool> {
let cs = db_name.as_ref().map(N::as_cstr).transpose()?;
let db_name = cs.as_ref().map_or(ptr::null(), |s| s.as_ptr());
let table_name = table_name.as_cstr()?;
let cn = column_name.as_ref().map(N::as_cstr).transpose()?;
let column_name = cn.as_ref().map_or(ptr::null(), |s| s.as_ptr());
let r = unsafe {
ffi::sqlite3_table_column_metadata(
self.handle(),
db_name,
table_name.as_ptr(),
column_name,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
)
};
match r {
ffi::SQLITE_OK => Ok(true),
ffi::SQLITE_ERROR => Ok(false),
_ => self.db.borrow().decode_result(r).map(|()| false),
}
}
}
struct ColumnsIter<'s, T> {
stmt: &'s Statement<'s>,
range: std::ops::Range<usize>,
map: fn(stmt: &'s Statement<'s>, col: usize) -> T,
}
impl<'s, T> ColumnsIter<'s, T> {
fn new(stmt: &'s Statement<'s>, map: fn(stmt: &'s Statement<'s>, col: usize) -> T) -> Self {
Self {
stmt,
range: 0..stmt.column_count(),
map,
}
}
}
impl<T> Iterator for ColumnsIter<'_, T> {
type Item = T;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
Some((self.map)(self.stmt, self.range.next()?))
}
#[inline]
fn nth(&mut self, n: usize) -> Option<Self::Item> {
Some((self.map)(self.stmt, self.range.nth(n)?))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.range.size_hint()
}
}
impl<T> DoubleEndedIterator for ColumnsIter<'_, T> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
Some((self.map)(self.stmt, self.range.next_back()?))
}
#[inline]
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
Some((self.map)(self.stmt, self.range.nth_back(n)?))
}
}
impl<T> ExactSizeIterator for ColumnsIter<'_, T> {}
#[cfg(all(test, not(miri)))]
mod test {
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
use wasm_bindgen_test::wasm_bindgen_test as 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().collect::<Vec<_>>();
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]
#[cfg(feature = "column_metadata")]
fn test_columns_with_metadata() -> Result<()> {
let db = Connection::open_in_memory()?;
let query = db.prepare("SELECT *, 1 FROM sqlite_master")?;
let col_mets = query.columns_with_metadata().collect::<Vec<_>>();
assert_eq!(col_mets.len(), 6);
for col in col_mets.iter().take(5) {
assert_eq!(&col.database_name(), &Some("main"));
assert_eq!(&col.table_name(), &Some("sqlite_master"));
}
assert!(col_mets[5].database_name().is_none());
assert!(col_mets[5].table_name().is_none());
assert!(col_mets[5].origin_name().is_none());
let col_origins: Vec<Option<&str>> = col_mets.iter().map(|col| col.origin_name()).collect();
assert_eq!(
&col_origins[..5],
&[
Some("type"),
Some("name"),
Some("tbl_name"),
Some("rootpage"),
Some("sql"),
]
);
Ok(())
}
#[test]
fn test_column_name_in_error() -> Result<()> {
use crate::{Error, types::Type};
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(())
}
#[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;")?;
let same_column_name = stmt.column_name(0)?;
assert_eq!(same_column_name, column_name);
Ok(())
}
#[test]
#[cfg(feature = "column_metadata")]
fn stmt_column_metadata() -> Result<()> {
let db = Connection::open_in_memory()?;
let query = db.prepare("SELECT *, 1 FROM sqlite_master")?;
let (db_name, table_name, col_name, data_type, coll_seq, not_null, primary_key, auto_inc) =
query.column_metadata(0)?.unwrap();
assert_eq!(db_name, crate::MAIN_DB);
assert_eq!(table_name, c"sqlite_master");
assert_eq!(col_name, c"type");
assert_eq!(data_type, Some(c"TEXT"));
assert_eq!(coll_seq, Some(c"BINARY"));
assert!(!not_null);
assert!(!primary_key);
assert!(!auto_inc);
assert!(query.column_metadata(5)?.is_none());
Ok(())
}
#[test]
fn column_exists() -> Result<()> {
let db = Connection::open_in_memory()?;
assert!(db.column_exists(None, c"sqlite_master", c"type")?);
assert!(db.column_exists(Some(crate::TEMP_DB), c"sqlite_master", c"type")?);
assert!(!db.column_exists(Some(crate::MAIN_DB), c"sqlite_temp_master", c"type")?);
Ok(())
}
#[test]
fn table_exists() -> Result<()> {
let db = Connection::open_in_memory()?;
assert!(db.table_exists(None, c"sqlite_master")?);
assert!(db.table_exists(Some(crate::TEMP_DB), c"sqlite_master")?);
assert!(!db.table_exists(Some(crate::MAIN_DB), c"sqlite_temp_master")?);
Ok(())
}
#[test]
fn column_metadata() -> Result<()> {
let db = Connection::open_in_memory()?;
let (data_type, coll_seq, not_null, primary_key, auto_inc) =
db.column_metadata(None, c"sqlite_master", c"type")?;
assert_eq!(
data_type.map(|cs| cs.to_str().unwrap().to_ascii_uppercase()),
Some("TEXT".to_owned())
);
assert_eq!(coll_seq, Some(c"BINARY"));
assert!(!not_null);
assert!(!primary_key);
assert!(!auto_inc);
assert!(db.column_metadata(None, c"sqlite_master", c"foo").is_err());
Ok(())
}
}