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#[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 #[inline]
20 #[must_use]
21 pub fn name(&self) -> &str {
22 self.name
23 }
24
25 #[inline]
27 #[must_use]
28 pub fn decl_type(&self) -> Option<&str> {
29 self.decl_type
30 }
31}
32
33#[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 pub fn name(&self) -> &str {
49 self.name
50 }
51
52 #[inline]
53 #[must_use]
54 pub fn database_name(&self) -> Option<&str> {
56 self.database_name
57 }
58
59 #[inline]
60 #[must_use]
61 pub fn table_name(&self) -> Option<&str> {
63 self.table_name
64 }
65
66 #[inline]
67 #[must_use]
68 pub fn origin_name(&self) -> Option<&str> {
70 self.origin_name
71 }
72}
73
74impl Statement<'_> {
75 pub fn column_names(&self) -> impl DoubleEndedIterator<Item = &str> + ExactSizeIterator {
81 ColumnsIter::new(self, Statement::column_name_unwrap)
82 }
83
84 #[inline]
91 pub fn column_count(&self) -> usize {
92 self.stmt.column_count()
93 }
94
95 #[inline]
115 pub(super) fn column_name_unwrap(&self, col: usize) -> &str {
116 self.column_name(col).expect("Column out of bounds")
119 }
120
121 #[inline]
137 pub fn column_name(&self, col: usize) -> Result<&str> {
138 self.stmt
139 .column_name(col)
140 .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 #[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 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 #[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 #[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 #[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 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 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 #[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 #[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 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}