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#[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) -> 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 #[inline]
97 pub fn column_count(&self) -> usize {
98 self.stmt.column_count()
99 }
100
101 #[inline]
121 pub(super) fn column_name_unwrap(&self, col: usize) -> &str {
122 self.column_name(col).expect("Column out of bounds")
125 }
126
127 #[inline]
143 pub fn column_name(&self, col: usize) -> Result<&str> {
144 self.stmt
145 .column_name(col)
146 .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 #[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 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 #[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 #[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 #[cfg(feature = "column_metadata")]
249 #[expect(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 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 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 #[expect(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 #[cfg(all(target_family = "wasm", target_os = "unknown"))]
399 use wasm_bindgen_test::wasm_bindgen_test as test;
400
401 use crate::{Connection, Result};
402
403 #[test]
404 #[cfg(feature = "column_decltype")]
405 fn test_columns() -> Result<()> {
406 use super::Column;
407
408 let db = Connection::open_in_memory()?;
409 let query = db.prepare("SELECT * FROM sqlite_master")?;
410 let columns = query.columns();
411 let column_names: Vec<&str> = columns.iter().map(Column::name).collect();
412 assert_eq!(
413 column_names.as_slice(),
414 &["type", "name", "tbl_name", "rootpage", "sql"]
415 );
416 let column_types: Vec<Option<String>> = columns
417 .iter()
418 .map(|col| col.decl_type().map(str::to_lowercase))
419 .collect();
420 assert_eq!(
421 &column_types[..3],
422 &[
423 Some("text".to_owned()),
424 Some("text".to_owned()),
425 Some("text".to_owned()),
426 ]
427 );
428 Ok(())
429 }
430
431 #[test]
432 #[cfg(feature = "column_metadata")]
433 fn test_columns_with_metadata() -> Result<()> {
434 let db = Connection::open_in_memory()?;
435 let query = db.prepare("SELECT *, 1 FROM sqlite_master")?;
436
437 let col_mets = query.columns_with_metadata();
438
439 assert_eq!(col_mets.len(), 6);
440
441 for col in col_mets.iter().take(5) {
442 assert_eq!(&col.database_name(), &Some("main"));
443 assert_eq!(&col.table_name(), &Some("sqlite_master"));
444 }
445
446 assert!(col_mets[5].database_name().is_none());
447 assert!(col_mets[5].table_name().is_none());
448 assert!(col_mets[5].origin_name().is_none());
449
450 let col_origins: Vec<Option<&str>> = col_mets.iter().map(|col| col.origin_name()).collect();
451
452 assert_eq!(
453 &col_origins[..5],
454 &[
455 Some("type"),
456 Some("name"),
457 Some("tbl_name"),
458 Some("rootpage"),
459 Some("sql"),
460 ]
461 );
462
463 Ok(())
464 }
465
466 #[test]
467 fn test_column_name_in_error() -> Result<()> {
468 use crate::{types::Type, Error};
469 let db = Connection::open_in_memory()?;
470 db.execute_batch(
471 "BEGIN;
472 CREATE TABLE foo(x INTEGER, y TEXT);
473 INSERT INTO foo VALUES(4, NULL);
474 END;",
475 )?;
476 let mut stmt = db.prepare("SELECT x as renamed, y FROM foo")?;
477 let mut rows = stmt.query([])?;
478 let row = rows.next()?.unwrap();
479 match row.get::<_, String>(0).unwrap_err() {
480 Error::InvalidColumnType(idx, name, ty) => {
481 assert_eq!(idx, 0);
482 assert_eq!(name, "renamed");
483 assert_eq!(ty, Type::Integer);
484 }
485 e => {
486 panic!("Unexpected error type: {e:?}");
487 }
488 }
489 match row.get::<_, String>("y").unwrap_err() {
490 Error::InvalidColumnType(idx, name, ty) => {
491 assert_eq!(idx, 1);
492 assert_eq!(name, "y");
493 assert_eq!(ty, Type::Null);
494 }
495 e => {
496 panic!("Unexpected error type: {e:?}");
497 }
498 }
499 Ok(())
500 }
501
502 #[test]
509 #[cfg(feature = "modern_sqlite")]
510 fn test_column_name_reference() -> Result<()> {
511 let db = Connection::open_in_memory()?;
512 db.execute_batch("CREATE TABLE y (x);")?;
513 let stmt = db.prepare("SELECT x FROM y;")?;
514 let column_name = stmt.column_name(0)?;
515 assert_eq!("x", column_name);
516 db.execute_batch("ALTER TABLE y RENAME COLUMN x TO z;")?;
517 let same_column_name = stmt.column_name(0)?;
519 assert_eq!(same_column_name, column_name);
520 Ok(())
521 }
522
523 #[test]
524 #[cfg(feature = "column_metadata")]
525 fn stmt_column_metadata() -> Result<()> {
526 let db = Connection::open_in_memory()?;
527 let query = db.prepare("SELECT *, 1 FROM sqlite_master")?;
528 let (db_name, table_name, col_name, data_type, coll_seq, not_null, primary_key, auto_inc) =
529 query.column_metadata(0)?.unwrap();
530 assert_eq!(db_name, crate::MAIN_DB);
531 assert_eq!(table_name, c"sqlite_master");
532 assert_eq!(col_name, c"type");
533 assert_eq!(data_type, Some(c"TEXT"));
534 assert_eq!(coll_seq, Some(c"BINARY"));
535 assert!(!not_null);
536 assert!(!primary_key);
537 assert!(!auto_inc);
538 assert!(query.column_metadata(5)?.is_none());
539 Ok(())
540 }
541
542 #[test]
543 fn column_exists() -> Result<()> {
544 let db = Connection::open_in_memory()?;
545 assert!(db.column_exists(None, c"sqlite_master", c"type")?);
546 assert!(db.column_exists(Some(crate::TEMP_DB), c"sqlite_master", c"type")?);
547 assert!(!db.column_exists(Some(crate::MAIN_DB), c"sqlite_temp_master", c"type")?);
548 Ok(())
549 }
550
551 #[test]
552 fn table_exists() -> Result<()> {
553 let db = Connection::open_in_memory()?;
554 assert!(db.table_exists(None, c"sqlite_master")?);
555 assert!(db.table_exists(Some(crate::TEMP_DB), c"sqlite_master")?);
556 assert!(!db.table_exists(Some(crate::MAIN_DB), c"sqlite_temp_master")?);
557 Ok(())
558 }
559
560 #[test]
561 fn column_metadata() -> Result<()> {
562 let db = Connection::open_in_memory()?;
563 let (data_type, coll_seq, not_null, primary_key, auto_inc) =
564 db.column_metadata(None, c"sqlite_master", c"type")?;
565 assert_eq!(
566 data_type.map(|cs| cs.to_str().unwrap().to_ascii_uppercase()),
567 Some("TEXT".to_owned())
568 );
569 assert_eq!(coll_seq, Some(c"BINARY"));
570 assert!(!not_null);
571 assert!(!primary_key);
572 assert!(!auto_inc);
573 assert!(db.column_metadata(None, c"sqlite_master", c"foo").is_err());
574 Ok(())
575 }
576}