1#![allow(clippy::cast_possible_truncation)]
23#![allow(clippy::cast_sign_loss)]
24#![allow(clippy::cast_lossless)]
25#![allow(clippy::result_large_err)] #![allow(clippy::borrow_as_ptr)] #![allow(clippy::if_not_else)] #![allow(clippy::implicit_clone)] #![allow(clippy::map_unwrap_or)] #![allow(clippy::redundant_closure)] use crate::ffi;
33use crate::types;
34use sqlmodel_core::{
35 Connection, Cx, Error, IsolationLevel, Outcome, PreparedStatement, Row, TransactionOps, Value,
36 error::{ConfigError, ConnectionError, ConnectionErrorKind, QueryError, QueryErrorKind},
37 row::ColumnInfo,
38};
39use std::ffi::{CStr, CString, c_int};
40use std::future::Future;
41use std::ptr;
42use std::sync::{Arc, Mutex};
43use std::time::{Duration, Instant};
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub struct SqliteErrorCode {
53 primary: c_int,
54 extended: c_int,
55}
56
57impl SqliteErrorCode {
58 const fn from_result_codes(result: c_int, extended: c_int) -> Self {
59 Self {
60 primary: result & 0xff,
61 extended,
62 }
63 }
64
65 #[must_use]
67 pub const fn primary(self) -> c_int {
68 self.primary
69 }
70
71 #[must_use]
73 pub const fn extended(self) -> c_int {
74 self.extended
75 }
76}
77
78impl std::fmt::Display for SqliteErrorCode {
79 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 write!(
81 formatter,
82 "SQLite result code {} (extended {})",
83 self.primary, self.extended
84 )
85 }
86}
87
88impl std::error::Error for SqliteErrorCode {}
89
90#[must_use]
96pub fn sqlite_error_code(error: &Error) -> Option<SqliteErrorCode> {
97 std::error::Error::source(error)?
98 .downcast_ref::<SqliteErrorCode>()
99 .copied()
100}
101
102#[cfg(feature = "console")]
103use sqlmodel_console::{ConsoleAware, SqlModelConsole};
104
105#[derive(Debug, Clone)]
107pub struct SqliteConfig {
108 pub path: String,
110 pub flags: OpenFlags,
112 pub busy_timeout_ms: u32,
114}
115
116#[derive(Debug, Clone, Copy, Default)]
118pub struct OpenFlags {
119 pub read_only: bool,
121 pub read_write: bool,
123 pub create: bool,
125 pub uri: bool,
127 pub no_mutex: bool,
129 pub full_mutex: bool,
131 pub shared_cache: bool,
134 pub private_cache: bool,
138}
139
140impl OpenFlags {
141 pub fn read_only() -> Self {
143 Self {
144 read_only: true,
145 ..Default::default()
146 }
147 }
148
149 pub fn read_write() -> Self {
151 Self {
152 read_write: true,
153 ..Default::default()
154 }
155 }
156
157 pub fn create_read_write() -> Self {
159 Self {
160 read_write: true,
161 create: true,
162 ..Default::default()
163 }
164 }
165
166 fn to_sqlite_flags(self) -> c_int {
167 let mut flags = 0;
168
169 if self.read_only {
170 flags |= ffi::SQLITE_OPEN_READONLY;
171 }
172 if self.read_write {
173 flags |= ffi::SQLITE_OPEN_READWRITE;
174 }
175 if self.create {
176 flags |= ffi::SQLITE_OPEN_CREATE;
177 }
178 if self.uri {
179 flags |= ffi::SQLITE_OPEN_URI;
180 }
181 if self.no_mutex {
182 flags |= ffi::SQLITE_OPEN_NOMUTEX;
183 }
184 if self.full_mutex {
185 flags |= ffi::SQLITE_OPEN_FULLMUTEX;
186 }
187 if self.shared_cache {
188 flags |= ffi::SQLITE_OPEN_SHAREDCACHE;
189 } else {
190 flags |= ffi::SQLITE_OPEN_PRIVATECACHE;
197 }
198
199 if flags & (ffi::SQLITE_OPEN_READONLY | ffi::SQLITE_OPEN_READWRITE) == 0 {
201 flags |= ffi::SQLITE_OPEN_READWRITE | ffi::SQLITE_OPEN_CREATE;
202 }
203
204 flags
205 }
206}
207
208impl Default for SqliteConfig {
209 fn default() -> Self {
210 Self {
211 path: ":memory:".to_string(),
212 flags: OpenFlags::create_read_write(),
213 busy_timeout_ms: 5000,
214 }
215 }
216}
217
218impl SqliteConfig {
219 pub fn file(path: impl Into<String>) -> Self {
221 Self {
222 path: path.into(),
223 flags: OpenFlags::create_read_write(),
224 busy_timeout_ms: 5000,
225 }
226 }
227
228 pub fn memory() -> Self {
230 Self::default()
231 }
232
233 pub fn flags(mut self, flags: OpenFlags) -> Self {
235 self.flags = flags;
236 self
237 }
238
239 pub fn busy_timeout(mut self, ms: u32) -> Self {
241 self.busy_timeout_ms = ms;
242 self
243 }
244}
245
246struct SqliteInner {
248 db: *mut ffi::sqlite3,
249 in_transaction: bool,
250}
251
252unsafe impl Send for SqliteInner {}
256
257pub struct SqliteConnection {
261 inner: Mutex<SqliteInner>,
262 path: String,
263 uses_shared_cache: bool,
264 #[cfg(feature = "console")]
266 console: Option<Arc<SqlModelConsole>>,
267}
268
269unsafe impl Send for SqliteConnection {}
271unsafe impl Sync for SqliteConnection {}
272
273impl SqliteConnection {
274 pub fn open(config: &SqliteConfig) -> Result<Self, Error> {
276 if config.flags.shared_cache && config.flags.private_cache {
277 return Err(Error::Config(ConfigError {
278 message: "SQLite shared_cache and private_cache flags are mutually exclusive"
279 .to_string(),
280 source: None,
281 }));
282 }
283 let busy_timeout_ms = c_int::try_from(config.busy_timeout_ms).map_err(|_| {
284 Error::Config(ConfigError {
285 message: format!(
286 "SQLite busy timeout {}ms exceeds the native {}ms limit",
287 config.busy_timeout_ms,
288 c_int::MAX
289 ),
290 source: None,
291 })
292 })?;
293 let c_path = CString::new(config.path.as_str()).map_err(|_| {
294 Error::Connection(ConnectionError {
295 kind: ConnectionErrorKind::Connect,
296 message: "Invalid path: contains null byte".to_string(),
297 source: None,
298 })
299 })?;
300
301 let mut db: *mut ffi::sqlite3 = ptr::null_mut();
302 let flags = config.flags.to_sqlite_flags();
303
304 let rc = unsafe { ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags, ptr::null()) };
306
307 if rc != ffi::SQLITE_OK {
308 let error_code = sqlite_error_code_from_db(db, rc);
309 let msg = if !db.is_null() {
310 unsafe {
312 let err_ptr = ffi::sqlite3_errmsg(db);
313 let msg = CStr::from_ptr(err_ptr).to_string_lossy().into_owned();
314 ffi::sqlite3_close(db);
315 msg
316 }
317 } else {
318 ffi::error_string(rc).to_string()
319 };
320
321 return Err(Error::Connection(ConnectionError {
322 kind: ConnectionErrorKind::Connect,
323 message: format!("Failed to open database: {}", msg),
324 source: Some(Box::new(error_code)),
325 }));
326 }
327
328 if busy_timeout_ms > 0 {
330 let busy_rc = unsafe { ffi::sqlite3_busy_timeout(db, busy_timeout_ms) };
332 if busy_rc != ffi::SQLITE_OK {
333 let error_code = sqlite_error_code_from_db(db, busy_rc);
334 let msg = unsafe { CStr::from_ptr(ffi::sqlite3_errmsg(db)) }
335 .to_string_lossy()
336 .into_owned();
337 unsafe { ffi::sqlite3_close(db) };
339 return Err(Error::Connection(ConnectionError {
340 kind: ConnectionErrorKind::Connect,
341 message: format!("Failed to configure SQLite busy timeout: {msg}"),
342 source: Some(Box::new(error_code)),
343 }));
344 }
345 }
346
347 Ok(Self {
348 inner: Mutex::new(SqliteInner {
349 db,
350 in_transaction: false,
351 }),
352 path: config.path.clone(),
353 uses_shared_cache: connection_uses_shared_cache(config),
354 #[cfg(feature = "console")]
355 console: None,
356 })
357 }
358
359 pub fn open_memory() -> Result<Self, Error> {
361 Self::open(&SqliteConfig::memory())
362 }
363
364 pub fn open_file(path: impl Into<String>) -> Result<Self, Error> {
366 Self::open(&SqliteConfig::file(path))
367 }
368
369 pub fn path(&self) -> &str {
371 &self.path
372 }
373
374 pub fn execute_raw(&self, sql: &str) -> Result<(), Error> {
376 let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
377 let c_sql = CString::new(sql).map_err(|_| {
378 Error::Query(QueryError {
379 kind: QueryErrorKind::Syntax,
380 sql: Some(sql.to_string()),
381 sqlstate: None,
382 message: "SQL contains null byte".to_string(),
383 detail: None,
384 hint: None,
385 position: None,
386 source: None,
387 })
388 })?;
389
390 let mut errmsg: *mut std::ffi::c_char = ptr::null_mut();
391
392 let rc = unsafe {
394 ffi::sqlite3_exec(inner.db, c_sql.as_ptr(), None, ptr::null_mut(), &mut errmsg)
395 };
396
397 if rc != ffi::SQLITE_OK {
398 let error_code = sqlite_error_code_from_db(inner.db, rc);
399 let msg = if !errmsg.is_null() {
400 let msg = unsafe { CStr::from_ptr(errmsg).to_string_lossy().into_owned() };
402 unsafe { ffi::sqlite3_free(errmsg.cast()) };
403 msg
404 } else {
405 ffi::error_string(rc).to_string()
406 };
407
408 return Err(Error::Query(QueryError {
409 kind: error_code_to_kind(rc),
410 sql: Some(sql.to_string()),
411 sqlstate: None,
412 message: msg,
413 detail: None,
414 hint: None,
415 position: None,
416 source: Some(Box::new(error_code)),
417 }));
418 }
419
420 Ok(())
421 }
422
423 pub fn backup_to_path(&self, dest_path: impl AsRef<str>) -> Result<(), Error> {
428 let dest = SqliteConnection::open(
429 &SqliteConfig::file(dest_path.as_ref()).flags(OpenFlags::create_read_write()),
430 )?;
431 self.backup_to_connection(&dest)
432 }
433
434 pub fn backup_to_connection(&self, dest: &SqliteConnection) -> Result<(), Error> {
439 if std::ptr::eq(self, dest) {
440 return Err(Error::Connection(ConnectionError {
441 kind: ConnectionErrorKind::Connect,
442 message: "SQLite backup source and destination must be different connections"
443 .to_string(),
444 source: None,
445 }));
446 }
447 if dest.uses_shared_cache {
454 return Err(Error::Connection(ConnectionError {
455 kind: ConnectionErrorKind::Connect,
456 message: "SQLite backup destinations cannot use shared-cache mode".to_string(),
457 source: None,
458 }));
459 }
460 let self_first = (std::ptr::from_ref(self) as usize) <= (std::ptr::from_ref(dest) as usize);
461 let (source_guard, dest_guard) = if self_first {
462 let source_guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
463 let dest_guard = dest.inner.lock().unwrap_or_else(|e| e.into_inner());
464 (source_guard, dest_guard)
465 } else {
466 let dest_guard = dest.inner.lock().unwrap_or_else(|e| e.into_inner());
467 let source_guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
468 (source_guard, dest_guard)
469 };
470
471 let source_db = source_guard.db;
472 let dest_db = dest_guard.db;
473 let source_busy_timeout_ms = sqlite_busy_timeout_ms(source_db)?;
474 let dest_busy_timeout_ms = sqlite_busy_timeout_ms(dest_db)?;
475
476 let main = CString::new("main").expect("static sqlite db name");
477
478 let backup =
480 unsafe { ffi::sqlite3_backup_init(dest_db, main.as_ptr(), source_db, main.as_ptr()) };
481 if backup.is_null() {
482 let result_code = unsafe { ffi::sqlite3_errcode(dest_db) };
483 let error_code = sqlite_error_code_from_db(dest_db, result_code);
484 let msg = unsafe { CStr::from_ptr(ffi::sqlite3_errmsg(dest_db)) }
485 .to_string_lossy()
486 .into_owned();
487 return Err(Error::Connection(ConnectionError {
488 kind: ConnectionErrorKind::Connect,
489 message: format!("SQLite backup init failed: {msg}"),
490 source: Some(Box::new(error_code)),
491 }));
492 }
493
494 let busy_deadline = Instant::now()
495 + Duration::from_millis(
496 u64::try_from(source_busy_timeout_ms.max(dest_busy_timeout_ms))
497 .expect("SQLite busy timeout is non-negative"),
498 );
499 let _busy_timeout_guard = BackupBusyTimeoutGuard::disable(
506 source_db,
507 dest_db,
508 source_busy_timeout_ms,
509 dest_busy_timeout_ms,
510 );
511 let mut rc = unsafe { ffi::sqlite3_backup_step(backup, 100) };
512 loop {
513 if rc == ffi::SQLITE_DONE {
514 break;
515 }
516 if rc == ffi::SQLITE_OK {
517 rc = unsafe { ffi::sqlite3_backup_step(backup, 100) };
518 continue;
519 }
520 if rc == ffi::SQLITE_BUSY || rc == ffi::SQLITE_LOCKED {
521 let now = Instant::now();
522 if now >= busy_deadline {
523 break;
524 }
525 std::thread::sleep(
526 Duration::from_millis(50).min(busy_deadline.saturating_duration_since(now)),
527 );
528 if Instant::now() >= busy_deadline {
529 break;
530 }
531 rc = unsafe { ffi::sqlite3_backup_step(backup, 100) };
532 continue;
533 }
534 break;
535 }
536
537 let backup_error = if rc != ffi::SQLITE_DONE && rc != ffi::SQLITE_OK {
538 Some(backup_step_error(dest_db, rc))
544 } else {
545 None
546 };
547
548 let finish_rc = unsafe { ffi::sqlite3_backup_finish(backup) };
549
550 if let Some(error) = backup_error {
551 return Err(error);
552 }
553
554 if finish_rc != ffi::SQLITE_OK {
555 let error_code = sqlite_error_code_from_db(dest_db, finish_rc);
556 let msg = unsafe { CStr::from_ptr(ffi::sqlite3_errmsg(dest_db)) }
557 .to_string_lossy()
558 .into_owned();
559 return Err(Error::Connection(ConnectionError {
560 kind: ConnectionErrorKind::Connect,
561 message: format!(
562 "SQLite backup finish failed: {} ({})",
563 msg,
564 ffi::error_string(finish_rc)
565 ),
566 source: Some(Box::new(error_code)),
567 }));
568 }
569
570 Ok(())
571 }
572
573 pub fn last_insert_rowid(&self) -> i64 {
575 let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
576 unsafe { ffi::sqlite3_last_insert_rowid(inner.db) }
578 }
579
580 pub fn changes(&self) -> i32 {
582 let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
583 unsafe { ffi::sqlite3_changes(inner.db) }
585 }
586
587 pub fn query_sync(&self, sql: &str, params: &[Value]) -> Result<Vec<Row>, Error> {
592 #[cfg(feature = "console")]
593 let start = std::time::Instant::now();
594
595 let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
596 let stmt = prepare_stmt(inner.db, sql)?;
597
598 for (i, param) in params.iter().enumerate() {
600 let rc = unsafe { types::bind_value(stmt, (i + 1) as c_int, param) };
602 if rc != ffi::SQLITE_OK {
603 let error = bind_error(inner.db, sql, i + 1, rc);
604 unsafe { ffi::sqlite3_finalize(stmt) };
606 return Err(error);
607 }
608 }
609
610 let col_count = unsafe { ffi::sqlite3_column_count(stmt) };
613 let mut col_names = Vec::with_capacity(col_count as usize);
614 for i in 0..col_count {
615 let name =
616 unsafe { types::column_name(stmt, i) }.unwrap_or_else(|| format!("col{}", i));
617 col_names.push(name);
618 }
619 let columns = Arc::new(ColumnInfo::new(col_names.clone()));
620
621 let mut rows = Vec::new();
623 loop {
624 let rc = unsafe { ffi::sqlite3_step(stmt) };
626 match rc {
627 ffi::SQLITE_ROW => {
628 let mut values = Vec::with_capacity(col_count as usize);
629 for i in 0..col_count {
630 let value = unsafe { types::read_column(stmt, i) };
632 values.push(value);
633 }
634 rows.push(Row::with_columns(Arc::clone(&columns), values));
635 }
636 ffi::SQLITE_DONE => break,
637 _ => {
638 let error = step_error(inner.db, sql, rc);
639 unsafe { ffi::sqlite3_finalize(stmt) };
641 return Err(error);
642 }
643 }
644 }
645
646 unsafe { ffi::sqlite3_finalize(stmt) };
648
649 #[cfg(feature = "console")]
651 {
652 let elapsed_ms = start.elapsed().as_secs_f64() * 1000.0;
653 self.emit_query_result(sql, &col_names, &rows, elapsed_ms);
654 }
655
656 Ok(rows)
657 }
658
659 pub fn execute_sync(&self, sql: &str, params: &[Value]) -> Result<u64, Error> {
664 #[cfg(feature = "console")]
665 let start = std::time::Instant::now();
666
667 let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
668 let stmt = prepare_stmt(inner.db, sql)?;
669
670 for (i, param) in params.iter().enumerate() {
672 let rc = unsafe { types::bind_value(stmt, (i + 1) as c_int, param) };
674 if rc != ffi::SQLITE_OK {
675 let error = bind_error(inner.db, sql, i + 1, rc);
676 unsafe { ffi::sqlite3_finalize(stmt) };
678 return Err(error);
679 }
680 }
681
682 let execution_error = loop {
686 let rc = unsafe { ffi::sqlite3_step(stmt) };
688 match rc {
689 ffi::SQLITE_ROW => continue,
690 ffi::SQLITE_DONE => break None,
691 _ => break Some(step_error(inner.db, sql, rc)),
692 }
693 };
694
695 unsafe { ffi::sqlite3_finalize(stmt) };
697
698 if let Some(error) = execution_error {
699 return Err(error);
700 }
701
702 let changes = unsafe { ffi::sqlite3_changes(inner.db) };
704
705 #[cfg(feature = "console")]
706 {
707 let elapsed_ms = start.elapsed().as_secs_f64() * 1000.0;
708 self.emit_execute_timing(sql, changes as u64, elapsed_ms);
709 }
710
711 Ok(changes as u64)
712 }
713
714 fn insert_sync(&self, sql: &str, params: &[Value]) -> Result<i64, Error> {
716 self.execute_sync(sql, params)?;
717 Ok(self.last_insert_rowid())
718 }
719
720 fn begin_sync(&self, isolation: IsolationLevel) -> Result<(), Error> {
722 let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
723 if inner.in_transaction {
724 return Err(Error::Query(QueryError {
725 kind: QueryErrorKind::Database,
726 sql: None,
727 sqlstate: None,
728 message: "Already in a transaction".to_string(),
729 detail: None,
730 hint: None,
731 position: None,
732 source: None,
733 }));
734 }
735
736 let begin_sql = match isolation {
739 IsolationLevel::Serializable => "BEGIN EXCLUSIVE",
740 IsolationLevel::RepeatableRead | IsolationLevel::ReadCommitted => "BEGIN IMMEDIATE",
741 IsolationLevel::ReadUncommitted => "BEGIN DEFERRED",
742 };
743
744 drop(inner); self.execute_raw(begin_sql)?;
746
747 let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
748 inner.in_transaction = true;
749 self.emit_transaction_state("BEGIN");
750 Ok(())
751 }
752
753 fn commit_sync(&self) -> Result<(), Error> {
755 let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
756 if !inner.in_transaction {
757 return Err(Error::Query(QueryError {
758 kind: QueryErrorKind::Database,
759 sql: None,
760 sqlstate: None,
761 message: "Not in a transaction".to_string(),
762 detail: None,
763 hint: None,
764 position: None,
765 source: None,
766 }));
767 }
768
769 drop(inner);
770 self.execute_raw("COMMIT")?;
771
772 let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
773 inner.in_transaction = false;
774 self.emit_transaction_state("COMMIT");
775 Ok(())
776 }
777
778 fn rollback_sync(&self) -> Result<(), Error> {
780 let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
781 if !inner.in_transaction {
782 return Err(Error::Query(QueryError {
783 kind: QueryErrorKind::Database,
784 sql: None,
785 sqlstate: None,
786 message: "Not in a transaction".to_string(),
787 detail: None,
788 hint: None,
789 position: None,
790 source: None,
791 }));
792 }
793
794 drop(inner);
795 self.execute_raw("ROLLBACK")?;
796
797 let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
798 inner.in_transaction = false;
799 self.emit_transaction_state("ROLLBACK");
800 Ok(())
801 }
802}
803
804impl Drop for SqliteConnection {
805 fn drop(&mut self) {
806 if let Ok(inner) = self.inner.lock() {
807 if !inner.db.is_null() {
808 unsafe {
810 ffi::sqlite3_close_v2(inner.db);
811 }
812 }
813 }
814 }
815}
816
817pub struct SqliteTransaction<'conn> {
819 conn: &'conn SqliteConnection,
820 committed: bool,
821}
822
823impl<'conn> SqliteTransaction<'conn> {
824 fn new(conn: &'conn SqliteConnection) -> Self {
825 Self {
826 conn,
827 committed: false,
828 }
829 }
830}
831
832impl Drop for SqliteTransaction<'_> {
833 fn drop(&mut self) {
834 if !self.committed {
835 let _ = self.conn.rollback_sync();
837 }
838 }
839}
840
841impl Connection for SqliteConnection {
843 type Tx<'conn>
844 = SqliteTransaction<'conn>
845 where
846 Self: 'conn;
847
848 fn dialect(&self) -> sqlmodel_core::Dialect {
849 sqlmodel_core::Dialect::Sqlite
850 }
851
852 fn query(
853 &self,
854 _cx: &Cx,
855 sql: &str,
856 params: &[Value],
857 ) -> impl Future<Output = Outcome<Vec<Row>, Error>> + Send {
858 let result = self.query_sync(sql, params);
859 async move { result.map_or_else(Outcome::Err, Outcome::Ok) }
860 }
861
862 fn query_one(
863 &self,
864 _cx: &Cx,
865 sql: &str,
866 params: &[Value],
867 ) -> impl Future<Output = Outcome<Option<Row>, Error>> + Send {
868 let result = self.query_sync(sql, params).map(|mut rows| rows.pop());
869 async move { result.map_or_else(Outcome::Err, Outcome::Ok) }
870 }
871
872 fn execute(
873 &self,
874 _cx: &Cx,
875 sql: &str,
876 params: &[Value],
877 ) -> impl Future<Output = Outcome<u64, Error>> + Send {
878 let result = self.execute_sync(sql, params);
879 async move { result.map_or_else(Outcome::Err, Outcome::Ok) }
880 }
881
882 fn insert(
883 &self,
884 _cx: &Cx,
885 sql: &str,
886 params: &[Value],
887 ) -> impl Future<Output = Outcome<i64, Error>> + Send {
888 let result = self.insert_sync(sql, params);
889 async move { result.map_or_else(Outcome::Err, Outcome::Ok) }
890 }
891
892 fn batch(
893 &self,
894 _cx: &Cx,
895 statements: &[(String, Vec<Value>)],
896 ) -> impl Future<Output = Outcome<Vec<u64>, Error>> + Send {
897 let mut results = Vec::with_capacity(statements.len());
898 let mut error = None;
899
900 for (sql, params) in statements {
901 match self.execute_sync(sql, params) {
902 Ok(n) => results.push(n),
903 Err(e) => {
904 error = Some(e);
905 break;
906 }
907 }
908 }
909
910 async move {
911 match error {
912 Some(e) => Outcome::Err(e),
913 None => Outcome::Ok(results),
914 }
915 }
916 }
917
918 fn begin(&self, cx: &Cx) -> impl Future<Output = Outcome<Self::Tx<'_>, Error>> + Send {
919 self.begin_with(cx, IsolationLevel::default())
920 }
921
922 fn begin_with(
923 &self,
924 _cx: &Cx,
925 isolation: IsolationLevel,
926 ) -> impl Future<Output = Outcome<Self::Tx<'_>, Error>> + Send {
927 let result = self
928 .begin_sync(isolation)
929 .map(|()| SqliteTransaction::new(self));
930 async move { result.map_or_else(Outcome::Err, Outcome::Ok) }
931 }
932
933 fn prepare(
934 &self,
935 _cx: &Cx,
936 sql: &str,
937 ) -> impl Future<Output = Outcome<PreparedStatement, Error>> + Send {
938 let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
939 let result = prepare_stmt(inner.db, sql).map(|stmt| {
940 let param_count = unsafe { ffi::sqlite3_bind_parameter_count(stmt) } as usize;
942 let col_count = unsafe { ffi::sqlite3_column_count(stmt) } as c_int;
943
944 let mut columns = Vec::with_capacity(col_count as usize);
945 for i in 0..col_count {
946 if let Some(name) = unsafe { types::column_name(stmt, i) } {
947 columns.push(name);
948 }
949 }
950
951 unsafe { ffi::sqlite3_finalize(stmt) };
953
954 let id = sql.as_ptr() as u64;
956 PreparedStatement::with_columns(id, sql.to_string(), param_count, columns)
957 });
958
959 async move { result.map_or_else(Outcome::Err, Outcome::Ok) }
960 }
961
962 fn query_prepared(
963 &self,
964 cx: &Cx,
965 stmt: &PreparedStatement,
966 params: &[Value],
967 ) -> impl Future<Output = Outcome<Vec<Row>, Error>> + Send {
968 self.query(cx, stmt.sql(), params)
971 }
972
973 fn execute_prepared(
974 &self,
975 cx: &Cx,
976 stmt: &PreparedStatement,
977 params: &[Value],
978 ) -> impl Future<Output = Outcome<u64, Error>> + Send {
979 self.execute(cx, stmt.sql(), params)
980 }
981
982 fn ping(&self, _cx: &Cx) -> impl Future<Output = Outcome<(), Error>> + Send {
983 let result = self.query_sync("SELECT 1", &[]).map(|_| ());
985 async move { result.map_or_else(Outcome::Err, Outcome::Ok) }
986 }
987
988 fn close(self, _cx: &Cx) -> impl Future<Output = sqlmodel_core::Result<()>> + Send {
989 std::future::ready(Ok(()))
991 }
992}
993
994impl TransactionOps for SqliteTransaction<'_> {
996 fn query(
997 &self,
998 _cx: &Cx,
999 sql: &str,
1000 params: &[Value],
1001 ) -> impl Future<Output = Outcome<Vec<Row>, Error>> + Send {
1002 let result = self.conn.query_sync(sql, params);
1003 async move { result.map_or_else(Outcome::Err, Outcome::Ok) }
1004 }
1005
1006 fn query_one(
1007 &self,
1008 _cx: &Cx,
1009 sql: &str,
1010 params: &[Value],
1011 ) -> impl Future<Output = Outcome<Option<Row>, Error>> + Send {
1012 let result = self.conn.query_sync(sql, params).map(|mut rows| rows.pop());
1013 async move { result.map_or_else(Outcome::Err, Outcome::Ok) }
1014 }
1015
1016 fn execute(
1017 &self,
1018 _cx: &Cx,
1019 sql: &str,
1020 params: &[Value],
1021 ) -> impl Future<Output = Outcome<u64, Error>> + Send {
1022 let result = self.conn.execute_sync(sql, params);
1023 async move { result.map_or_else(Outcome::Err, Outcome::Ok) }
1024 }
1025
1026 fn savepoint(&self, _cx: &Cx, name: &str) -> impl Future<Output = Outcome<(), Error>> + Send {
1027 let quoted_name = format!("\"{}\"", name.replace('"', "\"\""));
1029 let sql = format!("SAVEPOINT {}", quoted_name);
1030 let result = self.conn.execute_raw(&sql);
1031 async move { result.map_or_else(Outcome::Err, Outcome::Ok) }
1032 }
1033
1034 fn rollback_to(&self, _cx: &Cx, name: &str) -> impl Future<Output = Outcome<(), Error>> + Send {
1035 let quoted_name = format!("\"{}\"", name.replace('"', "\"\""));
1037 let sql = format!("ROLLBACK TO {}", quoted_name);
1038 let result = self.conn.execute_raw(&sql);
1039 async move { result.map_or_else(Outcome::Err, Outcome::Ok) }
1040 }
1041
1042 fn release(&self, _cx: &Cx, name: &str) -> impl Future<Output = Outcome<(), Error>> + Send {
1043 let quoted_name = format!("\"{}\"", name.replace('"', "\"\""));
1045 let sql = format!("RELEASE {}", quoted_name);
1046 let result = self.conn.execute_raw(&sql);
1047 async move { result.map_or_else(Outcome::Err, Outcome::Ok) }
1048 }
1049
1050 fn commit(mut self, _cx: &Cx) -> impl Future<Output = Outcome<(), Error>> + Send {
1051 self.committed = true;
1052 std::future::ready(
1053 self.conn
1054 .commit_sync()
1055 .map_or_else(Outcome::Err, Outcome::Ok),
1056 )
1057 }
1058
1059 fn rollback(mut self, _cx: &Cx) -> impl Future<Output = Outcome<(), Error>> + Send {
1060 self.committed = true; std::future::ready(
1062 self.conn
1063 .rollback_sync()
1064 .map_or_else(Outcome::Err, Outcome::Ok),
1065 )
1066 }
1067}
1068
1069fn connection_uses_shared_cache(config: &SqliteConfig) -> bool {
1072 if config.path == ":memory:" || config.path.is_empty() {
1076 return false;
1077 }
1078
1079 let uri_mode = sqlite_uri_cache_mode(&config.path);
1080 if config.flags.uri {
1081 uri_mode.unwrap_or(config.flags.shared_cache)
1084 } else {
1085 config.flags.shared_cache || uri_mode == Some(true)
1090 }
1091}
1092
1093struct BackupBusyTimeoutGuard {
1094 source_db: *mut ffi::sqlite3,
1095 dest_db: *mut ffi::sqlite3,
1096 source_timeout_ms: c_int,
1097 dest_timeout_ms: c_int,
1098}
1099
1100impl BackupBusyTimeoutGuard {
1101 fn disable(
1102 source_db: *mut ffi::sqlite3,
1103 dest_db: *mut ffi::sqlite3,
1104 source_timeout_ms: c_int,
1105 dest_timeout_ms: c_int,
1106 ) -> Self {
1107 let source_rc = unsafe { ffi::sqlite3_busy_timeout(source_db, 0) };
1110 let dest_rc = unsafe { ffi::sqlite3_busy_timeout(dest_db, 0) };
1111 debug_assert_eq!(source_rc, ffi::SQLITE_OK);
1112 debug_assert_eq!(dest_rc, ffi::SQLITE_OK);
1113
1114 Self {
1115 source_db,
1116 dest_db,
1117 source_timeout_ms,
1118 dest_timeout_ms,
1119 }
1120 }
1121}
1122
1123fn sqlite_busy_timeout_ms(db: *mut ffi::sqlite3) -> Result<c_int, Error> {
1124 const SQL: &str = "PRAGMA busy_timeout";
1125 let stmt = prepare_stmt(db, SQL)?;
1126
1127 let row_rc = unsafe { ffi::sqlite3_step(stmt) };
1129 if row_rc != ffi::SQLITE_ROW {
1130 let error = step_error(db, SQL, row_rc);
1131 unsafe { ffi::sqlite3_finalize(stmt) };
1132 return Err(error);
1133 }
1134 let timeout_ms = unsafe { ffi::sqlite3_column_int(stmt, 0) };
1135
1136 let done_rc = unsafe { ffi::sqlite3_step(stmt) };
1137 if done_rc != ffi::SQLITE_DONE {
1138 let error = step_error(db, SQL, done_rc);
1139 unsafe { ffi::sqlite3_finalize(stmt) };
1140 return Err(error);
1141 }
1142 unsafe { ffi::sqlite3_finalize(stmt) };
1143
1144 Ok(timeout_ms.max(0))
1145}
1146
1147impl Drop for BackupBusyTimeoutGuard {
1148 fn drop(&mut self) {
1149 let source_rc =
1152 unsafe { ffi::sqlite3_busy_timeout(self.source_db, self.source_timeout_ms) };
1153 let dest_rc = unsafe { ffi::sqlite3_busy_timeout(self.dest_db, self.dest_timeout_ms) };
1154 debug_assert_eq!(source_rc, ffi::SQLITE_OK);
1155 debug_assert_eq!(dest_rc, ffi::SQLITE_OK);
1156 }
1157}
1158
1159fn sqlite_uri_cache_mode(path: &str) -> Option<bool> {
1160 let query = path.strip_prefix("file:")?.split_once('?')?.1;
1161 let query = query.split_once('#').map_or(query, |(query, _)| query);
1162 let mut cache_mode = None;
1163
1164 for parameter in query.split('&') {
1165 let (name, value) = parameter.split_once('=').unwrap_or((parameter, ""));
1166 let name = percent_decode_uri_component(name);
1167 if name != b"cache" {
1168 continue;
1169 }
1170
1171 match percent_decode_uri_component(value).as_slice() {
1172 b"shared" => cache_mode = Some(true),
1173 b"private" => cache_mode = Some(false),
1174 _ => {
1175 }
1180 }
1181 }
1182
1183 cache_mode
1184}
1185
1186fn percent_decode_uri_component(component: &str) -> Vec<u8> {
1187 let bytes = component.as_bytes();
1188 let mut decoded = Vec::with_capacity(bytes.len());
1189 let mut index = 0;
1190 while index < bytes.len() {
1191 if bytes[index] == b'%' {
1192 if let (Some(high), Some(low)) = (bytes.get(index + 1), bytes.get(index + 2)) {
1193 if let (Some(high), Some(low)) = (hex_nibble(*high), hex_nibble(*low)) {
1194 let byte = (high << 4) | low;
1195 if byte == 0 {
1196 break;
1199 }
1200 decoded.push(byte);
1201 index += 3;
1202 continue;
1203 }
1204 }
1205 }
1206
1207 decoded.push(bytes[index]);
1208 index += 1;
1209 }
1210 decoded
1211}
1212
1213const fn hex_nibble(byte: u8) -> Option<u8> {
1214 match byte {
1215 b'0'..=b'9' => Some(byte - b'0'),
1216 b'a'..=b'f' => Some(byte - b'a' + 10),
1217 b'A'..=b'F' => Some(byte - b'A' + 10),
1218 _ => None,
1219 }
1220}
1221
1222fn sqlite_error_code_from_db(db: *mut ffi::sqlite3, result: c_int) -> SqliteErrorCode {
1223 let primary = result & 0xff;
1224 let observed_extended = if db.is_null() {
1225 result
1226 } else {
1227 unsafe { ffi::sqlite3_extended_errcode(db) }
1230 };
1231 let extended = if observed_extended & 0xff == primary {
1235 observed_extended
1236 } else {
1237 result
1238 };
1239 SqliteErrorCode::from_result_codes(result, extended)
1240}
1241
1242fn direct_sqlite_error_code(result: c_int) -> SqliteErrorCode {
1243 SqliteErrorCode::from_result_codes(result, result)
1244}
1245
1246fn backup_step_error(db: *mut ffi::sqlite3, result: c_int) -> Error {
1247 let detail = if db.is_null() {
1248 ffi::error_string(result).to_string()
1249 } else {
1250 unsafe { CStr::from_ptr(ffi::sqlite3_errmsg(db)) }
1254 .to_string_lossy()
1255 .into_owned()
1256 };
1257 Error::Connection(ConnectionError {
1258 kind: ConnectionErrorKind::Connect,
1259 message: format!(
1260 "SQLite backup failed: {detail} ({})",
1261 ffi::error_string(result)
1262 ),
1263 source: Some(Box::new(direct_sqlite_error_code(result))),
1264 })
1265}
1266
1267fn prepare_stmt(db: *mut ffi::sqlite3, sql: &str) -> Result<*mut ffi::sqlite3_stmt, Error> {
1268 let c_sql = CString::new(sql).map_err(|_| {
1269 Error::Query(QueryError {
1270 kind: QueryErrorKind::Syntax,
1271 sql: Some(sql.to_string()),
1272 sqlstate: None,
1273 message: "SQL contains null byte".to_string(),
1274 detail: None,
1275 hint: None,
1276 position: None,
1277 source: None,
1278 })
1279 })?;
1280
1281 let mut stmt: *mut ffi::sqlite3_stmt = ptr::null_mut();
1282
1283 let rc = unsafe {
1285 ffi::sqlite3_prepare_v2(
1286 db,
1287 c_sql.as_ptr(),
1288 c_sql.as_bytes().len() as c_int,
1289 &mut stmt,
1290 ptr::null_mut(),
1291 )
1292 };
1293
1294 if rc != ffi::SQLITE_OK {
1295 return Err(prepare_error(db, sql, rc));
1296 }
1297
1298 if stmt.is_null() {
1299 return Err(Error::Query(QueryError {
1300 kind: QueryErrorKind::Syntax,
1301 sql: Some(sql.to_string()),
1302 sqlstate: None,
1303 message: "SQL contains no executable statement".to_string(),
1304 detail: None,
1305 hint: None,
1306 position: None,
1307 source: None,
1308 }));
1309 }
1310
1311 Ok(stmt)
1312}
1313
1314fn prepare_error(db: *mut ffi::sqlite3, sql: &str, code: c_int) -> Error {
1315 let msg = unsafe {
1317 let ptr = ffi::sqlite3_errmsg(db);
1318 CStr::from_ptr(ptr).to_string_lossy().into_owned()
1319 };
1320 let error_code = sqlite_error_code_from_db(db, code);
1321
1322 Error::Query(QueryError {
1323 kind: error_code_to_kind(code),
1324 sql: Some(sql.to_string()),
1325 sqlstate: None,
1326 message: msg,
1327 detail: None,
1328 hint: None,
1329 position: None,
1330 source: Some(Box::new(error_code)),
1331 })
1332}
1333
1334fn bind_error(db: *mut ffi::sqlite3, sql: &str, param_index: usize, code: c_int) -> Error {
1335 let msg = unsafe {
1337 let ptr = ffi::sqlite3_errmsg(db);
1338 CStr::from_ptr(ptr).to_string_lossy().into_owned()
1339 };
1340 let error_code = sqlite_error_code_from_db(db, code);
1341
1342 Error::Query(QueryError {
1343 kind: error_code_to_kind(code),
1344 sql: Some(sql.to_string()),
1345 sqlstate: None,
1346 message: format!("Failed to bind parameter {}: {}", param_index, msg),
1347 detail: None,
1348 hint: None,
1349 position: None,
1350 source: Some(Box::new(error_code)),
1351 })
1352}
1353
1354fn step_error(db: *mut ffi::sqlite3, sql: &str, code: c_int) -> Error {
1355 let msg = unsafe {
1357 let ptr = ffi::sqlite3_errmsg(db);
1358 CStr::from_ptr(ptr).to_string_lossy().into_owned()
1359 };
1360 let error_code = sqlite_error_code_from_db(db, code);
1361
1362 Error::Query(QueryError {
1363 kind: error_code_to_kind(code),
1364 sql: Some(sql.to_string()),
1365 sqlstate: None,
1366 message: msg,
1367 detail: None,
1368 hint: None,
1369 position: None,
1370 source: Some(Box::new(error_code)),
1371 })
1372}
1373
1374fn error_code_to_kind(code: c_int) -> QueryErrorKind {
1375 match code & 0xff {
1376 ffi::SQLITE_CONSTRAINT => QueryErrorKind::Constraint,
1377 ffi::SQLITE_BUSY | ffi::SQLITE_LOCKED => QueryErrorKind::Deadlock,
1378 ffi::SQLITE_PERM | ffi::SQLITE_READONLY | ffi::SQLITE_AUTH => QueryErrorKind::Permission,
1379 ffi::SQLITE_NOTFOUND => QueryErrorKind::NotFound,
1380 ffi::SQLITE_TOOBIG => QueryErrorKind::DataTruncation,
1381 ffi::SQLITE_INTERRUPT => QueryErrorKind::Cancelled,
1382 _ => QueryErrorKind::Database,
1383 }
1384}
1385
1386#[allow(dead_code)]
1388fn format_value(value: &Value) -> String {
1389 match value {
1390 Value::Null => "NULL".to_string(),
1391 Value::Bool(b) => if *b { "true" } else { "false" }.to_string(),
1392 Value::TinyInt(n) => n.to_string(),
1393 Value::SmallInt(n) => n.to_string(),
1394 Value::Int(n) => n.to_string(),
1395 Value::BigInt(n) => n.to_string(),
1396 Value::Float(n) => format!("{:.6}", n),
1397 Value::Double(n) => format!("{:.6}", n),
1398 Value::Text(s) => s.clone(),
1399 Value::Bytes(b) => format!("[BLOB: {} bytes]", b.len()),
1400 Value::Date(d) => d.to_string(),
1401 Value::Time(t) => t.to_string(),
1402 Value::Timestamp(ts) => ts.to_string(),
1403 Value::TimestampTz(ts) => ts.to_string(),
1404 Value::Json(j) => j.to_string(),
1405 Value::Uuid(u) => {
1406 format!(
1408 "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
1409 u[0],
1410 u[1],
1411 u[2],
1412 u[3],
1413 u[4],
1414 u[5],
1415 u[6],
1416 u[7],
1417 u[8],
1418 u[9],
1419 u[10],
1420 u[11],
1421 u[12],
1422 u[13],
1423 u[14],
1424 u[15]
1425 )
1426 }
1427 Value::Decimal(d) => d.to_string(),
1428 Value::Array(arr) => format!("[{} items]", arr.len()),
1429 Value::Default => "DEFAULT".to_string(),
1430 }
1431}
1432
1433#[cfg(feature = "console")]
1436impl ConsoleAware for SqliteConnection {
1437 fn set_console(&mut self, console: Option<Arc<SqlModelConsole>>) {
1438 self.console = console;
1439 self.emit_open_status();
1441 }
1442
1443 fn console(&self) -> Option<&Arc<SqlModelConsole>> {
1444 self.console.as_ref()
1445 }
1446
1447 fn has_console(&self) -> bool {
1448 self.console.is_some()
1449 }
1450}
1451
1452impl SqliteConnection {
1453 #[cfg(feature = "console")]
1455 fn emit_open_status(&self) {
1456 if let Some(console) = &self.console {
1457 let mode = if self.path == ":memory:" {
1459 "in-memory"
1460 } else {
1461 "file"
1462 };
1463
1464 let journal_mode = self
1466 .query_sync("PRAGMA journal_mode", &[])
1467 .ok()
1468 .and_then(|rows| rows.first().and_then(|r| r.get_as::<String>(0).ok()));
1469
1470 let page_size = self
1471 .query_sync("PRAGMA page_size", &[])
1472 .ok()
1473 .and_then(|rows| rows.first().and_then(|r| r.get_as::<i64>(0).ok()));
1474
1475 if console.mode().is_plain() {
1476 let journal = journal_mode.as_deref().unwrap_or("unknown");
1478 console.status(&format!(
1479 "Opened SQLite database: {} ({} mode, journal: {})",
1480 self.path, mode, journal
1481 ));
1482 } else {
1483 console.status(&format!("SQLite database: {}", self.path));
1485 console.status(&format!(" Mode: {}", mode));
1486 if let Some(journal) = journal_mode {
1487 console.status(&format!(" Journal: {}", journal.to_uppercase()));
1488 }
1489 if let Some(size) = page_size {
1490 console.status(&format!(" Page size: {} bytes", size));
1491 }
1492 }
1493 }
1494 }
1495
1496 #[cfg(feature = "console")]
1498 fn emit_transaction_state(&self, state: &str) {
1499 if let Some(console) = &self.console {
1500 if console.mode().is_plain() {
1501 console.status(&format!("Transaction: {}", state));
1502 } else {
1503 console.status(&format!("[{}] Transaction {}", state, state.to_lowercase()));
1504 }
1505 }
1506 }
1507
1508 #[cfg(feature = "console")]
1510 fn emit_query_timing(&self, elapsed_ms: f64, rows: usize) {
1511 if let Some(console) = &self.console {
1512 console.status(&format!("Query: {:.1}ms, {} rows", elapsed_ms, rows));
1513 }
1514 }
1515
1516 #[cfg(feature = "console")]
1518 fn emit_query_result(&self, sql: &str, col_names: &[String], rows: &[Row], elapsed_ms: f64) {
1519 if let Some(console) = &self.console {
1520 let sql_upper = sql.trim().to_uppercase();
1522 let is_pragma = sql_upper.starts_with("PRAGMA");
1523
1524 if is_pragma && !rows.is_empty() {
1525 if console.mode().is_plain() {
1527 console.status(&format!("{}:", sql.trim()));
1529 console.status(&format!(" {}", col_names.join("|")));
1531 for row in rows.iter().take(20) {
1533 let values: Vec<String> = (0..col_names.len())
1534 .map(|i| {
1535 row.get(i)
1536 .map(|v| format_value(v))
1537 .unwrap_or_else(|| "NULL".to_string())
1538 })
1539 .collect();
1540 console.status(&format!(" {}", values.join("|")));
1541 }
1542 if rows.len() > 20 {
1543 console.status(&format!(" ... and {} more rows", rows.len() - 20));
1544 }
1545 console.status(&format!(" ({:.1}ms)", elapsed_ms));
1546 } else {
1547 let mut table_output = String::new();
1549 table_output.push_str(&format!("PRAGMA Query Results ({:.1}ms)\n", elapsed_ms));
1550
1551 let mut widths: Vec<usize> = col_names.iter().map(|c| c.len()).collect();
1553 for row in rows.iter().take(20) {
1554 for (i, w) in widths.iter_mut().enumerate() {
1555 let val_len = row.get(i).map(|v| format_value(v).len()).unwrap_or(4); if val_len > *w {
1557 *w = val_len;
1558 }
1559 }
1560 }
1561
1562 let sep: String = widths
1564 .iter()
1565 .map(|w| "-".repeat(*w + 2))
1566 .collect::<Vec<_>>()
1567 .join("+");
1568 table_output.push_str(&format!("+{}+\n", sep));
1569
1570 let header: String = col_names
1572 .iter()
1573 .enumerate()
1574 .map(|(i, name)| format!(" {:width$} ", name, width = widths[i]))
1575 .collect::<Vec<_>>()
1576 .join("|");
1577 table_output.push_str(&format!("|{}|\n", header));
1578 table_output.push_str(&format!("+{}+\n", sep));
1579
1580 for row in rows.iter().take(20) {
1582 let data: String = (0..col_names.len())
1583 .map(|i| {
1584 let val = row
1585 .get(i)
1586 .map(|v| format_value(v))
1587 .unwrap_or_else(|| "NULL".to_string());
1588 format!(" {:width$} ", val, width = widths[i])
1589 })
1590 .collect::<Vec<_>>()
1591 .join("|");
1592 table_output.push_str(&format!("|{}|\n", data));
1593 }
1594 table_output.push_str(&format!("+{}+", sep));
1595
1596 if rows.len() > 20 {
1597 table_output.push_str(&format!("\n... and {} more rows", rows.len() - 20));
1598 }
1599
1600 console.status(&table_output);
1601 }
1602 } else {
1603 self.emit_query_timing(elapsed_ms, rows.len());
1605 }
1606 }
1607 }
1608
1609 #[cfg(feature = "console")]
1611 fn emit_execute_timing(&self, sql: &str, rows_affected: u64, elapsed_ms: f64) {
1612 if let Some(console) = &self.console {
1613 let sql_upper = sql.trim().to_uppercase();
1614
1615 let op_type = if sql_upper.starts_with("INSERT") {
1617 "Insert"
1618 } else if sql_upper.starts_with("UPDATE") {
1619 "Update"
1620 } else if sql_upper.starts_with("DELETE") {
1621 "Delete"
1622 } else if sql_upper.starts_with("CREATE") {
1623 "Create"
1624 } else if sql_upper.starts_with("DROP") {
1625 "Drop"
1626 } else if sql_upper.starts_with("ALTER") {
1627 "Alter"
1628 } else {
1629 "Execute"
1630 };
1631
1632 if console.mode().is_plain() {
1633 console.status(&format!(
1634 "{}: {} rows affected ({:.1}ms)",
1635 op_type, rows_affected, elapsed_ms
1636 ));
1637 } else {
1638 console.status(&format!(
1639 "[{}] {} rows affected ({:.1}ms)",
1640 op_type.to_uppercase(),
1641 rows_affected,
1642 elapsed_ms
1643 ));
1644 }
1645 }
1646 }
1647
1648 #[cfg(feature = "console")]
1650 pub fn emit_busy_waiting(&self, elapsed_secs: f64) {
1651 if let Some(console) = &self.console {
1652 if console.mode().is_plain() {
1653 console.status(&format!(
1654 "Waiting for database lock... ({:.1}s)",
1655 elapsed_secs
1656 ));
1657 } else {
1658 console.status(&format!(
1659 "[..] Waiting for database lock... ({:.1}s)",
1660 elapsed_secs
1661 ));
1662 }
1663 }
1664 }
1665
1666 #[cfg(feature = "console")]
1668 pub fn emit_checkpoint_progress(&self, pages_done: u32, pages_total: u32) {
1669 if let Some(console) = &self.console {
1670 let pct = if pages_total > 0 {
1671 (pages_done as f64 / pages_total as f64) * 100.0
1672 } else {
1673 100.0
1674 };
1675
1676 if console.mode().is_plain() {
1677 console.status(&format!(
1678 "WAL checkpoint: {:.0}% ({}/{} pages)",
1679 pct, pages_done, pages_total
1680 ));
1681 } else {
1682 let bar_width: usize = 20;
1684 let filled = ((pct / 100.0) * bar_width as f64).round() as usize;
1685 let empty = bar_width.saturating_sub(filled);
1686 let bar = format!("[{}{}]", "=".repeat(filled), " ".repeat(empty));
1687 console.status(&format!(
1688 "WAL checkpoint: {} {:.0}% ({}/{} pages)",
1689 bar, pct, pages_done, pages_total
1690 ));
1691 }
1692 }
1693 }
1694
1695 #[cfg(not(feature = "console"))]
1697 #[allow(dead_code)]
1698 fn emit_open_status(&self) {}
1699
1700 #[cfg(not(feature = "console"))]
1702 fn emit_transaction_state(&self, _state: &str) {}
1703
1704 #[cfg(not(feature = "console"))]
1706 #[allow(dead_code)]
1707 fn emit_query_timing(&self, _elapsed_ms: f64, _rows: usize) {}
1708
1709 #[cfg(not(feature = "console"))]
1711 #[allow(dead_code)]
1712 fn emit_query_result(
1713 &self,
1714 _sql: &str,
1715 _col_names: &[String],
1716 _rows: &[Row],
1717 _elapsed_ms: f64,
1718 ) {
1719 }
1720
1721 #[cfg(not(feature = "console"))]
1723 #[allow(dead_code)]
1724 fn emit_execute_timing(&self, _sql: &str, _rows_affected: u64, _elapsed_ms: f64) {}
1725
1726 #[cfg(not(feature = "console"))]
1728 pub fn emit_busy_waiting(&self, _elapsed_secs: f64) {}
1729
1730 #[cfg(not(feature = "console"))]
1732 pub fn emit_checkpoint_progress(&self, _pages_done: u32, _pages_total: u32) {}
1733}
1734
1735#[cfg(test)]
1736mod tests {
1737 use super::*;
1738
1739 static NEXT_TEMP_DB: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1740
1741 fn unique_temp_db_path(label: &str) -> std::path::PathBuf {
1742 let nonce = NEXT_TEMP_DB.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1743 std::env::temp_dir().join(format!(
1744 "sqlmodel_{label}_{}_{}.db",
1745 std::process::id(),
1746 nonce
1747 ))
1748 }
1749
1750 #[test]
1751 fn test_open_memory() {
1752 let conn = SqliteConnection::open_memory().unwrap();
1753 assert_eq!(conn.path(), ":memory:");
1754 }
1755
1756 #[test]
1757 fn test_execute_raw() {
1758 let conn = SqliteConnection::open_memory().unwrap();
1759 conn.execute_raw("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
1760 .unwrap();
1761 conn.execute_raw("INSERT INTO test (name) VALUES ('Alice')")
1762 .unwrap();
1763 assert_eq!(conn.changes(), 1);
1764 assert_eq!(conn.last_insert_rowid(), 1);
1765
1766 let pre_sqlite_error = conn
1767 .execute_raw("SELECT \0")
1768 .expect_err("NUL-bearing SQL must fail before SQLite");
1769 assert_eq!(
1770 sqlite_error_code(&pre_sqlite_error),
1771 None,
1772 "errors produced before the native call must not invent a SQLite result code"
1773 );
1774 }
1775
1776 #[test]
1777 fn test_query_sync() {
1778 let conn = SqliteConnection::open_memory().unwrap();
1779 conn.execute_raw("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
1780 .unwrap();
1781 conn.execute_raw("INSERT INTO test (name) VALUES ('Alice'), ('Bob')")
1782 .unwrap();
1783
1784 let rows = conn
1785 .query_sync("SELECT * FROM test ORDER BY id", &[])
1786 .unwrap();
1787 assert_eq!(rows.len(), 2);
1788
1789 assert_eq!(rows[0].get_named::<i32>("id").unwrap(), 1);
1790 assert_eq!(rows[0].get_named::<String>("name").unwrap(), "Alice");
1791 assert_eq!(rows[1].get_named::<i32>("id").unwrap(), 2);
1792 assert_eq!(rows[1].get_named::<String>("name").unwrap(), "Bob");
1793 }
1794
1795 #[test]
1796 fn test_parameterized_query() {
1797 let conn = SqliteConnection::open_memory().unwrap();
1798 conn.execute_raw("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)")
1799 .unwrap();
1800
1801 conn.execute_sync(
1802 "INSERT INTO test (name, age) VALUES (?, ?)",
1803 &[Value::Text("Alice".to_string()), Value::Int(30)],
1804 )
1805 .unwrap();
1806
1807 let rows = conn
1808 .query_sync(
1809 "SELECT * FROM test WHERE name = ?",
1810 &[Value::Text("Alice".to_string())],
1811 )
1812 .unwrap();
1813
1814 assert_eq!(rows.len(), 1);
1815 assert_eq!(rows[0].get_named::<String>("name").unwrap(), "Alice");
1816 assert_eq!(rows[0].get_named::<i32>("age").unwrap(), 30);
1817 }
1818
1819 #[test]
1820 fn test_prepared_errors_retain_exact_native_codes() {
1821 let conn = SqliteConnection::open_memory().unwrap();
1822
1823 let prepare_error = conn
1824 .query_sync("SELEC 1", &[])
1825 .expect_err("invalid SQL must fail during prepare");
1826 let prepare_code = sqlite_error_code(&prepare_error)
1827 .expect("prepare failures must retain their native SQLite result code");
1828 assert_eq!(prepare_code.primary(), ffi::SQLITE_ERROR);
1829 assert_eq!(prepare_code.extended(), ffi::SQLITE_ERROR);
1830
1831 let query_bind_error = conn
1832 .query_sync("SELECT ?1", &[Value::Int(1), Value::Int(2)])
1833 .expect_err("binding beyond the statement parameter count must fail");
1834 let execute_bind_error = conn
1835 .execute_sync("SELECT ?1", &[Value::Int(1), Value::Int(2)])
1836 .expect_err("execute_sync must retain the same bind failure");
1837 for error in [&query_bind_error, &execute_bind_error] {
1838 let code = sqlite_error_code(error)
1839 .expect("bind failures must survive statement finalization");
1840 assert_eq!(code.primary(), ffi::SQLITE_RANGE);
1841 assert_eq!(code.extended(), ffi::SQLITE_RANGE);
1842 }
1843
1844 conn.execute_raw("CREATE TABLE exact_codes (value INTEGER UNIQUE)")
1845 .unwrap();
1846 conn.execute_sync("INSERT INTO exact_codes VALUES (1)", &[])
1847 .unwrap();
1848 let execute_step_error = conn
1849 .execute_sync("INSERT INTO exact_codes VALUES (1)", &[])
1850 .expect_err("duplicate prepared insert must fail during step");
1851 let query_step_error = conn
1852 .query_sync("INSERT INTO exact_codes VALUES (1) RETURNING value", &[])
1853 .expect_err("query_sync must retain a step failure before finalization");
1854 for error in [&execute_step_error, &query_step_error] {
1855 let code = sqlite_error_code(error)
1856 .expect("step failures must retain their extended SQLite result code");
1857 assert_eq!(code.primary(), ffi::SQLITE_CONSTRAINT);
1858 assert_eq!(code.extended(), ffi::SQLITE_CONSTRAINT_UNIQUE);
1859 assert!(
1860 matches!(error, Error::Query(query) if query.kind == QueryErrorKind::Constraint),
1861 "unique violations should map to the constraint error family: {error}"
1862 );
1863 }
1864 }
1865
1866 #[test]
1867 fn test_empty_prepared_sql_is_rejected_before_statement_ffi() {
1868 let conn = SqliteConnection::open_memory().unwrap();
1869
1870 for sql in ["", " \n\t", "-- comment only\n", "/* comment only */"] {
1871 for error in [
1872 conn.query_sync(sql, &[])
1873 .expect_err("empty query SQL must not produce a null statement"),
1874 conn.execute_sync(sql, &[])
1875 .expect_err("empty execute SQL must not produce a null statement"),
1876 ] {
1877 assert!(
1878 matches!(error, Error::Query(ref query) if query.kind == QueryErrorKind::Syntax),
1879 "empty prepared SQL should be a typed syntax error: {error}"
1880 );
1881 assert!(error.to_string().contains("no executable statement"));
1882 assert_eq!(sqlite_error_code(&error), None);
1883 }
1884 }
1885 }
1886
1887 #[test]
1888 fn test_execute_returning_steps_until_done_and_retains_late_busy() {
1889 let path = unique_temp_db_path("returning_busy");
1890 let _ = std::fs::remove_file(&path);
1891 let config = SqliteConfig::file(path.to_string_lossy().into_owned()).busy_timeout(0);
1892 let writer = SqliteConnection::open(&config).unwrap();
1893 writer.execute_raw("PRAGMA journal_mode=DELETE").unwrap();
1894 writer
1895 .execute_raw("CREATE TABLE returning_rows (value INTEGER)")
1896 .unwrap();
1897 let reader = SqliteConnection::open(&config).unwrap();
1898 reader.execute_raw("BEGIN DEFERRED").unwrap();
1899 reader
1900 .query_sync("SELECT COUNT(*) FROM returning_rows", &[])
1901 .unwrap();
1902
1903 let error = writer
1904 .execute_sync("INSERT INTO returning_rows VALUES (7) RETURNING value", &[])
1905 .expect_err("the read lock must surface after RETURNING rows but before DONE");
1906 let code = sqlite_error_code(&error)
1907 .expect("late RETURNING completion failure must retain its native code");
1908 assert_eq!(code.primary(), ffi::SQLITE_BUSY);
1909 assert!(
1910 matches!(error, Error::Query(ref query) if query.kind == QueryErrorKind::Deadlock),
1911 "late SQLITE_BUSY should map to the deadlock family: {error}"
1912 );
1913
1914 reader.execute_raw("ROLLBACK").unwrap();
1915 let rows = writer
1916 .query_sync("SELECT COUNT(*) AS row_count FROM returning_rows", &[])
1917 .unwrap();
1918 assert_eq!(rows[0].get_named::<i64>("row_count").unwrap(), 0);
1919 drop(reader);
1920 drop(writer);
1921 let _ = std::fs::remove_file(path);
1922 }
1923
1924 #[test]
1925 fn test_non_native_connection_preflights_have_no_sqlite_code() {
1926 let invalid_timeout = SqliteConfig::memory().busy_timeout(c_int::MAX as u32 + 1);
1927 let Err(timeout_error) = SqliteConnection::open(&invalid_timeout) else {
1928 panic!("out-of-range native busy timeout must fail closed");
1929 };
1930 assert!(matches!(timeout_error, Error::Config(_)));
1931 assert_eq!(sqlite_error_code(&timeout_error), None);
1932
1933 let conn = SqliteConnection::open_memory().unwrap();
1934 let backup_error = conn
1935 .backup_to_connection(&conn)
1936 .expect_err("backing a connection up onto itself must fail before locking");
1937 assert!(
1938 backup_error
1939 .to_string()
1940 .contains("source and destination must be different")
1941 );
1942 assert_eq!(sqlite_error_code(&backup_error), None);
1943
1944 let contradictory_cache_flags = SqliteConfig::memory().flags(OpenFlags {
1945 shared_cache: true,
1946 private_cache: true,
1947 ..OpenFlags::create_read_write()
1948 });
1949 let Err(cache_error) = SqliteConnection::open(&contradictory_cache_flags) else {
1950 panic!("contradictory SQLite cache flags must fail before native open");
1951 };
1952 assert!(matches!(cache_error, Error::Config(_)));
1953 assert_eq!(sqlite_error_code(&cache_error), None);
1954 assert_ne!(
1955 OpenFlags::create_read_write().to_sqlite_flags() & ffi::SQLITE_OPEN_PRIVATECACHE,
1956 0,
1957 "ordinary opens must override process-global shared-cache mode"
1958 );
1959 }
1960
1961 #[test]
1962 fn test_backup_copies_data_and_opposite_directions_do_not_deadlock() {
1963 let left = Arc::new(SqliteConnection::open_memory().unwrap());
1964 let right = Arc::new(SqliteConnection::open_memory().unwrap());
1965 left.execute_raw("CREATE TABLE backup_rows (value INTEGER)")
1966 .unwrap();
1967 left.execute_raw("INSERT INTO backup_rows VALUES (7)")
1968 .unwrap();
1969
1970 left.backup_to_connection(&right)
1971 .expect("ordinary backup should copy the source database");
1972 let copied = right
1973 .query_sync("SELECT value FROM backup_rows", &[])
1974 .expect("copied table should be readable");
1975 assert_eq!(copied[0].get_named::<i64>("value").unwrap(), 7);
1976
1977 let barrier = Arc::new(std::sync::Barrier::new(3));
1978 let (completed_tx, completed_rx) = std::sync::mpsc::channel();
1979 let mut workers = Vec::new();
1980 for (source, destination) in [
1981 (Arc::clone(&left), Arc::clone(&right)),
1982 (Arc::clone(&right), Arc::clone(&left)),
1983 ] {
1984 let worker_barrier = Arc::clone(&barrier);
1985 let worker_tx = completed_tx.clone();
1986 workers.push(std::thread::spawn(move || {
1987 worker_barrier.wait();
1988 let result = source.backup_to_connection(&destination);
1989 worker_tx.send(result).expect("test receiver remains live");
1990 }));
1991 }
1992 drop(completed_tx);
1993 barrier.wait();
1994 for _ in 0..2 {
1995 completed_rx
1996 .recv_timeout(Duration::from_secs(5))
1997 .expect("opposing backups must not deadlock")
1998 .expect("serialized opposing backup should succeed");
1999 }
2000 for worker in workers {
2001 worker.join().expect("backup worker should not panic");
2002 }
2003 }
2004
2005 #[test]
2006 fn test_backup_lock_retries_respect_deadline_and_restore_busy_timeouts() {
2007 let source = SqliteConnection::open(&SqliteConfig::memory().busy_timeout(500)).unwrap();
2008 source
2009 .execute_raw("CREATE TABLE backup_rows (value INTEGER)")
2010 .unwrap();
2011 source
2012 .execute_raw("INSERT INTO backup_rows VALUES (7)")
2013 .unwrap();
2014
2015 let path = unique_temp_db_path("backup_deadline");
2016 let _ = std::fs::remove_file(&path);
2017 let destination_config =
2018 SqliteConfig::file(path.to_string_lossy().into_owned()).busy_timeout(450);
2019 let destination = SqliteConnection::open(&destination_config).unwrap();
2020 destination
2021 .execute_raw("CREATE TABLE old_rows (value INTEGER)")
2022 .unwrap();
2023 source.execute_raw("PRAGMA busy_timeout=520").unwrap();
2024 destination.execute_raw("PRAGMA busy_timeout=470").unwrap();
2025 let blocker = SqliteConnection::open(&destination_config).unwrap();
2026 blocker.execute_raw("BEGIN EXCLUSIVE").unwrap();
2027
2028 let started = Instant::now();
2029 let error = source
2030 .backup_to_connection(&destination)
2031 .expect_err("an exclusive destination lock must block the backup");
2032 let elapsed = started.elapsed();
2033 let code = sqlite_error_code(&error)
2034 .expect("a lock-blocked backup must retain its native SQLite result code");
2035 assert!(
2036 matches!(code.primary(), ffi::SQLITE_BUSY | ffi::SQLITE_LOCKED),
2037 "unexpected lock failure code: {code}"
2038 );
2039 assert!(
2040 elapsed < Duration::from_millis(800),
2041 "backup retry deadline overran by a native busy-timeout interval: {elapsed:?}"
2042 );
2043
2044 blocker.execute_raw("ROLLBACK").unwrap();
2045 for (connection, expected_timeout) in [(&source, 520), (&destination, 470)] {
2046 let rows = connection.query_sync("PRAGMA busy_timeout", &[]).unwrap();
2047 assert_eq!(
2048 rows[0].get_named::<i32>("timeout").unwrap(),
2049 expected_timeout
2050 );
2051 }
2052
2053 drop(blocker);
2054 drop(destination);
2055 let _ = std::fs::remove_file(path);
2056 }
2057
2058 #[test]
2059 fn test_backup_rejects_shared_cache_destination_before_locking() {
2060 let source = SqliteConnection::open_memory().unwrap();
2061 let shared_uri = format!(
2062 "file:sqlmodel_backup_shared_{}?mode=memory&cache=shared",
2063 std::process::id()
2064 );
2065 let destination =
2066 SqliteConnection::open(&SqliteConfig::file(shared_uri).flags(OpenFlags {
2067 uri: true,
2068 ..OpenFlags::create_read_write()
2069 }))
2070 .expect("shared-cache connection should open for the preflight test");
2071 assert!(destination.uses_shared_cache);
2072
2073 let error = source
2074 .backup_to_connection(&destination)
2075 .expect_err("shared-cache backup destination must fail closed");
2076 assert!(error.to_string().contains("shared-cache mode"));
2077 assert_eq!(sqlite_error_code(&error), None);
2078
2079 let plain_memory = SqliteConnection::open(&SqliteConfig::memory().flags(OpenFlags {
2080 shared_cache: true,
2081 ..OpenFlags::create_read_write()
2082 }))
2083 .expect("plain :memory: remains private even with SHAREDCACHE requested");
2084 assert!(!plain_memory.uses_shared_cache);
2085 source
2086 .backup_to_connection(&plain_memory)
2087 .expect("a truly private in-memory destination is backup-safe");
2088
2089 let private_uri = format!(
2090 "file:sqlmodel_backup_private_{}?mode=memory&cache=private",
2091 std::process::id()
2092 );
2093 let uri_overrides_flag =
2094 SqliteConnection::open(&SqliteConfig::file(private_uri).flags(OpenFlags {
2095 uri: true,
2096 shared_cache: true,
2097 ..OpenFlags::create_read_write()
2098 }))
2099 .expect("URI cache=private should override the shared-cache open flag");
2100 assert!(!uri_overrides_flag.uses_shared_cache);
2101 source
2102 .backup_to_connection(&uri_overrides_flag)
2103 .expect("an effectively private URI destination is backup-safe");
2104 }
2105
2106 #[test]
2107 fn test_sqlite_uri_cache_mode_matches_sqlite_uri_rules() {
2108 assert_eq!(
2109 sqlite_uri_cache_mode("file:memory?mode=memory&cache=shared"),
2110 Some(true)
2111 );
2112 assert_eq!(
2113 sqlite_uri_cache_mode("file:memory?cache=private&cache=shared"),
2114 Some(true),
2115 "SQLite applies duplicate cache parameters in order, so the last one wins"
2116 );
2117 assert_eq!(
2118 sqlite_uri_cache_mode("file:memory?%63ache=%70rivate"),
2119 Some(false),
2120 "SQLite percent-decodes URI parameter names and values"
2121 );
2122 assert_eq!(
2123 sqlite_uri_cache_mode("file:memory?cache%00ignored=shared%00ignored"),
2124 Some(true),
2125 "SQLite truncates URI components at encoded NUL bytes"
2126 );
2127 assert_eq!(
2128 sqlite_uri_cache_mode("file:memory?CACHE=shared"),
2129 None,
2130 "SQLite URI parameter names are case-sensitive"
2131 );
2132 assert_eq!(
2133 sqlite_uri_cache_mode("file:memory?cache=shared#cache=private"),
2134 Some(true),
2135 "SQLite ignores URI fragments"
2136 );
2137 }
2138
2139 #[test]
2140 fn test_backup_failure_retains_native_destination_error() {
2141 let source = SqliteConnection::open_memory().unwrap();
2142 source
2143 .execute_raw("CREATE TABLE backup_source (value INTEGER)")
2144 .unwrap();
2145
2146 let path = unique_temp_db_path("readonly_backup");
2147 let writable = SqliteConnection::open_file(path.to_string_lossy().into_owned()).unwrap();
2148 writable
2149 .execute_raw("CREATE TABLE backup_destination (value INTEGER)")
2150 .unwrap();
2151 drop(writable);
2152 let destination = SqliteConnection::open(
2153 &SqliteConfig::file(path.to_string_lossy().into_owned()).flags(OpenFlags::read_only()),
2154 )
2155 .unwrap();
2156
2157 let error = source
2158 .backup_to_connection(&destination)
2159 .expect_err("read-only destination must reject backup writes");
2160 let code = sqlite_error_code(&error)
2161 .expect("native backup failure must retain its SQLite result code");
2162 assert_eq!(code.primary(), ffi::SQLITE_READONLY);
2163 assert!(error.to_string().to_ascii_lowercase().contains("readonly"));
2164
2165 drop(destination);
2166 let _ = std::fs::remove_file(path);
2167 }
2168
2169 #[test]
2170 fn test_backup_step_error_preserves_direct_extended_result() {
2171 let direct_extended = ffi::SQLITE_IOERR | (42 << 8);
2172 let error = backup_step_error(std::ptr::null_mut(), direct_extended);
2173 let code = sqlite_error_code(&error)
2174 .expect("a backup-step failure must retain its direct native result");
2175 assert_eq!(code.primary(), ffi::SQLITE_IOERR);
2176 assert_eq!(code.extended(), direct_extended);
2177 }
2178
2179 #[test]
2180 fn test_null_handling() {
2181 let conn = SqliteConnection::open_memory().unwrap();
2182 conn.execute_raw("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
2183 .unwrap();
2184
2185 conn.execute_sync("INSERT INTO test (name) VALUES (?)", &[Value::Null])
2186 .unwrap();
2187
2188 let rows = conn.query_sync("SELECT * FROM test", &[]).unwrap();
2189 assert_eq!(rows.len(), 1);
2190 assert_eq!(rows[0].get_named::<Option<String>>("name").unwrap(), None);
2191 }
2192
2193 #[test]
2194 fn test_transaction() {
2195 let conn = SqliteConnection::open_memory().unwrap();
2196 conn.execute_raw("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
2197 .unwrap();
2198
2199 conn.begin_sync(IsolationLevel::default()).unwrap();
2201 conn.execute_sync(
2202 "INSERT INTO test (name) VALUES (?)",
2203 &[Value::Text("Alice".to_string())],
2204 )
2205 .unwrap();
2206 conn.rollback_sync().unwrap();
2207
2208 let rows = conn.query_sync("SELECT * FROM test", &[]).unwrap();
2210 assert_eq!(rows.len(), 0);
2211
2212 conn.begin_sync(IsolationLevel::default()).unwrap();
2214 conn.execute_sync(
2215 "INSERT INTO test (name) VALUES (?)",
2216 &[Value::Text("Bob".to_string())],
2217 )
2218 .unwrap();
2219 conn.commit_sync().unwrap();
2220
2221 let rows = conn.query_sync("SELECT * FROM test", &[]).unwrap();
2223 assert_eq!(rows.len(), 1);
2224 assert_eq!(rows[0].get_named::<String>("name").unwrap(), "Bob");
2225 }
2226
2227 #[test]
2228 fn test_insert_rowid() {
2229 let conn = SqliteConnection::open_memory().unwrap();
2230 conn.execute_raw("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
2231 .unwrap();
2232
2233 let rowid = conn
2234 .insert_sync(
2235 "INSERT INTO test (name) VALUES (?)",
2236 &[Value::Text("Alice".to_string())],
2237 )
2238 .unwrap();
2239 assert_eq!(rowid, 1);
2240
2241 let rowid = conn
2242 .insert_sync(
2243 "INSERT INTO test (name) VALUES (?)",
2244 &[Value::Text("Bob".to_string())],
2245 )
2246 .unwrap();
2247 assert_eq!(rowid, 2);
2248 }
2249
2250 #[test]
2251 #[allow(clippy::approx_constant)]
2252 fn test_type_conversions() {
2253 let conn = SqliteConnection::open_memory().unwrap();
2254 conn.execute_raw(
2255 "CREATE TABLE types (
2256 b BOOLEAN,
2257 i INTEGER,
2258 f REAL,
2259 t TEXT,
2260 bl BLOB
2261 )",
2262 )
2263 .unwrap();
2264
2265 conn.execute_sync(
2266 "INSERT INTO types VALUES (?, ?, ?, ?, ?)",
2267 &[
2268 Value::Bool(true),
2269 Value::BigInt(42),
2270 Value::Double(3.14),
2271 Value::Text("hello".to_string()),
2272 Value::Bytes(vec![1, 2, 3]),
2273 ],
2274 )
2275 .unwrap();
2276
2277 let rows = conn.query_sync("SELECT * FROM types", &[]).unwrap();
2278 assert_eq!(rows.len(), 1);
2279
2280 let b: i32 = rows[0].get_named("b").unwrap();
2282 assert_eq!(b, 1);
2283
2284 let i: i32 = rows[0].get_named("i").unwrap();
2285 assert_eq!(i, 42);
2286
2287 let f: f64 = rows[0].get_named("f").unwrap();
2288 assert!((f - 3.14).abs() < 0.001);
2289
2290 let t: String = rows[0].get_named("t").unwrap();
2291 assert_eq!(t, "hello");
2292
2293 let bl: Vec<u8> = rows[0].get_named("bl").unwrap();
2294 assert_eq!(bl, vec![1, 2, 3]);
2295 }
2296
2297 #[test]
2298 fn test_open_flags() {
2299 let tmp = unique_temp_db_path("open_flags");
2301 let _ = std::fs::remove_file(&tmp); let config = SqliteConfig::file(tmp.to_string_lossy().to_string())
2304 .flags(OpenFlags::create_read_write());
2305 let conn = SqliteConnection::open(&config).unwrap();
2306 conn.execute_raw("CREATE TABLE test (id INTEGER)").unwrap();
2307 drop(conn);
2308
2309 let config =
2311 SqliteConfig::file(tmp.to_string_lossy().to_string()).flags(OpenFlags::read_only());
2312 let conn = SqliteConnection::open(&config).unwrap();
2313
2314 let rows = conn.query_sync("SELECT * FROM test", &[]).unwrap();
2316 assert_eq!(rows.len(), 0);
2317
2318 let error = conn
2320 .execute_raw("INSERT INTO test VALUES (1)")
2321 .expect_err("read-only connection must reject writes");
2322 let error_code = sqlite_error_code(&error)
2323 .expect("native write rejection must retain its exact SQLite result code");
2324 assert_eq!(error_code.primary(), ffi::SQLITE_READONLY);
2325 assert_eq!(error_code.extended() & 0xff, ffi::SQLITE_READONLY);
2326 assert!(
2327 matches!(error, Error::Query(ref query) if query.kind == QueryErrorKind::Permission),
2328 "SQLITE_READONLY should map to the permission error family: {error}"
2329 );
2330
2331 let prepared_error = conn
2332 .execute_sync("INSERT INTO test VALUES (1)", &[])
2333 .expect_err("prepared writes must also retain SQLITE_READONLY");
2334 let prepared_code = sqlite_error_code(&prepared_error)
2335 .expect("prepared write rejection must retain its native result code");
2336 assert_eq!(prepared_code.primary(), ffi::SQLITE_READONLY);
2337 assert!(
2338 matches!(prepared_error, Error::Query(ref query) if query.kind == QueryErrorKind::Permission),
2339 "prepared SQLITE_READONLY should map to permission: {prepared_error}"
2340 );
2341
2342 drop(conn);
2343 let _ = std::fs::remove_file(&tmp);
2344 }
2345
2346 #[cfg(feature = "console")]
2349 mod console_tests {
2350 use super::*;
2351
2352 #[test]
2354 fn test_console_aware_trait_impl() {
2355 let mut conn = SqliteConnection::open_memory().unwrap();
2356
2357 assert!(!conn.has_console());
2359 assert!(conn.console().is_none());
2360
2361 let console = Arc::new(SqlModelConsole::with_mode(
2363 sqlmodel_console::OutputMode::Plain,
2364 ));
2365 conn.set_console(Some(console.clone()));
2366
2367 assert!(conn.has_console());
2369 assert!(conn.console().is_some());
2370
2371 conn.set_console(None);
2373 assert!(!conn.has_console());
2374 }
2375
2376 #[test]
2378 fn test_database_open_feedback() {
2379 let mut conn = SqliteConnection::open_memory().unwrap();
2380
2381 let console = Arc::new(SqlModelConsole::with_mode(
2384 sqlmodel_console::OutputMode::Plain,
2385 ));
2386 conn.set_console(Some(console));
2387
2388 }
2390
2391 #[test]
2393 fn test_pragma_formatting() {
2394 let mut conn = SqliteConnection::open_memory().unwrap();
2395
2396 conn.execute_raw("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
2398 .unwrap();
2399
2400 let console = Arc::new(SqlModelConsole::with_mode(
2402 sqlmodel_console::OutputMode::Plain,
2403 ));
2404 conn.set_console(Some(console));
2405
2406 let rows = conn.query_sync("PRAGMA table_info(test)", &[]).unwrap();
2408
2409 assert!(!rows.is_empty());
2411 }
2412
2413 #[test]
2415 fn test_transaction_state() {
2416 let mut conn = SqliteConnection::open_memory().unwrap();
2417 conn.execute_raw("CREATE TABLE test (id INTEGER PRIMARY KEY)")
2418 .unwrap();
2419
2420 let console = Arc::new(SqlModelConsole::with_mode(
2422 sqlmodel_console::OutputMode::Plain,
2423 ));
2424 conn.set_console(Some(console));
2425
2426 conn.begin_sync(IsolationLevel::default()).unwrap();
2428 conn.execute_sync("INSERT INTO test (id) VALUES (?)", &[Value::Int(1)])
2429 .unwrap();
2430 conn.commit_sync().unwrap();
2431
2432 let rows = conn.query_sync("SELECT * FROM test", &[]).unwrap();
2434 assert_eq!(rows.len(), 1);
2435 }
2436
2437 #[test]
2439 fn test_wal_checkpoint_progress() {
2440 let conn = SqliteConnection::open_memory().unwrap();
2441
2442 conn.emit_checkpoint_progress(50, 100);
2444 conn.emit_checkpoint_progress(100, 100);
2445 conn.emit_checkpoint_progress(0, 0);
2446 }
2447
2448 #[test]
2450 fn test_busy_timeout_feedback() {
2451 let conn = SqliteConnection::open_memory().unwrap();
2452
2453 conn.emit_busy_waiting(0.5);
2455 conn.emit_busy_waiting(2.1);
2456 }
2457
2458 #[test]
2460 fn test_console_disabled_no_output() {
2461 let conn = SqliteConnection::open_memory().unwrap();
2462
2463 conn.emit_busy_waiting(1.0);
2465 conn.emit_checkpoint_progress(10, 100);
2466
2467 conn.execute_raw("CREATE TABLE test (id INTEGER PRIMARY KEY)")
2469 .unwrap();
2470 let rows = conn.query_sync("SELECT * FROM test", &[]).unwrap();
2471 assert_eq!(rows.len(), 0);
2472 }
2473
2474 #[test]
2476 fn test_plain_mode_output() {
2477 let mut conn = SqliteConnection::open_memory().unwrap();
2478
2479 let console = Arc::new(SqlModelConsole::with_mode(
2481 sqlmodel_console::OutputMode::Plain,
2482 ));
2483 conn.set_console(Some(console.clone()));
2484
2485 assert!(conn.console().unwrap().is_plain());
2487
2488 conn.execute_raw("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
2490 .unwrap();
2491 conn.execute_sync(
2492 "INSERT INTO test (name) VALUES (?)",
2493 &[Value::Text("Alice".to_string())],
2494 )
2495 .unwrap();
2496
2497 let rows = conn.query_sync("PRAGMA table_info(test)", &[]).unwrap();
2498 assert!(!rows.is_empty());
2499 }
2500
2501 #[test]
2503 fn test_rich_mode_output() {
2504 let mut conn = SqliteConnection::open_memory().unwrap();
2505
2506 let console = Arc::new(SqlModelConsole::with_mode(
2508 sqlmodel_console::OutputMode::Rich,
2509 ));
2510 conn.set_console(Some(console.clone()));
2511
2512 assert!(conn.console().unwrap().is_rich());
2514
2515 conn.execute_raw("CREATE TABLE test (id INTEGER PRIMARY KEY)")
2517 .unwrap();
2518 conn.emit_checkpoint_progress(50, 100);
2519 }
2520 }
2521}