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