1#![recursion_limit = "512"]
10#![allow(clippy::future_not_send)]
16#![allow(clippy::large_futures)]
17
18pub use fsqlite_core::connection::{
19 Connection, ConnectionEnv, IoPollStrategy, PreparedStatement, Row, RuntimeConfig,
20 RuntimeContext, TraceEvent, TraceMask, init_global_runtime,
21};
22pub use fsqlite_error::FrankenError;
23pub use fsqlite_types::SqliteValue;
24pub use fsqlite_vfs;
25pub use fsqlite_vfs::FileIdentity;
26#[cfg(all(feature = "native", any(unix, windows)))]
27pub use fsqlite_vfs::{
28 DatabaseNamespaceGenerationTransition, NamespaceGenerationTransitionOutcome,
29 begin_database_namespace_generation_transition,
30};
31
32#[cfg(feature = "session")]
33pub mod session {
35 pub use fsqlite_ext_session::{
36 ApplyOutcome, ChangeOp, Changeset, ChangesetRow, ChangesetValue, ConflictAction,
37 ConflictType, Session, SimpleTarget, TableChangeset, TableInfo, changeset_varint_len,
38 extension_name,
39 };
40}
41
42#[cfg(feature = "async-api")]
43pub mod async_api;
44#[cfg(feature = "async-api")]
45pub use async_api::AsyncConnection;
46
47pub mod compat;
48pub mod migrate;
49
50#[cfg(test)]
51#[allow(
52 clippy::too_many_lines,
53 clippy::items_after_statements,
54 clippy::needless_collect,
55 clippy::single_match_else,
56 clippy::branches_sharing_code
57)]
58mod tests {
59 use super::{
60 Connection, ConnectionEnv, FileIdentity, IoPollStrategy, RuntimeConfig, RuntimeContext,
61 init_global_runtime,
62 };
63 use fsqlite_ast::{CreateTableBody, Statement};
64 use fsqlite_error::FrankenError;
65 use fsqlite_parser::parse_first_statement_with_tail;
66 use fsqlite_types::value::SqliteValue;
67 use std::io::{BufRead, BufReader};
68 use std::process::{Command, Stdio};
69 use std::sync::{Arc, mpsc};
70 use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
71
72 const CONCURRENT_STRESS_CHILD_ENV: &str = "FSQLITE_CONCURRENT_STRESS_CHILD";
73 const CONCURRENT_STRESS_RECEIPT_ENV: &str = "FSQLITE_CONCURRENT_STRESS_RECEIPT";
74 const CONCURRENT_STRESS_RECEIPT_PREFIX: &str = "FSQLITE_CONCURRENT_STRESS_COMPLETE:";
75 const CONCURRENT_STRESS_TEST_NAME: &str = "tests::concurrent_writers_stress_conservation";
76 const CONCURRENT_STRESS_CHILD_TIMEOUT: Duration = Duration::from_secs(90);
77 const CONCURRENT_STRESS_STARTUP_TIMEOUT: Duration = Duration::from_secs(10);
78 const CONCURRENT_STRESS_WORKER_TIMEOUT: Duration = Duration::from_secs(60);
79 const CONCURRENT_STRESS_MAX_ATTEMPTS_PER_COMMIT: u64 = 512;
80 const CONCURRENT_STRESS_MAX_ATTEMPTS_PER_WORKER: u64 = 2_560;
81 const CONCURRENT_READER_MAX_OPEN_ATTEMPTS: u64 = 4;
82
83 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
84 enum ConcurrentStressStartDecision {
85 Run,
86 Abort,
87 }
88
89 #[derive(Debug)]
90 enum ConcurrentStressStartup {
91 Ready { worker_id: usize },
92 Failed { worker_id: usize, error: String },
93 }
94
95 struct ConcurrentStressStartGate {
96 senders: Vec<mpsc::SyncSender<ConcurrentStressStartDecision>>,
97 armed: bool,
98 }
99
100 impl ConcurrentStressStartGate {
101 fn new(senders: Vec<mpsc::SyncSender<ConcurrentStressStartDecision>>) -> Self {
102 Self {
103 senders,
104 armed: true,
105 }
106 }
107
108 fn release(mut self) -> Result<(), String> {
109 let mut failures = Vec::new();
110 for (worker_id, sender) in self.senders.iter().enumerate() {
111 if sender.send(ConcurrentStressStartDecision::Run).is_err() {
112 failures.push(worker_id);
113 }
114 }
115 if failures.is_empty() {
116 self.armed = false;
117 Ok(())
118 } else {
119 Err(format!(
120 "workers disconnected before the run decision: {failures:?}"
121 ))
122 }
123 }
124 }
125
126 impl Drop for ConcurrentStressStartGate {
127 fn drop(&mut self) {
128 if !self.armed {
129 return;
130 }
131 for sender in &self.senders {
132 let _ = sender.try_send(ConcurrentStressStartDecision::Abort);
133 }
134 }
135 }
136
137 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
138 struct ConcurrentStressRetryCounts {
139 busy: u64,
140 busy_recovery: u64,
141 busy_snapshot: u64,
142 database_locked: u64,
143 write_conflict: u64,
144 serialization_failure: u64,
145 }
146
147 impl ConcurrentStressRetryCounts {
148 fn record(&mut self, error: &FrankenError) -> bool {
149 match error {
150 FrankenError::Busy => self.busy += 1,
151 FrankenError::BusyRecovery => self.busy_recovery += 1,
152 FrankenError::BusySnapshot { .. } => self.busy_snapshot += 1,
153 FrankenError::DatabaseLocked { .. } => self.database_locked += 1,
154 FrankenError::WriteConflict { .. } => self.write_conflict += 1,
155 FrankenError::SerializationFailure { .. } => self.serialization_failure += 1,
156 _ => return false,
157 }
158 true
159 }
160
161 const fn total(self) -> u64 {
162 self.busy
163 + self.busy_recovery
164 + self.busy_snapshot
165 + self.database_locked
166 + self.write_conflict
167 + self.serialization_failure
168 }
169 }
170
171 #[derive(Debug)]
172 struct ConcurrentStressTransfer {
173 from_id: i64,
174 to_id: i64,
175 amount: i64,
176 begin_seq: u64,
177 commit_seq: u64,
178 }
179
180 #[derive(Debug)]
181 struct ConcurrentStressStockDiagnostic {
182 row_count: i64,
183 balance_sum: i64,
184 point_count: i64,
185 scan_count: i64,
186 integrity: Vec<String>,
187 balances: Vec<(i64, i64)>,
188 }
189
190 #[derive(Debug)]
191 struct ConcurrentStressWorkerOutcome {
192 worker_id: usize,
193 concurrent_mode_default: bool,
194 commits: u64,
195 attempts: u64,
196 max_attempts_for_commit: u64,
197 retries: ConcurrentStressRetryCounts,
198 elapsed: Duration,
199 failure: Option<String>,
200 committed_transfers: Vec<ConcurrentStressTransfer>,
201 }
202
203 impl ConcurrentStressWorkerOutcome {
204 fn pending(worker_id: usize) -> Self {
205 Self {
206 worker_id,
207 concurrent_mode_default: false,
208 commits: 0,
209 attempts: 0,
210 max_attempts_for_commit: 0,
211 retries: ConcurrentStressRetryCounts::default(),
212 elapsed: Duration::ZERO,
213 failure: Some("worker exited without recording an outcome".to_owned()),
214 committed_transfers: Vec::new(),
215 }
216 }
217 }
218
219 fn supervise_concurrent_writer_stress() -> bool {
220 match (
221 std::env::var_os(CONCURRENT_STRESS_CHILD_ENV),
222 std::env::var_os(CONCURRENT_STRESS_RECEIPT_ENV),
223 ) {
224 (Some(child_token), Some(receipt_token)) if child_token == receipt_token => {
225 return false;
226 }
227 (None, None) => {}
228 _ => panic!("inconsistent inherited concurrent-stress supervision environment"),
229 }
230
231 let receipt_token = format!(
232 "{}-{}",
233 std::process::id(),
234 SystemTime::now()
235 .duration_since(UNIX_EPOCH)
236 .expect("system clock must be after the Unix epoch")
237 .as_nanos()
238 );
239 let expected_receipt = format!("{CONCURRENT_STRESS_RECEIPT_PREFIX}{receipt_token}");
240 let mut child =
241 Command::new(std::env::current_exe().expect("resolve current fsqlite test executable"))
242 .args([
243 "--exact",
244 CONCURRENT_STRESS_TEST_NAME,
245 "--include-ignored",
246 "--nocapture",
247 ])
248 .env(CONCURRENT_STRESS_CHILD_ENV, &receipt_token)
249 .env(CONCURRENT_STRESS_RECEIPT_ENV, &receipt_token)
250 .stdout(Stdio::piped())
251 .spawn()
252 .expect("spawn supervised concurrent-writer stress child");
253 let child_stdout = child
254 .stdout
255 .take()
256 .expect("capture concurrent-writer stress child stdout");
257 let mut receipt_reader = Some(std::thread::spawn(move || {
258 let mut receipt_found = false;
259 for line in BufReader::new(child_stdout).lines() {
260 if line.expect("read concurrent-writer stress child stdout") == expected_receipt {
261 receipt_found = true;
262 }
263 }
264 receipt_found
265 }));
266 let deadline = Instant::now() + CONCURRENT_STRESS_CHILD_TIMEOUT;
267
268 loop {
269 match child
270 .try_wait()
271 .expect("poll supervised concurrent-writer stress child")
272 {
273 Some(status) => {
274 let receipt_found = receipt_reader
275 .take()
276 .expect("receipt reader must be present")
277 .join()
278 .expect("concurrent-writer stress receipt reader must not panic");
279 assert!(
280 status.success(),
281 "supervised concurrent-writer stress child failed with {status}"
282 );
283 assert!(
284 receipt_found,
285 "supervised concurrent-writer stress child exited without its completion receipt"
286 );
287 return true;
288 }
289 None if Instant::now() >= deadline => {
290 let _ = child.kill();
291 child
292 .wait()
293 .expect("reap timed-out concurrent-writer stress child");
294 receipt_reader
295 .take()
296 .expect("receipt reader must be present")
297 .join()
298 .expect("concurrent-writer stress receipt reader must not panic");
299 panic!(
300 "supervised concurrent-writer stress test exceeded {:?}",
301 CONCURRENT_STRESS_CHILD_TIMEOUT
302 );
303 }
304 None => std::thread::sleep(Duration::from_millis(50)),
305 }
306 }
307 }
308
309 fn concurrent_stress_attempt_budget_error(
310 attempts_for_commit: u64,
311 total_attempts: u64,
312 elapsed: Duration,
313 ) -> Option<String> {
314 if attempts_for_commit >= CONCURRENT_STRESS_MAX_ATTEMPTS_PER_COMMIT {
315 Some(format!(
316 "exhausted {CONCURRENT_STRESS_MAX_ATTEMPTS_PER_COMMIT} attempts for one commit"
317 ))
318 } else if total_attempts >= CONCURRENT_STRESS_MAX_ATTEMPTS_PER_WORKER {
319 Some(format!(
320 "exhausted {CONCURRENT_STRESS_MAX_ATTEMPTS_PER_WORKER} total attempts"
321 ))
322 } else if elapsed >= CONCURRENT_STRESS_WORKER_TIMEOUT {
323 Some(format!(
324 "exceeded worker deadline {:?}",
325 CONCURRENT_STRESS_WORKER_TIMEOUT
326 ))
327 } else {
328 None
329 }
330 }
331
332 fn concurrent_stress_backoff(attempts_for_commit: u64, participant_id: u64) {
333 let exponent = attempts_for_commit.saturating_sub(1).min(5) as u32;
334 let base_millis = 1_u64 << exponent;
335 let jitter_millis = participant_id
336 .wrapping_mul(11)
337 .wrapping_add(attempts_for_commit.wrapping_mul(7))
338 % (base_millis + 1);
339 std::thread::sleep(Duration::from_millis(base_millis + jitter_millis));
340 }
341
342 async fn concurrent_stress_rollback_precommit_transient(
343 conn: &Connection,
344 outcome: &mut ConcurrentStressWorkerOutcome,
345 phase: &str,
346 primary_error: &FrankenError,
347 ) -> Result<(), String> {
348 if !outcome.retries.record(primary_error) {
349 return Err(format!("unexpected {phase} error: {primary_error:?}"));
350 }
351
352 if let Err(rollback_error) = conn.execute("ROLLBACK;").await {
356 let _ = outcome.retries.record(&rollback_error);
357 if matches!(rollback_error, FrankenError::BusyRecovery) && !conn.in_transaction() {
365 return Ok(());
366 }
367 return Err(format!(
368 "ROLLBACK after retryable {phase} error {primary_error:?} failed: \
369 {rollback_error:?}"
370 ));
371 }
372
373 Ok(())
374 }
375
376 fn concurrent_stress_panic_message(payload: &(dyn std::any::Any + Send)) -> &str {
377 if let Some(message) = payload.downcast_ref::<String>() {
378 message
379 } else if let Some(message) = payload.downcast_ref::<&'static str>() {
380 message
381 } else {
382 "non-string panic payload"
383 }
384 }
385
386 fn row_values(row: &super::Row) -> Vec<SqliteValue> {
387 row.values().to_vec()
388 }
389
390 #[cfg(all(feature = "native", any(unix, windows)))]
391 fn native_suffixed_path(path: &std::path::Path, suffix: &str) -> std::path::PathBuf {
392 let mut suffixed = path.as_os_str().to_owned();
393 suffixed.push(suffix);
394 std::path::PathBuf::from(suffixed)
395 }
396
397 #[cfg(all(feature = "native", any(unix, windows)))]
398 fn native_database_artifacts(path: &std::path::Path) -> [std::path::PathBuf; 8] {
399 [
400 path.to_owned(),
401 native_suffixed_path(path, "-journal"),
402 native_suffixed_path(path, "-wal"),
403 native_suffixed_path(path, "-wal-fec"),
404 native_suffixed_path(path, "-shm"),
405 native_suffixed_path(path, "-lock-shared"),
406 native_suffixed_path(path, "-lock-reserved"),
407 native_suffixed_path(path, "-lock-pending"),
408 ]
409 }
410
411 #[cfg(all(feature = "native", any(unix, windows)))]
412 fn snapshot_native_artifacts(paths: &[std::path::PathBuf]) -> Vec<Option<Vec<u8>>> {
413 paths
414 .iter()
415 .map(|path| match std::fs::read(path) {
416 Ok(bytes) => Some(bytes),
417 Err(error) if error.kind() == std::io::ErrorKind::NotFound => None,
418 Err(error) => panic!("snapshot {}: {error}", path.display()),
419 })
420 .collect()
421 }
422
423 #[cfg(all(feature = "native", windows))]
424 fn seed_windows_database(path: &std::path::Path) {
425 let seed = rusqlite::Connection::open(path).expect("create valid SQLite database");
426 seed.execute_batch(
427 "PRAGMA journal_mode = DELETE;
428 CREATE TABLE identity_probe(value INTEGER NOT NULL);
429 INSERT INTO identity_probe VALUES (1);",
430 )
431 .expect("seed valid SQLite database");
432 }
433
434 #[cfg(all(feature = "native", windows))]
435 fn windows_file_identity(path: &std::path::Path) -> FileIdentity {
436 let file = std::fs::File::open(path).expect("open Windows identity handle");
437 FileIdentity::from_file(&file)
438 .expect("query Windows identity handle")
439 .expect("Windows file identity must be available")
440 }
441
442 #[cfg(all(feature = "native", windows))]
443 fn seed_windows_auxiliary_sentinels(artifacts: &[std::path::PathBuf; 8]) {
444 std::fs::write(&artifacts[1], b"journal sentinel").expect("seed journal sentinel");
445 std::fs::write(&artifacts[2], b"WAL sentinel").expect("seed WAL sentinel");
446 std::fs::write(&artifacts[3], b"WAL-FEC sentinel").expect("seed WAL-FEC sentinel");
447 std::fs::write(&artifacts[4], b"SHM sentinel").expect("seed SHM sentinel");
448 }
449
450 #[test]
451 fn test_connection_open_and_path() {
452 asupersync::test_utils::run_test(|| async {
453 let conn = Connection::open(":memory:")
454 .await
455 .expect("in-memory connection should open");
456 assert_eq!(conn.path(), ":memory:");
457 });
458 }
459
460 #[test]
461 fn in_memory_connection_has_no_filesystem_identity() {
462 asupersync::test_utils::run_test(|| async {
463 let conn = Connection::open(":memory:")
464 .await
465 .expect("in-memory connection should open");
466 assert_eq!(conn.file_identity().await.unwrap(), None);
467 });
468 }
469
470 #[cfg(all(feature = "native", any(unix, windows)))]
471 #[test]
472 fn namespace_lifetime_connections_to_the_same_database_identity_coexist() {
473 asupersync::test_utils::run_test(|| async {
474 let dir = tempfile::tempdir().expect("create temp dir");
475 let database_path = dir.path().join("shared-generation.db");
476 let database_path = database_path.to_string_lossy().into_owned();
477 let first = Connection::open(database_path.clone())
478 .await
479 .expect("open first connection");
480 let second = Connection::open_existing(database_path)
481 .await
482 .expect("join the initialized live database generation");
483 first
484 .execute("PRAGMA fsqlite.stmt_microbatch = OFF;")
485 .await
486 .expect("disable statement carry on first connection");
487 second
488 .execute("PRAGMA fsqlite.stmt_microbatch = OFF;")
489 .await
490 .expect("disable statement carry on peer connection");
491 first
492 .execute_batch(
493 "CREATE TABLE shared_generation(value INTEGER NOT NULL);
494 INSERT INTO shared_generation VALUES (1);",
495 )
496 .await
497 .expect("seed the shared generation");
498
499 let first_identity = first
500 .file_identity()
501 .await
502 .expect("query first connection identity")
503 .expect("native database has a stable identity");
504 assert_eq!(
505 second.file_identity().await.expect("query peer identity"),
506 Some(first_identity),
507 "both connections must remain leased to the same database object"
508 );
509
510 second
511 .execute("INSERT INTO shared_generation VALUES (2);")
512 .await
513 .expect("peer connection writes through the shared generation");
514 let rows = first
515 .query("SELECT value FROM shared_generation ORDER BY value;")
516 .await
517 .expect("first connection observes the peer commit");
518 assert_eq!(
519 rows.iter().map(row_values).collect::<Vec<_>>(),
520 vec![vec![SqliteValue::Integer(1)], vec![SqliteValue::Integer(2)]]
521 );
522 });
523 }
524
525 #[cfg(all(feature = "native", any(unix, windows)))]
526 #[test]
527 fn namespace_generation_transition_reopens_quarantined_replacement() {
528 asupersync::test_utils::run_test(|| async {
529 let dir = tempfile::tempdir().expect("create temp dir");
530 let database_path = dir.path().join("recover.db");
531 let quarantine_path = dir.path().join("recover.db.quarantined");
532 let replacement_stage = dir.path().join("recover.db.replacement");
533 let database_path_string = database_path.to_string_lossy().into_owned();
534
535 {
536 let generation_a =
537 rusqlite::Connection::open(&database_path).expect("create generation A");
538 generation_a
539 .execute_batch(
540 "PRAGMA journal_mode = DELETE;
541 CREATE TABLE generation(value INTEGER NOT NULL);
542 INSERT INTO generation VALUES (1);",
543 )
544 .expect("seed generation A");
545 }
546 let generation_a = Connection::open_existing(database_path_string.clone())
547 .await
548 .expect("open generation A through the public facade");
549 let old_identity = generation_a
550 .file_identity()
551 .await
552 .expect("query generation A identity")
553 .expect("native database identity");
554 let rows = generation_a
555 .query("SELECT value FROM generation;")
556 .await
557 .expect("query generation A");
558 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(1)]);
559 drop(generation_a);
560
561 {
562 let generation_b =
563 rusqlite::Connection::open(&replacement_stage).expect("create generation B");
564 generation_b
565 .execute_batch(
566 "PRAGMA journal_mode = DELETE;
567 CREATE TABLE generation(value INTEGER NOT NULL);
568 INSERT INTO generation VALUES (2);",
569 )
570 .expect("seed generation B");
571 }
572 let replacement_file = std::fs::File::open(&replacement_stage)
573 .expect("retain generation B identity handle");
574 let replacement_identity = FileIdentity::from_file(&replacement_file)
575 .expect("query generation B identity")
576 .expect("native database identity");
577 assert_ne!(old_identity, replacement_identity);
578
579 let mut transition =
580 super::begin_database_namespace_generation_transition(&database_path, old_identity)
581 .expect("guard generation A before pathname mutation");
582 std::fs::rename(&database_path, &quarantine_path)
583 .expect("quarantine fully quiescent generation A under guard");
584 std::fs::rename(&replacement_stage, &database_path)
585 .expect("install generation B at the stable pathname");
586
587 assert_eq!(
588 transition
589 .publish_replacement(replacement_identity)
590 .expect("publish generation B"),
591 super::NamespaceGenerationTransitionOutcome::Published
592 );
593 assert_eq!(
594 transition.finish().expect("finish generation B transition"),
595 replacement_identity
596 );
597 let generation_b = Connection::open_existing_with_expected_identity(
598 database_path_string,
599 replacement_identity,
600 )
601 .await
602 .expect("reopen generation B through the public facade");
603 let rows = generation_b
604 .query("SELECT value FROM generation;")
605 .await
606 .expect("query generation B");
607 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(2)]);
608 drop(generation_b);
609
610 super::begin_database_namespace_generation_transition(
611 &database_path,
612 replacement_identity,
613 )
614 .expect("reacquire exact published generation")
615 .finish()
616 .expect("finish no-op exact reacquisition");
617 });
618 }
619
620 #[cfg(all(feature = "native", any(unix, windows)))]
621 #[test]
622 fn reserved_identity_open_never_synthesizes_a_missing_path() {
623 asupersync::test_utils::run_test(|| async {
624 let dir = tempfile::tempdir().expect("create temp dir");
625 let identity_path = dir.path().join("existing-identity.db");
626 let missing_path = dir.path().join("missing-reservation.db");
627 drop(std::fs::File::create(&identity_path).expect("create identity source"));
628 let identity_file = std::fs::File::open(&identity_path).expect("open identity source");
629 let expected_identity = FileIdentity::from_file(&identity_file)
630 .expect("query filesystem identity")
631 .expect("native filesystem identity must be available");
632
633 let artifacts = [
634 missing_path.clone(),
635 native_suffixed_path(&missing_path, "-journal"),
636 native_suffixed_path(&missing_path, "-wal"),
637 native_suffixed_path(&missing_path, "-wal-fec"),
638 native_suffixed_path(&missing_path, "-shm"),
639 native_suffixed_path(&missing_path, "-lock-shared"),
640 native_suffixed_path(&missing_path, "-lock-reserved"),
641 native_suffixed_path(&missing_path, "-lock-pending"),
642 ];
643 assert!(artifacts.iter().all(|path| !path.exists()));
644
645 let error = Connection::open_reserved_with_expected_identity(
646 missing_path.to_string_lossy().into_owned(),
647 expected_identity,
648 )
649 .await
650 .expect_err("missing reservation must not be created");
651
652 assert!(matches!(error, FrankenError::CannotOpen { .. }));
653 assert!(
654 artifacts.iter().all(|path| !path.exists()),
655 "identity-bound reserved open must leave the missing main path and every sidecar absent"
656 );
657 });
658 }
659
660 #[cfg(all(feature = "native", any(unix, windows)))]
661 #[test]
662 fn reserved_identity_open_refuses_a_preexisting_recovery_artifact_without_mutation() {
663 asupersync::test_utils::run_test(|| async {
664 let dir = tempfile::tempdir().expect("create temp dir");
665 let database_path = dir.path().join("reserved-empty.db");
666 let artifacts = native_database_artifacts(&database_path);
667 drop(std::fs::File::create(&database_path).expect("reserve empty database path"));
668 let reservation = std::fs::File::open(&database_path).expect("open reservation handle");
669 let expected_identity = FileIdentity::from_file(&reservation)
670 .expect("query reservation identity")
671 .expect("native filesystem identity must be available");
672 std::fs::write(&artifacts[1], b"reserved journal sentinel")
673 .expect("seed recovery artifact");
674 let before = snapshot_native_artifacts(&artifacts);
675 assert!(
676 before[2..].iter().all(Option::is_none),
677 "every unseeded recovery and advisory-lock sidecar must start absent"
678 );
679
680 let error = Connection::open_reserved_with_expected_identity(
681 database_path.to_string_lossy().into_owned(),
682 expected_identity,
683 )
684 .await
685 .expect_err("a reserved-empty open must refuse a pre-existing recovery artifact");
686
687 assert!(matches!(error, FrankenError::CannotOpen { .. }));
688 assert_eq!(
689 snapshot_native_artifacts(&artifacts),
690 before,
691 "refusal must leave main, recovery artifacts, and advisory-lock sidecars unchanged"
692 );
693 });
694 }
695
696 #[cfg(all(feature = "native", unix))]
697 #[test]
698 fn reserved_identity_open_refuses_dangling_recovery_artifact_symlinks() {
699 asupersync::test_utils::run_test(|| async {
700 use std::os::unix::fs::symlink;
701
702 let dir = tempfile::tempdir().expect("create temp dir");
703 for (index, suffix) in ["-journal", "-wal", "-wal-fec", "-shm"]
704 .into_iter()
705 .enumerate()
706 {
707 let database_path = dir.path().join(format!("reserved-dangling-{index}.db"));
708 drop(std::fs::File::create(&database_path).expect("reserve empty database path"));
709 let reservation =
710 std::fs::File::open(&database_path).expect("open reservation handle");
711 let expected_identity = FileIdentity::from_file(&reservation)
712 .expect("query reservation identity")
713 .expect("Unix filesystem identity must be available");
714 let dangling_target = dir.path().join(format!("missing-target-{index}"));
715 let artifact_path = native_suffixed_path(&database_path, suffix);
716 symlink(&dangling_target, &artifact_path).expect("seed dangling artifact symlink");
717
718 let error = Connection::open_reserved_with_expected_identity(
719 database_path.to_string_lossy().into_owned(),
720 expected_identity,
721 )
722 .await
723 .expect_err("a dangling recovery-artifact symlink must refuse initialization");
724
725 assert!(matches!(error, FrankenError::CannotOpen { .. }));
726 assert_eq!(
727 std::fs::metadata(&database_path).unwrap().len(),
728 0,
729 "refusal must leave the reserved main file empty"
730 );
731 assert!(
732 std::fs::symlink_metadata(&artifact_path)
733 .expect("refusal must preserve the dangling artifact")
734 .file_type()
735 .is_symlink(),
736 "refusal must preserve the {suffix} symlink itself"
737 );
738 assert!(
739 !dangling_target.exists(),
740 "refusal must not create the dangling symlink target"
741 );
742 }
743 });
744 }
745
746 #[cfg(all(feature = "native", any(unix, windows)))]
747 #[test]
748 fn reserved_identity_open_refuses_a_nonempty_file_without_artifact_mutation() {
749 asupersync::test_utils::run_test(|| async {
750 let dir = tempfile::tempdir().expect("create temp dir");
751 let database_path = dir.path().join("reserved-nonempty.db");
752 std::fs::write(&database_path, b"nonempty reservation sentinel")
753 .expect("seed nonempty reserved file");
754 let reservation = std::fs::File::open(&database_path).expect("open reservation handle");
755 let expected_identity = FileIdentity::from_file(&reservation)
756 .expect("query reservation identity")
757 .expect("native filesystem identity must be available");
758 let artifacts = native_database_artifacts(&database_path);
759 let before = snapshot_native_artifacts(&artifacts);
760 assert!(
761 before[1..].iter().all(Option::is_none),
762 "every database sidecar must start absent"
763 );
764
765 let error = Connection::open_reserved_with_expected_identity(
766 database_path.to_string_lossy().into_owned(),
767 expected_identity,
768 )
769 .await
770 .expect_err("a reserved-empty open must refuse a nonempty file");
771
772 assert!(matches!(error, FrankenError::CannotOpen { .. }));
773 assert_eq!(
774 snapshot_native_artifacts(&artifacts),
775 before,
776 "nonempty refusal must leave the main file and every sidecar unchanged"
777 );
778 });
779 }
780
781 #[cfg(all(feature = "native", any(unix, windows)))]
782 #[test]
783 fn namespace_lifetime_reserved_open_refuses_wal_segment_and_fec_rewrite_artifacts() {
784 asupersync::test_utils::run_test(|| async {
785 let dir = tempfile::tempdir().expect("create temp dir");
786
787 for artifact_kind in ["wal-segment", "wal-fec-rewrite"] {
788 let database_path = dir.path().join(format!("reserved-{artifact_kind}.db"));
789 drop(std::fs::File::create(&database_path).expect("reserve empty database path"));
790 let reservation =
791 std::fs::File::open(&database_path).expect("retain reservation handle");
792 let expected_identity = FileIdentity::from_file(&reservation)
793 .expect("query reservation identity")
794 .expect("native filesystem identity must be available");
795 let artifact_path = match artifact_kind {
796 "wal-segment" => dir.path().join("reserved-wal-segment.db-wal-seg-00000001"),
797 "wal-fec-rewrite" => native_suffixed_path(&database_path, "-wal-fec")
798 .with_extension("wal-fec.tmp"),
799 _ => unreachable!("artifact cases are exhaustive"),
800 };
801 let sentinel = format!("{artifact_kind} sentinel").into_bytes();
802 std::fs::write(&artifact_path, &sentinel).expect("seed forbidden artifact");
803
804 let error = Connection::open_reserved_with_expected_identity(
805 database_path.to_string_lossy().into_owned(),
806 expected_identity,
807 )
808 .await
809 .expect_err("reserved bootstrap must reject every pre-existing WAL artifact");
810
811 assert!(matches!(error, FrankenError::CannotOpen { .. }));
812 assert_eq!(
813 std::fs::metadata(&database_path).unwrap().len(),
814 0,
815 "refusal must leave the reserved main file empty"
816 );
817 assert_eq!(
818 std::fs::read(&artifact_path).expect("read preserved forbidden artifact"),
819 sentinel,
820 "refusal must not mutate the forbidden artifact"
821 );
822 }
823 });
824 }
825
826 #[cfg(all(feature = "native", unix))]
827 #[test]
828 fn connection_identity_remains_bound_to_open_file_after_path_swap() {
829 asupersync::test_utils::run_test(|| async {
830 use std::fs::File;
831
832 let dir = tempfile::tempdir().expect("create temp dir");
833 let database_path = dir.path().join("identity.db");
834 let displaced_path = dir.path().join("identity.opened.db");
835 let conn = Connection::open(database_path.to_string_lossy().into_owned())
836 .await
837 .expect("open file-backed connection");
838
839 let leased_file = File::open(&database_path).expect("lease opened database descriptor");
840 let leased_identity = FileIdentity::from_file(&leased_file)
841 .expect("read leased descriptor identity")
842 .expect("Unix descriptors have stable identities");
843 let connection_identity = conn
844 .file_identity()
845 .await
846 .expect("read connection identity")
847 .expect("Unix VFS exposes an open-file identity");
848 assert_eq!(connection_identity, leased_identity);
849
850 std::fs::rename(&database_path, &displaced_path)
851 .expect("displace opened database path");
852 drop(File::create(&database_path).expect("create replacement path"));
853 let replacement_file =
854 File::open(&database_path).expect("lease replacement descriptor");
855 let replacement_identity = FileIdentity::from_file(&replacement_file)
856 .expect("read replacement descriptor identity")
857 .expect("Unix descriptors have stable identities");
858
859 assert_ne!(connection_identity, replacement_identity);
860 assert_eq!(conn.file_identity().await.unwrap(), Some(leased_identity));
861 drop(conn);
862 });
863 }
864
865 #[cfg(all(feature = "native", unix))]
866 async fn exercise_live_namespace_replacement_rejection(journal_mode: &str) {
867 let dir = tempfile::tempdir().expect("create temp dir");
868 let database_path = dir.path().join(format!("live-{journal_mode}.db"));
869 let displaced_path = dir.path().join(format!("live-{journal_mode}.displaced.db"));
870 let replacement_stage = dir
871 .path()
872 .join(format!("live-{journal_mode}.replacement.db"));
873 let database_path_string = database_path.to_string_lossy().into_owned();
874
875 let live = Connection::open(database_path_string.clone())
876 .await
877 .expect("open live generation");
878 live.execute("PRAGMA fsqlite.stmt_microbatch = OFF;")
879 .await
880 .expect("disable retained statement carry for namespace boundary proof");
881 live.execute(&format!("PRAGMA journal_mode = '{journal_mode}';"))
882 .await
883 .expect("select requested journal mode");
884 live.execute_batch(
885 "CREATE TABLE identity_probe(value INTEGER NOT NULL);
886 INSERT INTO identity_probe VALUES (1);",
887 )
888 .await
889 .expect("seed live generation");
890 let live_identity = live
891 .file_identity()
892 .await
893 .expect("query live identity")
894 .expect("Unix database has a stable identity");
895
896 {
897 let replacement = rusqlite::Connection::open(&replacement_stage)
898 .expect("create valid replacement database");
899 replacement
900 .execute_batch(
901 "PRAGMA journal_mode = DELETE;
902 CREATE TABLE identity_probe(value INTEGER NOT NULL);
903 INSERT INTO identity_probe VALUES (9001);",
904 )
905 .expect("seed replacement database");
906 }
907 let replacement_staged_bytes =
908 std::fs::read(&replacement_stage).expect("snapshot staged replacement");
909
910 std::fs::rename(&database_path, &displaced_path).expect("displace live main file");
911 std::fs::rename(&replacement_stage, &database_path)
912 .expect("install replacement at the live pathname");
913 let replacement_file =
914 std::fs::File::open(&database_path).expect("open replacement identity handle");
915 let replacement_identity = FileIdentity::from_file(&replacement_file)
916 .expect("query replacement identity")
917 .expect("Unix database has a stable identity");
918 assert_ne!(live_identity, replacement_identity);
919
920 let write_error = live
921 .execute("INSERT INTO identity_probe VALUES (2);")
922 .await
923 .expect_err("live connection must reject a replaced main pathname");
924 assert!(matches!(write_error, FrankenError::CannotOpen { .. }));
925 assert_eq!(
926 std::fs::read(&database_path).expect("read rejected replacement"),
927 replacement_staged_bytes,
928 "rejection must precede every write to the replacement database object"
929 );
930
931 let join_error = Connection::open(database_path_string.clone())
932 .await
933 .expect_err("a peer must not join the replacement while the old generation is live");
934 assert!(matches!(join_error, FrankenError::CannotOpen { .. }));
935 assert!(matches!(
936 super::begin_database_namespace_generation_transition(&database_path, live_identity),
937 Err(FrankenError::Busy)
938 ));
939 assert_eq!(
940 std::fs::read(&database_path).expect("read replacement after rejected join"),
941 replacement_staged_bytes,
942 "rejected admission must not mutate the replacement"
943 );
944
945 drop(live);
946 std::fs::rename(&database_path, &replacement_stage)
947 .expect("restage replacement before acquiring transition guard");
948 std::fs::rename(&displaced_path, &database_path)
949 .expect("restore live generation before acquiring transition guard");
950 let mut transition =
951 super::begin_database_namespace_generation_transition(&database_path, live_identity)
952 .expect("guard the quiescent old generation");
953 std::fs::rename(&database_path, &displaced_path)
954 .expect("quarantine old generation under guard");
955 std::fs::rename(&replacement_stage, &database_path)
956 .expect("activate replacement under guard");
957 assert_eq!(
958 transition
959 .publish_replacement(replacement_identity)
960 .expect("publish the quiescent replacement generation"),
961 super::NamespaceGenerationTransitionOutcome::Published
962 );
963 transition.finish().expect("finish replacement transition");
964 let replacement = Connection::open_existing(database_path_string)
965 .await
966 .expect("replacement becomes a new generation after the old lease drops");
967 assert_eq!(
968 replacement.file_identity().await.unwrap(),
969 Some(replacement_identity)
970 );
971 let rows = replacement
972 .query("SELECT value FROM identity_probe;")
973 .await
974 .expect("query the replacement generation");
975 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(9001)]);
976 replacement
977 .execute("INSERT INTO identity_probe VALUES (9002);")
978 .await
979 .expect("new generation is writable");
980 }
981
982 #[cfg(all(feature = "native", unix))]
983 #[test]
984 fn namespace_lifetime_delete_mode_replacement_is_rejected() {
985 asupersync::test_utils::run_test(|| async {
986 exercise_live_namespace_replacement_rejection("DELETE").await;
987 });
988 }
989
990 #[cfg(all(feature = "native", unix))]
991 #[test]
992 fn namespace_lifetime_wal_mode_replacement_is_rejected() {
993 asupersync::test_utils::run_test(|| async {
994 exercise_live_namespace_replacement_rejection("WAL").await;
995 });
996 }
997
998 #[cfg(all(feature = "native", any(unix, windows)))]
999 #[test]
1000 fn namespace_lifetime_relative_path_remains_anchored_after_cwd_change() {
1001 asupersync::test_utils::run_test(|| async {
1002 const CHILD_ROOT: &str = "FSQLITE_NAMESPACE_CWD_CHILD_ROOT";
1003
1004 if let Some(root) = std::env::var_os(CHILD_ROOT) {
1005 let root = std::path::PathBuf::from(root);
1006 let original_dir = root.join("original-cwd");
1007 let later_dir = root.join("later-cwd");
1008 std::env::set_current_dir(&original_dir).expect("enter original cwd");
1009
1010 let conn = Connection::open("anchored.db")
1011 .await
1012 .expect("open relative database path");
1013 let expected_path = original_dir.join("anchored.db");
1014 let expected_canonical_path = expected_path
1015 .canonicalize()
1016 .expect("canonicalize newly opened relative database path");
1017 assert_eq!(std::path::Path::new(conn.path()), expected_canonical_path);
1018 conn.execute_batch(
1019 "CREATE TABLE cwd_probe(value INTEGER NOT NULL);
1020 INSERT INTO cwd_probe VALUES (1);",
1021 )
1022 .await
1023 .expect("seed database from original cwd");
1024
1025 std::env::set_current_dir(&later_dir).expect("change process cwd");
1026 conn.execute("INSERT INTO cwd_probe VALUES (2);")
1027 .await
1028 .expect("write remains bound to original absolute path");
1029 drop(conn);
1030
1031 assert!(expected_path.exists());
1032 assert!(
1033 !later_dir.join("anchored.db").exists(),
1034 "no operation may re-resolve the configured relative path against the new cwd"
1035 );
1036 let verification =
1037 rusqlite::Connection::open(expected_path).expect("open anchored database");
1038 let values = verification
1039 .prepare("SELECT value FROM cwd_probe ORDER BY value")
1040 .expect("prepare anchored verification")
1041 .query_map([], |row| row.get::<_, i64>(0))
1042 .expect("query anchored verification")
1043 .collect::<rusqlite::Result<Vec<_>>>()
1044 .expect("collect anchored verification");
1045 assert_eq!(values, vec![1, 2]);
1046 return;
1047 }
1048
1049 let dir = tempfile::tempdir().expect("create parent temp dir");
1050 std::fs::create_dir(dir.path().join("original-cwd")).expect("create original cwd");
1051 std::fs::create_dir(dir.path().join("later-cwd")).expect("create later cwd");
1052 let output = std::process::Command::new(
1053 std::env::current_exe().expect("locate current Rust test binary"),
1054 )
1055 .args([
1056 "--exact",
1057 "tests::namespace_lifetime_relative_path_remains_anchored_after_cwd_change",
1058 "--nocapture",
1059 ])
1060 .env(CHILD_ROOT, dir.path())
1061 .current_dir(dir.path())
1062 .output()
1063 .expect("run cwd-isolated child test");
1064 assert!(
1065 output.status.success(),
1066 "cwd-isolated child failed\nstdout:\n{}\nstderr:\n{}",
1067 String::from_utf8_lossy(&output.stdout),
1068 String::from_utf8_lossy(&output.stderr)
1069 );
1070 });
1071 }
1072
1073 #[cfg(all(feature = "native", unix))]
1074 #[test]
1075 fn expected_identity_refuses_swapped_hot_journal_without_mutation() {
1076 asupersync::test_utils::run_test(|| async {
1077 use fsqlite_pager::{JournalHeader, JournalPageRecord};
1078 use std::fs::File;
1079
1080 let dir = tempfile::tempdir().expect("create temp dir");
1081 let database_path = dir.path().join("identity-bound.db");
1082 let displaced_path = dir.path().join("identity-bound.leased.db");
1083 let journal_path = dir.path().join("identity-bound.db-journal");
1084
1085 {
1086 let leased_seed = rusqlite::Connection::open(&database_path)
1087 .expect("create identity-leased database");
1088 leased_seed
1089 .execute_batch(
1090 "PRAGMA journal_mode = DELETE;
1091 CREATE TABLE leased_marker(value INTEGER);
1092 INSERT INTO leased_marker VALUES (1);",
1093 )
1094 .expect("seed identity-leased database");
1095 }
1096 let leased_file =
1097 File::open(&database_path).expect("lease original database descriptor");
1098 let leased_identity = FileIdentity::from_file(&leased_file)
1099 .expect("read leased descriptor identity")
1100 .expect("Unix descriptors have stable identities");
1101 std::fs::rename(&database_path, &displaced_path)
1102 .expect("replace leased database pathname");
1103
1104 let (page_size, page_count) = {
1105 let replacement = rusqlite::Connection::open(&database_path)
1106 .expect("create replacement database");
1107 replacement
1108 .execute_batch(
1109 "PRAGMA page_size = 4096;
1110 PRAGMA journal_mode = DELETE;
1111 CREATE TABLE replacement_marker(value INTEGER);
1112 INSERT INTO replacement_marker VALUES (2);",
1113 )
1114 .expect("seed replacement database");
1115 let page_size: i64 = replacement
1116 .query_row("PRAGMA page_size", [], |row| row.get(0))
1117 .expect("read replacement page size");
1118 let page_count: i64 = replacement
1119 .query_row("PRAGMA page_count", [], |row| row.get(0))
1120 .expect("read replacement page count");
1121 (
1122 u32::try_from(page_size).expect("page size fits u32"),
1123 u32::try_from(page_count).expect("page count fits u32"),
1124 )
1125 };
1126 assert!(page_count >= 2, "replacement database must have page 2");
1127
1128 let replacement_file =
1129 File::open(&database_path).expect("open replacement database descriptor");
1130 let replacement_identity = FileIdentity::from_file(&replacement_file)
1131 .expect("read replacement descriptor identity")
1132 .expect("Unix descriptors have stable identities");
1133 assert_ne!(replacement_identity, leased_identity);
1134
1135 let page_size_usize = usize::try_from(page_size).expect("page size fits usize");
1136 let replacement_pristine =
1137 std::fs::read(&database_path).expect("read replacement database");
1138 let mut replacement_bytes = replacement_pristine.clone();
1139 assert!(replacement_bytes.len() >= page_size_usize * 2);
1140 let page_two_preimage =
1141 replacement_bytes[page_size_usize..page_size_usize * 2].to_vec();
1142 replacement_bytes[page_size_usize] ^= 0xff;
1143 std::fs::write(&database_path, &replacement_bytes)
1144 .expect("write simulated interrupted page update");
1145
1146 let nonce = 0x4653_514c;
1147 let journal_header = JournalHeader {
1148 page_count: 1,
1149 nonce,
1150 initial_db_size: page_count,
1151 sector_size: 512,
1152 page_size,
1153 };
1154 let mut journal_bytes = journal_header.encode_padded();
1155 journal_bytes.extend(JournalPageRecord::new(2, page_two_preimage, nonce).encode());
1156 std::fs::write(&journal_path, &journal_bytes).expect("write valid hot journal");
1157
1158 let database_before =
1159 std::fs::read(&database_path).expect("snapshot replacement bytes");
1160 let journal_before = std::fs::read(&journal_path).expect("snapshot hot journal bytes");
1161 let error = Connection::open_existing_with_expected_identity(
1162 database_path.to_string_lossy().into_owned(),
1163 leased_identity,
1164 )
1165 .await
1166 .expect_err("identity-bound open must reject the replacement database");
1167
1168 assert!(matches!(error, FrankenError::CannotOpen { .. }));
1169 assert_eq!(
1170 std::fs::read(&database_path).unwrap(),
1171 database_before,
1172 "identity refusal must precede hot-journal recovery writes"
1173 );
1174 assert_eq!(
1175 std::fs::read(&journal_path).unwrap(),
1176 journal_before,
1177 "identity refusal must not invalidate or delete the hot journal"
1178 );
1179
1180 let control_database_path = dir.path().join("identity-bound-control.db");
1181 let control_journal_path = dir.path().join("identity-bound-control.db-journal");
1182 std::fs::write(&control_database_path, &database_before)
1183 .expect("copy interrupted database into recovery control");
1184 std::fs::write(&control_journal_path, &journal_before)
1185 .expect("copy hot journal into recovery control");
1186
1187 let control =
1188 Connection::open_existing(control_database_path.to_string_lossy().into_owned())
1189 .await
1190 .expect("plain write-existing open must recover the control copy");
1191 drop(control);
1192
1193 let control_after =
1194 std::fs::read(&control_database_path).expect("read recovered control database");
1195 assert_ne!(
1196 control_after, database_before,
1197 "control recovery must prove the hot journal is not inert"
1198 );
1199 assert_eq!(
1200 &control_after[page_size_usize..page_size_usize * 2],
1201 &replacement_pristine[page_size_usize..page_size_usize * 2],
1202 "control recovery must restore the original page-two preimage"
1203 );
1204 assert!(
1205 !control_journal_path.exists()
1206 || std::fs::read(&control_journal_path).unwrap().is_empty(),
1207 "control recovery must consume or invalidate the hot journal"
1208 );
1209 });
1210 }
1211
1212 #[cfg(all(feature = "native", windows))]
1213 #[test]
1214 fn windows_expected_identity_refuses_before_any_database_artifact_mutation() {
1215 asupersync::test_utils::run_test(|| async {
1216 let dir = tempfile::tempdir().expect("create temp dir");
1217 let identity_path = dir.path().join("identity.db");
1218 let candidate_path = dir.path().join("candidate.db");
1219 for path in [&identity_path, &candidate_path] {
1220 seed_windows_database(path);
1221 }
1222
1223 let expected_identity = windows_file_identity(&identity_path);
1224 let candidate_identity = windows_file_identity(&candidate_path);
1225 assert_ne!(expected_identity, candidate_identity);
1226
1227 let artifacts = native_database_artifacts(&candidate_path);
1228 seed_windows_auxiliary_sentinels(&artifacts);
1229 let before = snapshot_native_artifacts(&artifacts);
1230 assert!(
1231 before[5..].iter().all(Option::is_none),
1232 "advisory lock sidecars must start absent"
1233 );
1234
1235 let error = Connection::open_existing_with_expected_identity(
1236 candidate_path.to_string_lossy().into_owned(),
1237 expected_identity,
1238 )
1239 .await
1240 .expect_err("wrong expected identity must refuse the candidate database");
1241
1242 assert!(matches!(error, FrankenError::CannotOpen { .. }));
1243 assert_eq!(
1244 snapshot_native_artifacts(&artifacts),
1245 before,
1246 "identity refusal must leave main, journal, WAL, SHM, and advisory sidecars unchanged"
1247 );
1248 });
1249 }
1250
1251 #[cfg(all(feature = "native", windows))]
1252 #[test]
1253 fn windows_schema_only_identity_mismatch_precedes_artifact_mutation() {
1254 asupersync::test_utils::run_test(|| async {
1255 let dir = tempfile::tempdir().expect("create temp dir");
1256 let identity_path = dir.path().join("schema-identity.db");
1257 let candidate_path = dir.path().join("schema-candidate.db");
1258 seed_windows_database(&identity_path);
1259 seed_windows_database(&candidate_path);
1260
1261 let expected_identity = windows_file_identity(&identity_path);
1262 assert_ne!(expected_identity, windows_file_identity(&candidate_path));
1263
1264 let artifacts = native_database_artifacts(&candidate_path);
1265 seed_windows_auxiliary_sentinels(&artifacts);
1266 let before = snapshot_native_artifacts(&artifacts);
1267 assert!(
1268 before[5..].iter().all(Option::is_none),
1269 "advisory lock sidecars must start absent"
1270 );
1271
1272 let error = Connection::open_schema_only_with_expected_identity(
1273 candidate_path.to_string_lossy().into_owned(),
1274 expected_identity,
1275 )
1276 .await
1277 .expect_err("wrong expected identity must refuse schema-only open");
1278
1279 assert!(matches!(error, FrankenError::CannotOpen { .. }));
1280 assert_eq!(
1281 snapshot_native_artifacts(&artifacts),
1282 before,
1283 "schema-only refusal must leave every database artifact unchanged"
1284 );
1285 });
1286 }
1287
1288 #[cfg(all(feature = "native", windows))]
1289 #[test]
1290 fn windows_existing_expected_identity_accepts_matching_file() {
1291 asupersync::test_utils::run_test(|| async {
1292 let dir = tempfile::tempdir().expect("create temp dir");
1293 let database_path = dir.path().join("existing-matching-identity.db");
1294 seed_windows_database(&database_path);
1295
1296 let leased_file =
1297 std::fs::File::open(&database_path).expect("retain existing database handle");
1298 let expected_identity = FileIdentity::from_file(&leased_file)
1299 .expect("query existing database identity")
1300 .expect("Windows file identity must be available");
1301 let conn = Connection::open_existing_with_expected_identity(
1302 database_path.to_string_lossy().into_owned(),
1303 expected_identity,
1304 )
1305 .await
1306 .expect("matching identity must open the existing database");
1307
1308 assert_eq!(conn.file_identity().await.unwrap(), Some(expected_identity));
1309 let rows = conn
1310 .query("SELECT COUNT(*) FROM identity_probe;")
1311 .await
1312 .expect("matching identity connection must query the seeded table");
1313 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(1)]);
1314 drop(conn);
1315 drop(leased_file);
1316 });
1317 }
1318
1319 #[cfg(all(feature = "native", windows))]
1320 #[test]
1321 fn windows_schema_only_expected_identity_accepts_matching_file() {
1322 asupersync::test_utils::run_test(|| async {
1323 let dir = tempfile::tempdir().expect("create temp dir");
1324 let database_path = dir.path().join("schema-matching-identity.db");
1325 seed_windows_database(&database_path);
1326
1327 let leased_file =
1328 std::fs::File::open(&database_path).expect("retain schema database handle");
1329 let expected_identity = FileIdentity::from_file(&leased_file)
1330 .expect("query schema database identity")
1331 .expect("Windows file identity must be available");
1332 let conn = Connection::open_schema_only_with_expected_identity(
1333 database_path.to_string_lossy().into_owned(),
1334 expected_identity,
1335 )
1336 .await
1337 .expect("matching identity must open the schema-only connection");
1338
1339 assert_eq!(conn.file_identity().await.unwrap(), Some(expected_identity));
1340 let rows = conn
1341 .query("SELECT COUNT(*) FROM sqlite_master WHERE name = 'identity_probe';")
1342 .await
1343 .expect("matching identity schema connection must load the seeded schema");
1344 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(1)]);
1345 drop(conn);
1346 drop(leased_file);
1347 });
1348 }
1349
1350 #[cfg(all(feature = "native", windows))]
1351 #[test]
1352 fn windows_reserved_empty_open_is_identity_bound() {
1353 asupersync::test_utils::run_test(|| async {
1354 let dir = tempfile::tempdir().expect("create temp dir");
1355 let identity_path = dir.path().join("reservation-identity.db");
1356 let candidate_path = dir.path().join("reservation-candidate.db");
1357 let accepted_path = dir.path().join("reservation-accepted.db");
1358 for path in [&identity_path, &candidate_path, &accepted_path] {
1359 drop(std::fs::File::create(path).expect("reserve empty database path"));
1360 }
1361
1362 let wrong_identity = windows_file_identity(&identity_path);
1363 assert_ne!(wrong_identity, windows_file_identity(&candidate_path));
1364 let candidate_artifacts = native_database_artifacts(&candidate_path);
1365 seed_windows_auxiliary_sentinels(&candidate_artifacts);
1366 let candidate_before = snapshot_native_artifacts(&candidate_artifacts);
1367 assert!(
1368 candidate_before[5..].iter().all(Option::is_none),
1369 "advisory lock sidecars must start absent"
1370 );
1371
1372 let error = Connection::open_reserved_with_expected_identity(
1373 candidate_path.to_string_lossy().into_owned(),
1374 wrong_identity,
1375 )
1376 .await
1377 .expect_err("wrong reservation identity must refuse initialization");
1378 assert!(matches!(error, FrankenError::CannotOpen { .. }));
1379 assert_eq!(
1380 snapshot_native_artifacts(&candidate_artifacts),
1381 candidate_before,
1382 "wrong reservation identity must leave the empty file and sidecars untouched"
1383 );
1384
1385 let accepted_reservation =
1386 std::fs::File::open(&accepted_path).expect("retain accepted reservation handle");
1387 let accepted_identity = FileIdentity::from_file(&accepted_reservation)
1388 .expect("query accepted reservation identity")
1389 .expect("Windows file identity must be available");
1390 let conn = Connection::open_reserved_with_expected_identity(
1391 accepted_path.to_string_lossy().into_owned(),
1392 accepted_identity,
1393 )
1394 .await
1395 .expect("matching reservation identity must initialize the database");
1396 assert_eq!(conn.file_identity().await.unwrap(), Some(accepted_identity));
1397 assert!(
1398 std::fs::metadata(&accepted_path).unwrap().len() > 0,
1399 "matching reservation must initialize the empty database image"
1400 );
1401 conn.execute("CREATE TABLE reservation_probe(value INTEGER NOT NULL);")
1402 .await
1403 .expect("initialized reservation must accept SQL");
1404 });
1405 }
1406
1407 #[test]
1408 fn test_public_api_query_expression() {
1409 asupersync::test_utils::run_test(|| async {
1410 let conn = Connection::open(":memory:")
1411 .await
1412 .expect("in-memory connection should open");
1413 let rows = conn
1414 .query("SELECT 1 + 2, 'ab' || 'cd';")
1415 .await
1416 .expect("query should succeed");
1417 assert_eq!(rows.len(), 1);
1418 assert_eq!(
1419 row_values(&rows[0]),
1420 vec![SqliteValue::Integer(3), SqliteValue::Text("abcd".into()),]
1421 );
1422 });
1423 }
1424
1425 #[test]
1426 fn test_public_api_query_with_params() {
1427 asupersync::test_utils::run_test(|| async {
1428 let conn = Connection::open(":memory:")
1429 .await
1430 .expect("in-memory connection should open");
1431 let rows = conn
1432 .query_with_params(
1433 "SELECT ?1 + ?2, ?3;",
1434 &[
1435 SqliteValue::Integer(4),
1436 SqliteValue::Integer(5),
1437 SqliteValue::Text("ok".into()),
1438 ],
1439 )
1440 .await
1441 .expect("query_with_params should succeed");
1442 assert_eq!(rows.len(), 1);
1443 assert_eq!(
1444 row_values(&rows[0]),
1445 vec![SqliteValue::Integer(9), SqliteValue::Text("ok".into())]
1446 );
1447 });
1448 }
1449
1450 #[test]
1451 fn test_public_api_query_row_multiple_rows_error() {
1452 asupersync::test_utils::run_test(|| async {
1453 let conn = Connection::open(":memory:")
1454 .await
1455 .expect("in-memory connection should open");
1456 let error = conn
1457 .query_row("VALUES (10), (20), (30);")
1458 .await
1459 .expect_err("query_row should fail when more than one row is returned");
1460 assert!(matches!(error, FrankenError::QueryReturnedMultipleRows));
1461 });
1462 }
1463
1464 #[test]
1465 fn test_public_api_query_row_empty_error() {
1466 asupersync::test_utils::run_test(|| async {
1467 let conn = Connection::open(":memory:")
1468 .await
1469 .expect("in-memory connection should open");
1470 let error = conn
1471 .query_row("SELECT 1 WHERE 0;")
1472 .await
1473 .expect_err("query_row should fail for empty result set");
1474 assert!(matches!(error, FrankenError::QueryReturnedNoRows));
1475 });
1476 }
1477
1478 #[test]
1479 fn test_public_api_execute_returns_row_count() {
1480 asupersync::test_utils::run_test(|| async {
1481 let conn = Connection::open(":memory:")
1482 .await
1483 .expect("in-memory connection should open");
1484 let count = conn
1485 .execute("VALUES (1), (2), (3);")
1486 .await
1487 .expect("execute should succeed");
1488 assert_eq!(count, 3);
1489 });
1490 }
1491
1492 #[test]
1495 fn open_empty_path_fails() {
1496 asupersync::test_utils::run_test(|| async {
1497 let err = Connection::open("")
1498 .await
1499 .expect_err("empty path should fail");
1500 assert!(matches!(err, FrankenError::CannotOpen { .. }));
1501 });
1502 }
1503
1504 #[test]
1505 fn runtime_api_is_reexported() {
1506 asupersync::test_utils::run_test(|| async {
1507 let runtime = init_global_runtime(RuntimeConfig {
1508 worker_threads: 2,
1509 io_poll_strategy: IoPollStrategy::Blocking,
1510 });
1511 assert_eq!(runtime.config().worker_threads, 2);
1512 assert_eq!(runtime.config().io_poll_strategy, IoPollStrategy::Blocking);
1513
1514 let parent_cx = fsqlite_types::cx::Cx::new().with_trace_context(11, 0, 0);
1515 let explicit_runtime = Arc::new(RuntimeContext::new_with_root_cx(
1516 RuntimeConfig {
1517 worker_threads: 1,
1518 io_poll_strategy: IoPollStrategy::Auto,
1519 },
1520 &parent_cx,
1521 ));
1522 let env = ConnectionEnv::new(Arc::clone(&explicit_runtime));
1523 let conn = Connection::open_with_env(":memory:", env)
1524 .await
1525 .expect("connection should open");
1526 assert_eq!(conn.path(), ":memory:");
1527 });
1528 }
1529
1530 #[test]
1533 fn row_get_valid_index() {
1534 asupersync::test_utils::run_test(|| async {
1535 let conn = Connection::open(":memory:").await.unwrap();
1536 let row = conn.query_row("SELECT 42, 'hello';").await.unwrap();
1537 assert_eq!(row.get(0), Some(&SqliteValue::Integer(42)));
1538 assert_eq!(row.get(1), Some(&SqliteValue::Text("hello".into())));
1539 });
1540 }
1541
1542 #[test]
1543 fn row_get_out_of_bounds() {
1544 asupersync::test_utils::run_test(|| async {
1545 let conn = Connection::open(":memory:").await.unwrap();
1546 let row = conn.query_row("SELECT 1;").await.unwrap();
1547 assert_eq!(row.get(99), None);
1548 });
1549 }
1550
1551 #[test]
1552 fn row_values_returns_all_columns() {
1553 asupersync::test_utils::run_test(|| async {
1554 let conn = Connection::open(":memory:").await.unwrap();
1555 let row = conn.query_row("SELECT 1, 2, 3;").await.unwrap();
1556 assert_eq!(row.values().len(), 3);
1557 });
1558 }
1559
1560 #[test]
1563 fn prepared_query() {
1564 asupersync::test_utils::run_test(|| async {
1565 let conn = Connection::open(":memory:").await.unwrap();
1566 let stmt = conn.prepare("SELECT 7 * 6;").await.unwrap();
1567 let rows = stmt.query().await.unwrap();
1568 assert_eq!(rows.len(), 1);
1569 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(42)]);
1570 });
1571 }
1572
1573 #[test]
1574 fn prepared_query_with_params() {
1575 asupersync::test_utils::run_test(|| async {
1576 let conn = Connection::open(":memory:").await.unwrap();
1577 let stmt = conn.prepare("SELECT ?1 + ?2;").await.unwrap();
1578 let rows = stmt
1579 .query_with_params(&[SqliteValue::Integer(10), SqliteValue::Integer(20)])
1580 .await
1581 .unwrap();
1582 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(30)]);
1583 });
1584 }
1585
1586 #[test]
1587 fn prepared_query_row() {
1588 asupersync::test_utils::run_test(|| async {
1589 let conn = Connection::open(":memory:").await.unwrap();
1590 let stmt = conn.prepare("SELECT 99;").await.unwrap();
1591 let row = stmt.query_row().await.unwrap();
1592 assert_eq!(row_values(&row), vec![SqliteValue::Integer(99)]);
1593 });
1594 }
1595
1596 #[test]
1597 fn prepared_query_row_with_params() {
1598 asupersync::test_utils::run_test(|| async {
1599 let conn = Connection::open(":memory:").await.unwrap();
1600 let stmt = conn.prepare("SELECT ?1;").await.unwrap();
1601 let row = stmt
1602 .query_row_with_params(&[SqliteValue::Text("xyz".into())])
1603 .await
1604 .unwrap();
1605 assert_eq!(row_values(&row), vec![SqliteValue::Text("xyz".into())]);
1606 });
1607 }
1608
1609 #[test]
1610 fn prepared_execute() {
1611 asupersync::test_utils::run_test(|| async {
1612 let conn = Connection::open(":memory:").await.unwrap();
1613 let stmt = conn.prepare("VALUES (1), (2);").await.unwrap();
1614 assert_eq!(stmt.execute().await.unwrap(), 2);
1615 });
1616 }
1617
1618 #[test]
1619 fn prepared_execute_with_params() {
1620 asupersync::test_utils::run_test(|| async {
1621 let conn = Connection::open(":memory:").await.unwrap();
1622 let stmt = conn.prepare("SELECT ?1;").await.unwrap();
1623 assert_eq!(
1624 stmt.execute_with_params(&[SqliteValue::Integer(1)])
1625 .await
1626 .unwrap(),
1627 1
1628 );
1629 });
1630 }
1631
1632 #[test]
1633 fn prepared_explain_not_empty() {
1634 asupersync::test_utils::run_test(|| async {
1635 let conn = Connection::open(":memory:").await.unwrap();
1636 let stmt = conn.prepare("SELECT 1 + 2;").await.unwrap();
1637 let explain = stmt.explain();
1638 assert!(!explain.is_empty());
1639 });
1640 }
1641
1642 #[cfg(feature = "session")]
1643 #[test]
1644 fn session_feature_reexports_manual_session_api() {
1645 assert_eq!(super::session::extension_name(), "session");
1646
1647 let mut session = super::session::Session::new();
1648 session.attach_table("users", 2, vec![true, false]);
1649 session.record_insert(
1650 "users",
1651 vec![
1652 super::session::ChangesetValue::Integer(1),
1653 super::session::ChangesetValue::Text("alice".to_owned()),
1654 ],
1655 );
1656
1657 let encoded = session.changeset().encode();
1658 let decoded = super::session::Changeset::decode(&encoded)
1659 .expect("re-exported session API should round-trip changesets");
1660 assert_eq!(decoded.encode(), encoded);
1661 }
1662
1663 #[test]
1664 fn prepared_indexed_equality_explain_uses_duplicate_run_probe() {
1665 asupersync::test_utils::run_test(|| async {
1666 let dir = tempfile::tempdir().unwrap();
1667 let db_path = dir.path().join("indexed-equality.db");
1668 let db = db_path.to_string_lossy().to_string();
1669
1670 {
1671 let conn = Connection::open(&db).await.unwrap();
1672 conn.execute(
1673 "CREATE TABLE t (id INTEGER PRIMARY KEY, b INTEGER NOT NULL, name TEXT NOT NULL);",
1674 )
1675 .await
1676 .unwrap();
1677 conn.execute("CREATE INDEX idx_t_b ON t(b);").await.unwrap();
1678 conn.execute(
1679 "INSERT INTO t (id, b, name) VALUES \
1680 (1, 42, 'alice'), (2, 42, 'bruce'), (5, 42, 'claire'), (9, 99, 'dora');",
1681 )
1682 .await
1683 .unwrap();
1684 }
1685
1686 let conn = Connection::open(&db).await.unwrap();
1687 let stmt = conn
1688 .prepare("SELECT name FROM t WHERE b = ?1;")
1689 .await
1690 .unwrap();
1691
1692 let explain = stmt.explain();
1693 assert!(explain.contains("idx_t_b"));
1694 assert!(explain.contains("SeekGE"));
1695 assert!(explain.contains("IdxRowid"));
1696 assert!(explain.contains("SeekRowid"));
1697 assert!(
1698 explain.contains("-9223372036854775808"),
1699 "expected synthetic low-rowid probe in explain output: {explain}"
1700 );
1701
1702 let rows = stmt
1703 .query_with_params(&[SqliteValue::Integer(42)])
1704 .await
1705 .unwrap();
1706 assert_eq!(
1707 rows.iter()
1708 .map(|row| row.get(0).cloned().unwrap())
1709 .collect::<Vec<_>>(),
1710 vec![
1711 SqliteValue::Text("alice".to_owned().into()),
1712 SqliteValue::Text("bruce".to_owned().into()),
1713 SqliteValue::Text("claire".to_owned().into()),
1714 ]
1715 );
1716 });
1717 }
1718
1719 #[test]
1722 fn query_row_with_params() {
1723 asupersync::test_utils::run_test(|| async {
1724 let conn = Connection::open(":memory:").await.unwrap();
1725 let row = conn
1726 .query_row_with_params("SELECT ?1 * 2;", &[SqliteValue::Integer(5)])
1727 .await
1728 .unwrap();
1729 assert_eq!(row_values(&row), vec![SqliteValue::Integer(10)]);
1730 });
1731 }
1732
1733 #[test]
1736 fn execute_with_params_returns_count() {
1737 asupersync::test_utils::run_test(|| async {
1738 let conn = Connection::open(":memory:").await.unwrap();
1739 let count = conn
1740 .execute_with_params("SELECT ?1;", &[SqliteValue::Integer(1)])
1741 .await
1742 .unwrap();
1743 assert_eq!(count, 1);
1744 });
1745 }
1746
1747 #[test]
1750 fn create_table_and_insert_select() {
1751 asupersync::test_utils::run_test(|| async {
1752 let conn = Connection::open(":memory:").await.unwrap();
1753 conn.execute("CREATE TABLE t1 (a INTEGER, b TEXT);")
1754 .await
1755 .unwrap();
1756 conn.execute("INSERT INTO t1 VALUES (1, 'one');")
1757 .await
1758 .unwrap();
1759 conn.execute("INSERT INTO t1 VALUES (2, 'two');")
1760 .await
1761 .unwrap();
1762 let rows = conn.query("SELECT a, b FROM t1;").await.unwrap();
1763 assert_eq!(rows.len(), 2);
1764 });
1765 }
1766
1767 #[test]
1768 fn create_table_if_not_exists_no_error() {
1769 asupersync::test_utils::run_test(|| async {
1770 let conn = Connection::open(":memory:").await.unwrap();
1771 conn.execute("CREATE TABLE t1 (x INTEGER);").await.unwrap();
1772 conn.execute("CREATE TABLE IF NOT EXISTS t1 (x INTEGER);")
1774 .await
1775 .unwrap();
1776 });
1777 }
1778
1779 #[test]
1780 fn create_duplicate_table_errors() {
1781 asupersync::test_utils::run_test(|| async {
1782 let conn = Connection::open(":memory:").await.unwrap();
1783 conn.execute("CREATE TABLE t1 (x INTEGER);").await.unwrap();
1784 let err = conn
1785 .execute("CREATE TABLE t1 (x INTEGER);")
1786 .await
1787 .expect_err("duplicate table should fail");
1788 assert!(matches!(err, FrankenError::Internal(_)));
1789 });
1790 }
1791
1792 #[test]
1793 fn public_api_writable_schema_allows_filebacked_sqlite_master_insert() {
1794 asupersync::test_utils::run_test(|| async {
1795 let dir = tempfile::tempdir().expect("create temp dir");
1796 let db_path = dir.path().join("writable-schema.db");
1797 let conn = Connection::open(db_path.to_string_lossy().into_owned())
1798 .await
1799 .unwrap();
1800
1801 conn.execute("CREATE TABLE real_table (id INTEGER);")
1802 .await
1803 .unwrap();
1804 let before = conn.query_row("PRAGMA writable_schema;").await.unwrap();
1805 assert_eq!(row_values(&before), vec![SqliteValue::Integer(0)]);
1806
1807 conn.execute("PRAGMA writable_schema = ON;").await.unwrap();
1808 let after = conn.query_row("PRAGMA writable_schema;").await.unwrap();
1809 assert_eq!(row_values(&after), vec![SqliteValue::Integer(1)]);
1810
1811 let inserted = conn
1812 .execute(
1813 "INSERT INTO sqlite_master(type, name, tbl_name, rootpage, sql) \
1814 VALUES('table', 'fake_tbl', 'fake_tbl', 0, 'CREATE TABLE fake_tbl(x)');",
1815 )
1816 .await
1817 .unwrap();
1818 assert_eq!(inserted, 1);
1819
1820 let deleted = conn
1821 .execute("DELETE FROM sqlite_master WHERE name = 'fake_tbl';")
1822 .await
1823 .unwrap();
1824 assert_eq!(deleted, 1);
1825 });
1826 }
1827
1828 #[test]
1831 fn execute_insert_returns_affected_count() {
1832 asupersync::test_utils::run_test(|| async {
1833 let conn = Connection::open(":memory:").await.unwrap();
1834 conn.execute("CREATE TABLE t (v INTEGER);").await.unwrap();
1835 assert_eq!(conn.execute("INSERT INTO t VALUES (1);").await.unwrap(), 1);
1836 assert_eq!(
1837 conn.execute("INSERT INTO t VALUES (2), (3), (4);")
1838 .await
1839 .unwrap(),
1840 3,
1841 );
1842 });
1843 }
1844
1845 #[test]
1846 fn execute_update_returns_affected_count() {
1847 asupersync::test_utils::run_test(|| async {
1848 let conn = Connection::open(":memory:").await.unwrap();
1849 conn.execute("CREATE TABLE t (v INTEGER);").await.unwrap();
1850 conn.execute("INSERT INTO t VALUES (1);").await.unwrap();
1851 conn.execute("INSERT INTO t VALUES (2);").await.unwrap();
1852 conn.execute("INSERT INTO t VALUES (3);").await.unwrap();
1853 assert_eq!(conn.execute("UPDATE t SET v = 0;").await.unwrap(), 3);
1854 assert_eq!(
1855 conn.execute("UPDATE t SET v = 99 WHERE v = 0;")
1856 .await
1857 .unwrap(),
1858 3
1859 );
1860 });
1861 }
1862
1863 #[test]
1864 fn execute_delete_returns_affected_count() {
1865 asupersync::test_utils::run_test(|| async {
1866 let conn = Connection::open(":memory:").await.unwrap();
1867 conn.execute("CREATE TABLE t (v INTEGER);").await.unwrap();
1868 conn.execute("INSERT INTO t VALUES (1);").await.unwrap();
1869 conn.execute("INSERT INTO t VALUES (2);").await.unwrap();
1870 conn.execute("INSERT INTO t VALUES (3);").await.unwrap();
1871 assert_eq!(conn.execute("DELETE FROM t WHERE v = 2;").await.unwrap(), 1);
1872 assert_eq!(conn.execute("DELETE FROM t;").await.unwrap(), 2);
1873 });
1874 }
1875
1876 #[test]
1879 fn update_modifies_rows() {
1880 asupersync::test_utils::run_test(|| async {
1881 let conn = Connection::open(":memory:").await.unwrap();
1882 conn.execute("CREATE TABLE t (v INTEGER);").await.unwrap();
1883 conn.execute("INSERT INTO t VALUES (10);").await.unwrap();
1884 conn.execute("INSERT INTO t VALUES (20);").await.unwrap();
1885 conn.execute("UPDATE t SET v = 99 WHERE v = 10;")
1886 .await
1887 .unwrap();
1888 let rows = conn.query("SELECT v FROM t;").await.unwrap();
1889 let vals: Vec<_> = rows.iter().map(row_values).collect();
1890 assert!(vals.contains(&vec![SqliteValue::Integer(99)]));
1891 assert!(vals.contains(&vec![SqliteValue::Integer(20)]));
1892 });
1893 }
1894
1895 #[test]
1896 fn update_preserves_integer_primary_key_rowid_alias() {
1897 asupersync::test_utils::run_test(|| async {
1898 let conn = Connection::open(":memory:").await.unwrap();
1899 conn.execute("CREATE TABLE accounts (id INTEGER PRIMARY KEY, balance INTEGER);")
1900 .await
1901 .unwrap();
1902 conn.execute("INSERT INTO accounts VALUES (1, 100);")
1903 .await
1904 .unwrap();
1905 conn.execute("INSERT INTO accounts VALUES (2, 200);")
1906 .await
1907 .unwrap();
1908
1909 conn.execute("UPDATE accounts SET balance = balance + 5 WHERE id = 1;")
1910 .await
1911 .unwrap();
1912
1913 let rows = conn
1914 .query("SELECT id, balance FROM accounts ORDER BY id;")
1915 .await
1916 .unwrap();
1917 assert_eq!(rows.len(), 2, "update must not create or lose rows");
1918 assert_eq!(
1919 row_values(&rows[0]),
1920 vec![SqliteValue::Integer(1), SqliteValue::Integer(105)],
1921 "id=1 row must be updated in place"
1922 );
1923 assert_eq!(
1924 row_values(&rows[1]),
1925 vec![SqliteValue::Integer(2), SqliteValue::Integer(200)],
1926 "id=2 row must remain unchanged"
1927 );
1928 });
1929 }
1930
1931 #[test]
1932 fn concurrent_same_row_deposit_commits_must_conflict_or_serialize() {
1933 asupersync::test_utils::run_test(|| async {
1934 let dir = tempfile::tempdir().unwrap();
1935 let db_path = dir.path().join("concurrent_same_row_deposit.db");
1936 let db = db_path.to_string_lossy().to_string();
1937
1938 {
1939 let conn = Connection::open(&db).await.unwrap();
1940 conn.execute("PRAGMA fsqlite.concurrent_mode=ON;")
1941 .await
1942 .unwrap();
1943 conn.execute(
1944 "CREATE TABLE accounts (id INTEGER PRIMARY KEY, balance INTEGER NOT NULL);",
1945 )
1946 .await
1947 .unwrap();
1948 conn.execute("INSERT INTO accounts VALUES (1, 0);")
1949 .await
1950 .unwrap();
1951 }
1952
1953 let conn1 = Connection::open(&db).await.unwrap();
1954 let conn2 = Connection::open(&db).await.unwrap();
1955 conn1
1956 .execute("PRAGMA fsqlite.concurrent_mode=ON;")
1957 .await
1958 .unwrap();
1959 conn2
1960 .execute("PRAGMA fsqlite.concurrent_mode=ON;")
1961 .await
1962 .unwrap();
1963
1964 conn1.execute("BEGIN CONCURRENT;").await.unwrap();
1965 conn2.execute("BEGIN CONCURRENT;").await.unwrap();
1966
1967 assert_eq!(
1968 conn1
1969 .execute("UPDATE accounts SET balance = balance + 1 WHERE id = 1;")
1970 .await
1971 .unwrap(),
1972 1
1973 );
1974 let update2 = conn2
1975 .execute("UPDATE accounts SET balance = balance + 1 WHERE id = 1;")
1976 .await;
1977
1978 let commit1 = conn1.execute("COMMIT;").await;
1979 let commit2 = match update2 {
1980 Ok(changes2) => {
1981 assert_eq!(changes2, 1, "second update should affect one row");
1982 conn2.execute("COMMIT;").await
1983 }
1984 Err(err) => {
1985 assert!(
1986 err.is_transient(),
1987 "second concurrent writer should fail transiently on conflict, got: {err}"
1988 );
1989 let rollback = conn2.execute("ROLLBACK;").await;
1990 assert!(
1991 rollback.is_ok(),
1992 "second writer should remain rollback-able after transient conflict: {rollback:?}"
1993 );
1994 Err(err)
1995 }
1996 };
1997
1998 let verify = Connection::open(&db).await.unwrap();
1999 let row = verify
2000 .query_row("SELECT balance FROM accounts WHERE id = 1;")
2001 .await
2002 .unwrap();
2003 let balance = row.get(0).cloned().unwrap_or(SqliteValue::Null);
2004 match (commit1, commit2) {
2005 (Ok(_), Ok(_)) => {
2006 assert_eq!(
2007 balance,
2008 SqliteValue::Integer(2),
2009 "if both commits succeed, both deposits must be visible"
2010 );
2011 }
2012 (Ok(_), Err(err)) | (Err(err), Ok(_)) => {
2013 assert!(
2014 err.is_transient(),
2015 "conflicting concurrent writer should fail with transient busy snapshot/busy, got: {err}"
2016 );
2017 assert_eq!(
2018 balance,
2019 SqliteValue::Integer(1),
2020 "if one writer aborts, exactly one deposit should persist"
2021 );
2022 }
2023 (Err(err1), Err(err2)) => {
2024 panic!("at least one concurrent writer must commit: err1={err1}; err2={err2}");
2025 }
2026 }
2027 });
2028 }
2029
2030 #[test]
2031 fn delete_removes_rows() {
2032 asupersync::test_utils::run_test(|| async {
2033 let conn = Connection::open(":memory:").await.unwrap();
2034 conn.execute("CREATE TABLE t (v INTEGER);").await.unwrap();
2035 conn.execute("INSERT INTO t VALUES (1);").await.unwrap();
2036 conn.execute("INSERT INTO t VALUES (2);").await.unwrap();
2037 conn.execute("INSERT INTO t VALUES (3);").await.unwrap();
2038 conn.execute("DELETE FROM t WHERE v = 2;").await.unwrap();
2039 let rows = conn.query("SELECT v FROM t;").await.unwrap();
2040 assert_eq!(rows.len(), 2);
2041 let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
2042 assert!(vals.contains(&SqliteValue::Integer(1)));
2043 assert!(vals.contains(&SqliteValue::Integer(3)));
2044 });
2045 }
2046
2047 #[test]
2050 fn null_value_roundtrip() {
2051 asupersync::test_utils::run_test(|| async {
2052 let conn = Connection::open(":memory:").await.unwrap();
2053 let row = conn.query_row("SELECT NULL;").await.unwrap();
2054 assert_eq!(row_values(&row), vec![SqliteValue::Null]);
2055 });
2056 }
2057
2058 #[test]
2059 #[allow(clippy::approx_constant)]
2060 fn real_value_roundtrip() {
2061 asupersync::test_utils::run_test(|| async {
2062 let conn = Connection::open(":memory:").await.unwrap();
2063 let row = conn.query_row("SELECT 3.14;").await.unwrap();
2064 if let SqliteValue::Float(v) = &row_values(&row)[0] {
2065 assert!((*v - 3.14).abs() < f64::EPSILON);
2066 } else {
2067 unreachable!("expected Float value");
2068 }
2069 });
2070 }
2071
2072 #[test]
2073 fn text_value_roundtrip() {
2074 asupersync::test_utils::run_test(|| async {
2075 let conn = Connection::open(":memory:").await.unwrap();
2076 let row = conn.query_row("SELECT 'hello world';").await.unwrap();
2077 assert_eq!(
2078 row_values(&row),
2079 vec![SqliteValue::Text("hello world".into())]
2080 );
2081 });
2082 }
2083
2084 #[test]
2085 fn blob_value_via_params() {
2086 asupersync::test_utils::run_test(|| async {
2087 let conn = Connection::open(":memory:").await.unwrap();
2088 let blob = vec![0xDE, 0xAD, 0xBE, 0xEF];
2089 let row = conn
2090 .query_row_with_params("SELECT ?1;", &[SqliteValue::Blob(blob.clone().into())])
2091 .await
2092 .unwrap();
2093 assert_eq!(row_values(&row), vec![SqliteValue::Blob(blob.into())]);
2094 });
2095 }
2096
2097 #[test]
2100 fn in_transaction_flag() {
2101 asupersync::test_utils::run_test(|| async {
2102 let conn = Connection::open(":memory:").await.unwrap();
2103 assert!(!conn.in_transaction());
2104 conn.execute("BEGIN;").await.unwrap();
2105 assert!(conn.in_transaction());
2106 conn.execute("COMMIT;").await.unwrap();
2107 assert!(!conn.in_transaction());
2108 });
2109 }
2110
2111 #[test]
2112 fn begin_commit_persists_changes() {
2113 asupersync::test_utils::run_test(|| async {
2114 let conn = Connection::open(":memory:").await.unwrap();
2115 conn.execute("CREATE TABLE t (v INTEGER);").await.unwrap();
2116 conn.execute("BEGIN;").await.unwrap();
2117 conn.execute("INSERT INTO t VALUES (42);").await.unwrap();
2118 conn.execute("COMMIT;").await.unwrap();
2119 let rows = conn.query("SELECT v FROM t;").await.unwrap();
2120 assert_eq!(rows.len(), 1);
2121 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(42)]);
2122 });
2123 }
2124
2125 #[test]
2126 fn rollback_reverts_changes() {
2127 asupersync::test_utils::run_test(|| async {
2128 let conn = Connection::open(":memory:").await.unwrap();
2129 conn.execute("CREATE TABLE t (v INTEGER);").await.unwrap();
2130 conn.execute("INSERT INTO t VALUES (1);").await.unwrap();
2131
2132 let m1 = conn.query("SELECT * FROM sqlite_master;").await.unwrap();
2133 eprintln!("MAIN BEFORE BEGIN: {:?}", m1);
2134
2135 conn.execute("BEGIN;").await.unwrap();
2136 conn.execute("INSERT INTO t VALUES (2);").await.unwrap();
2137 conn.execute("ROLLBACK;").await.unwrap();
2138
2139 let m2 = conn.query("SELECT * FROM sqlite_master;").await.unwrap();
2140 eprintln!("MAIN AFTER ROLLBACK: {:?}", m2);
2141
2142 let rows = conn.query("SELECT v FROM t;").await.unwrap();
2143 assert_eq!(rows.len(), 1);
2144 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(1)]);
2145 });
2146 }
2147
2148 #[test]
2149 fn nested_begin_errors() {
2150 asupersync::test_utils::run_test(|| async {
2151 let conn = Connection::open(":memory:").await.unwrap();
2152 conn.execute("BEGIN;").await.unwrap();
2153 let err = conn
2154 .execute("BEGIN;")
2155 .await
2156 .expect_err("nested begin should fail");
2157 assert!(matches!(err, FrankenError::Internal(_)));
2158 });
2159 }
2160
2161 #[test]
2162 fn commit_without_transaction_errors() {
2163 asupersync::test_utils::run_test(|| async {
2164 let conn = Connection::open(":memory:").await.unwrap();
2165 let err = conn
2166 .execute("COMMIT;")
2167 .await
2168 .expect_err("commit without txn should fail");
2169 assert!(matches!(err, FrankenError::Internal(_)));
2170 });
2171 }
2172
2173 #[test]
2174 fn rollback_without_transaction_errors() {
2175 asupersync::test_utils::run_test(|| async {
2176 let conn = Connection::open(":memory:").await.unwrap();
2177 let err = conn
2178 .execute("ROLLBACK;")
2179 .await
2180 .expect_err("rollback without txn should fail");
2181 assert!(matches!(err, FrankenError::Internal(_)));
2182 });
2183 }
2184
2185 #[test]
2188 fn savepoint_and_rollback_to() {
2189 asupersync::test_utils::run_test(|| async {
2190 let conn = Connection::open(":memory:").await.unwrap();
2191 conn.execute("CREATE TABLE t (v INTEGER);").await.unwrap();
2192 conn.execute("INSERT INTO t VALUES (1);").await.unwrap();
2193 conn.execute("SAVEPOINT sp1;").await.unwrap();
2194 conn.execute("INSERT INTO t VALUES (2);").await.unwrap();
2195 conn.execute("ROLLBACK TO sp1;").await.unwrap();
2196 let rows = conn.query("SELECT v FROM t;").await.unwrap();
2197 assert_eq!(rows.len(), 1);
2198 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(1)]);
2199 });
2200 }
2201
2202 #[test]
2203 fn savepoint_release_commits_changes() {
2204 asupersync::test_utils::run_test(|| async {
2205 let conn = Connection::open(":memory:").await.unwrap();
2206 conn.execute("CREATE TABLE t (v INTEGER);").await.unwrap();
2207 conn.execute("SAVEPOINT sp1;").await.unwrap();
2208 conn.execute("INSERT INTO t VALUES (100);").await.unwrap();
2209 conn.execute("RELEASE sp1;").await.unwrap();
2210 let rows = conn.query("SELECT v FROM t;").await.unwrap();
2211 assert_eq!(rows.len(), 1);
2212 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(100)]);
2213 });
2214 }
2215
2216 #[test]
2217 fn release_nonexistent_savepoint_errors() {
2218 asupersync::test_utils::run_test(|| async {
2219 let conn = Connection::open(":memory:").await.unwrap();
2220 conn.execute("BEGIN;").await.unwrap();
2221 let err = conn
2222 .execute("RELEASE nosuch;")
2223 .await
2224 .expect_err("release nonexistent savepoint should fail");
2225 assert!(matches!(err, FrankenError::Internal(_)));
2226 });
2227 }
2228
2229 #[test]
2232 fn parse_error_on_invalid_sql() {
2233 asupersync::test_utils::run_test(|| async {
2234 let conn = Connection::open(":memory:").await.unwrap();
2235 assert!(conn.query("NOT VALID SQL;").await.is_err());
2236 });
2237 }
2238
2239 #[test]
2242 fn multiple_statements_in_query() {
2243 asupersync::test_utils::run_test(|| async {
2244 let conn = Connection::open(":memory:").await.unwrap();
2245 conn.execute("CREATE TABLE t (v INTEGER);").await.unwrap();
2246 let rows = conn
2248 .query("INSERT INTO t VALUES (1); INSERT INTO t VALUES (2); SELECT v FROM t;")
2249 .await
2250 .unwrap();
2251 assert_eq!(rows.len(), 2);
2252 });
2253 }
2254
2255 #[test]
2258 fn arithmetic_expressions() {
2259 asupersync::test_utils::run_test(|| async {
2260 let conn = Connection::open(":memory:").await.unwrap();
2261 let row = conn
2262 .query_row("SELECT 10 - 3, 4 * 5, 20 / 4;")
2263 .await
2264 .unwrap();
2265 assert_eq!(
2266 row_values(&row),
2267 vec![
2268 SqliteValue::Integer(7),
2269 SqliteValue::Integer(20),
2270 SqliteValue::Integer(5),
2271 ]
2272 );
2273 });
2274 }
2275
2276 #[test]
2277 fn string_concatenation() {
2278 asupersync::test_utils::run_test(|| async {
2279 let conn = Connection::open(":memory:").await.unwrap();
2280 let row = conn.query_row("SELECT 'foo' || 'bar';").await.unwrap();
2281 assert_eq!(row_values(&row), vec![SqliteValue::Text("foobar".into())]);
2282 });
2283 }
2284
2285 async fn setup_three_rows(conn: &Connection) {
2288 conn.execute("CREATE TABLE t3 (a INTEGER, b TEXT);")
2289 .await
2290 .unwrap();
2291 conn.execute("INSERT INTO t3 VALUES (1, 'one');")
2292 .await
2293 .unwrap();
2294 conn.execute("INSERT INTO t3 VALUES (2, 'two');")
2295 .await
2296 .unwrap();
2297 conn.execute("INSERT INTO t3 VALUES (3, 'three');")
2298 .await
2299 .unwrap();
2300 }
2301
2302 #[test]
2303 fn where_and_predicate() {
2304 asupersync::test_utils::run_test(|| async {
2305 let conn = Connection::open(":memory:").await.unwrap();
2306 setup_three_rows(&conn).await;
2307 let rows = conn
2308 .query("SELECT a FROM t3 WHERE a > 1 AND b = 'two';")
2309 .await
2310 .unwrap();
2311 assert_eq!(rows.len(), 1);
2312 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(2)]);
2313 });
2314 }
2315
2316 #[test]
2317 fn where_or_predicate() {
2318 asupersync::test_utils::run_test(|| async {
2319 let conn = Connection::open(":memory:").await.unwrap();
2320 setup_three_rows(&conn).await;
2321 let rows = conn
2322 .query("SELECT a FROM t3 WHERE a = 1 OR a = 3;")
2323 .await
2324 .unwrap();
2325 assert_eq!(rows.len(), 2);
2326 let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
2327 assert!(vals.contains(&SqliteValue::Integer(1)));
2328 assert!(vals.contains(&SqliteValue::Integer(3)));
2329 });
2330 }
2331
2332 #[test]
2333 fn where_comparison_operators() {
2334 asupersync::test_utils::run_test(|| async {
2335 let conn = Connection::open(":memory:").await.unwrap();
2336 setup_three_rows(&conn).await;
2337 let rows = conn.query("SELECT a FROM t3 WHERE a > 2;").await.unwrap();
2339 assert_eq!(rows.len(), 1);
2340 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(3)]);
2341 let rows = conn.query("SELECT a FROM t3 WHERE a <= 2;").await.unwrap();
2343 assert_eq!(rows.len(), 2);
2344 let rows = conn.query("SELECT a FROM t3 WHERE a != 2;").await.unwrap();
2346 assert_eq!(rows.len(), 2);
2347 });
2348 }
2349
2350 #[test]
2353 fn where_is_null() {
2354 asupersync::test_utils::run_test(|| async {
2355 let conn = Connection::open(":memory:").await.unwrap();
2356 conn.execute("CREATE TABLE tn (a INTEGER, b TEXT);")
2357 .await
2358 .unwrap();
2359 conn.execute("INSERT INTO tn VALUES (1, 'x');")
2360 .await
2361 .unwrap();
2362 conn.execute("INSERT INTO tn VALUES (2, NULL);")
2363 .await
2364 .unwrap();
2365 let rows = conn
2366 .query("SELECT a FROM tn WHERE b IS NULL;")
2367 .await
2368 .unwrap();
2369 assert_eq!(rows.len(), 1);
2370 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(2)]);
2371 });
2372 }
2373
2374 #[test]
2375 fn where_is_not_null() {
2376 asupersync::test_utils::run_test(|| async {
2377 let conn = Connection::open(":memory:").await.unwrap();
2378 conn.execute("CREATE TABLE tn2 (a INTEGER, b TEXT);")
2379 .await
2380 .unwrap();
2381 conn.execute("INSERT INTO tn2 VALUES (1, 'x');")
2382 .await
2383 .unwrap();
2384 conn.execute("INSERT INTO tn2 VALUES (2, NULL);")
2385 .await
2386 .unwrap();
2387 let rows = conn
2388 .query("SELECT a FROM tn2 WHERE b IS NOT NULL;")
2389 .await
2390 .unwrap();
2391 assert_eq!(rows.len(), 1);
2392 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(1)]);
2393 });
2394 }
2395
2396 #[test]
2399 fn coalesce_expression() {
2400 asupersync::test_utils::run_test(|| async {
2401 let conn = Connection::open(":memory:").await.unwrap();
2402 let row = conn
2403 .query_row("SELECT COALESCE(NULL, NULL, 42);")
2404 .await
2405 .unwrap();
2406 assert_eq!(row_values(&row), vec![SqliteValue::Integer(42)]);
2407 });
2408 }
2409
2410 #[test]
2411 fn nullif_expression() {
2412 asupersync::test_utils::run_test(|| async {
2413 let conn = Connection::open(":memory:").await.unwrap();
2414 let row = conn.query_row("SELECT NULLIF(1, 1);").await.unwrap();
2415 assert_eq!(row_values(&row), vec![SqliteValue::Null]);
2416 let row = conn.query_row("SELECT NULLIF(1, 2);").await.unwrap();
2417 assert_eq!(row_values(&row), vec![SqliteValue::Integer(1)]);
2418 });
2419 }
2420
2421 #[test]
2424 fn case_when_expression() {
2425 asupersync::test_utils::run_test(|| async {
2426 let conn = Connection::open(":memory:").await.unwrap();
2427 let row = conn
2428 .query_row("SELECT CASE WHEN 1 > 0 THEN 'yes' ELSE 'no' END;")
2429 .await
2430 .unwrap();
2431 assert_eq!(row_values(&row), vec![SqliteValue::Text("yes".into())]);
2432 });
2433 }
2434
2435 #[test]
2436 fn case_simple_form() {
2437 asupersync::test_utils::run_test(|| async {
2438 let conn = Connection::open(":memory:").await.unwrap();
2439 let row = conn
2440 .query_row("SELECT CASE 2 WHEN 1 THEN 'a' WHEN 2 THEN 'b' ELSE 'c' END;")
2441 .await
2442 .unwrap();
2443 assert_eq!(row_values(&row), vec![SqliteValue::Text("b".into())]);
2444 });
2445 }
2446
2447 #[test]
2450 fn builtin_abs() {
2451 asupersync::test_utils::run_test(|| async {
2452 let conn = Connection::open(":memory:").await.unwrap();
2453 let row = conn.query_row("SELECT ABS(-42);").await.unwrap();
2454 assert_eq!(row_values(&row), vec![SqliteValue::Integer(42)]);
2455 });
2456 }
2457
2458 #[test]
2459 fn builtin_length() {
2460 asupersync::test_utils::run_test(|| async {
2461 let conn = Connection::open(":memory:").await.unwrap();
2462 let row = conn.query_row("SELECT LENGTH('hello');").await.unwrap();
2463 assert_eq!(row_values(&row), vec![SqliteValue::Integer(5)]);
2464 });
2465 }
2466
2467 #[test]
2468 fn builtin_upper_lower() {
2469 asupersync::test_utils::run_test(|| async {
2470 let conn = Connection::open(":memory:").await.unwrap();
2471 let row = conn
2472 .query_row("SELECT UPPER('hello'), LOWER('WORLD');")
2473 .await
2474 .unwrap();
2475 assert_eq!(
2476 row_values(&row),
2477 vec![
2478 SqliteValue::Text("HELLO".into()),
2479 SqliteValue::Text("world".into()),
2480 ]
2481 );
2482 });
2483 }
2484
2485 #[test]
2486 fn builtin_typeof() {
2487 asupersync::test_utils::run_test(|| async {
2488 let conn = Connection::open(":memory:").await.unwrap();
2489 let row = conn.query_row("SELECT TYPEOF(42);").await.unwrap();
2490 assert_eq!(row_values(&row), vec![SqliteValue::Text("integer".into())]);
2491 });
2492 }
2493
2494 #[test]
2497 fn cast_integer_to_text() {
2498 asupersync::test_utils::run_test(|| async {
2499 let conn = Connection::open(":memory:").await.unwrap();
2500 let row = conn.query_row("SELECT CAST(42 AS TEXT);").await.unwrap();
2501 assert_eq!(row_values(&row), vec![SqliteValue::Text("42".into())]);
2502 });
2503 }
2504
2505 #[test]
2506 fn cast_text_to_integer() {
2507 asupersync::test_utils::run_test(|| async {
2508 let conn = Connection::open(":memory:").await.unwrap();
2509 let row = conn
2510 .query_row("SELECT CAST('123' AS INTEGER);")
2511 .await
2512 .unwrap();
2513 assert_eq!(row_values(&row), vec![SqliteValue::Integer(123)]);
2514 });
2515 }
2516
2517 #[test]
2520 fn blob_literal_hex() {
2521 asupersync::test_utils::run_test(|| async {
2522 let conn = Connection::open(":memory:").await.unwrap();
2523 let row = conn.query_row("SELECT X'DEADBEEF';").await.unwrap();
2524 assert_eq!(
2525 row_values(&row),
2526 vec![SqliteValue::Blob(vec![0xDE, 0xAD, 0xBE, 0xEF].into())]
2527 );
2528 });
2529 }
2530
2531 #[test]
2534 fn unary_minus() {
2535 asupersync::test_utils::run_test(|| async {
2536 let conn = Connection::open(":memory:").await.unwrap();
2537 let row = conn.query_row("SELECT -42;").await.unwrap();
2538 assert_eq!(row_values(&row), vec![SqliteValue::Integer(-42)]);
2539 });
2540 }
2541
2542 #[test]
2543 fn not_operator() {
2544 asupersync::test_utils::run_test(|| async {
2545 let conn = Connection::open(":memory:").await.unwrap();
2546 let row = conn.query_row("SELECT NOT 0;").await.unwrap();
2547 assert_eq!(row_values(&row), vec![SqliteValue::Integer(1)]);
2548 });
2549 }
2550
2551 #[test]
2554 fn values_order_by() {
2555 asupersync::test_utils::run_test(|| async {
2556 let conn = Connection::open(":memory:").await.unwrap();
2557 assert!(
2558 conn.query("VALUES (3), (1), (2) ORDER BY 1;")
2559 .await
2560 .is_err(),
2561 "bare VALUES cannot carry an ORDER BY clause in SQLite grammar"
2562 );
2563 let rows = conn
2564 .query("SELECT * FROM (VALUES (3), (1), (2)) ORDER BY 1;")
2565 .await
2566 .unwrap();
2567 let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
2568 assert_eq!(
2569 vals,
2570 vec![
2571 SqliteValue::Integer(1),
2572 SqliteValue::Integer(2),
2573 SqliteValue::Integer(3),
2574 ]
2575 );
2576 });
2577 }
2578
2579 #[test]
2580 fn values_order_by_desc_with_limit() {
2581 asupersync::test_utils::run_test(|| async {
2582 let conn = Connection::open(":memory:").await.unwrap();
2583 let rows = conn
2584 .query("SELECT * FROM (VALUES (3), (1), (2)) ORDER BY 1 DESC LIMIT 2;")
2585 .await
2586 .unwrap();
2587 let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
2588 assert_eq!(vals, vec![SqliteValue::Integer(3), SqliteValue::Integer(2)]);
2589 });
2590 }
2591
2592 #[test]
2593 fn values_limit_offset() {
2594 asupersync::test_utils::run_test(|| async {
2595 let conn = Connection::open(":memory:").await.unwrap();
2596 assert!(
2597 conn.query("VALUES (10), (20), (30), (40) LIMIT 2 OFFSET 1;")
2598 .await
2599 .is_err(),
2600 "bare VALUES cannot carry a LIMIT clause in SQLite grammar"
2601 );
2602 let rows = conn
2603 .query("SELECT * FROM (VALUES (10), (20), (30), (40)) ORDER BY 1 LIMIT 2 OFFSET 1;")
2604 .await
2605 .unwrap();
2606 let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
2607 assert_eq!(
2608 vals,
2609 vec![SqliteValue::Integer(20), SqliteValue::Integer(30)]
2610 );
2611 });
2612 }
2613
2614 #[test]
2617 fn delete_all_rows() {
2618 asupersync::test_utils::run_test(|| async {
2619 let conn = Connection::open(":memory:").await.unwrap();
2620 setup_three_rows(&conn).await;
2621 conn.execute("DELETE FROM t3;").await.unwrap();
2622 let rows = conn.query("SELECT a FROM t3;").await.unwrap();
2623 assert_eq!(rows.len(), 0);
2624 });
2625 }
2626
2627 #[test]
2630 fn select_expression_column_arithmetic() {
2631 asupersync::test_utils::run_test(|| async {
2632 let conn = Connection::open(":memory:").await.unwrap();
2633 conn.execute("CREATE TABLE te (a INTEGER);").await.unwrap();
2634 conn.execute("INSERT INTO te VALUES (10);").await.unwrap();
2635 conn.execute("INSERT INTO te VALUES (20);").await.unwrap();
2636 let rows = conn.query("SELECT a + 1 FROM te;").await.unwrap();
2637 let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
2638 assert!(vals.contains(&SqliteValue::Integer(11)));
2639 assert!(vals.contains(&SqliteValue::Integer(21)));
2640 });
2641 }
2642
2643 #[test]
2644 fn select_expression_column_with_literal() {
2645 asupersync::test_utils::run_test(|| async {
2646 let conn = Connection::open(":memory:").await.unwrap();
2647 conn.execute("CREATE TABLE te2 (a INTEGER, b TEXT);")
2648 .await
2649 .unwrap();
2650 conn.execute("INSERT INTO te2 VALUES (5, 'hello');")
2651 .await
2652 .unwrap();
2653 let rows = conn.query("SELECT a * 2, b FROM te2;").await.unwrap();
2654 assert_eq!(rows.len(), 1);
2655 assert_eq!(
2656 row_values(&rows[0]),
2657 vec![SqliteValue::Integer(10), SqliteValue::Text("hello".into())]
2658 );
2659 });
2660 }
2661
2662 #[test]
2665 fn insert_multi_row_values() {
2666 asupersync::test_utils::run_test(|| async {
2667 let conn = Connection::open(":memory:").await.unwrap();
2668 conn.execute("CREATE TABLE tm (v INTEGER);").await.unwrap();
2669 conn.execute("INSERT INTO tm VALUES (1), (2), (3);")
2670 .await
2671 .unwrap();
2672 let rows = conn.query("SELECT v FROM tm;").await.unwrap();
2673 let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
2674 assert_eq!(vals.len(), 3);
2675 assert!(vals.contains(&SqliteValue::Integer(1)));
2676 assert!(vals.contains(&SqliteValue::Integer(2)));
2677 assert!(vals.contains(&SqliteValue::Integer(3)));
2678 });
2679 }
2680
2681 #[test]
2684 fn in_expression_only() {
2685 asupersync::test_utils::run_test(|| async {
2686 let conn = Connection::open(":memory:").await.unwrap();
2688 let row = conn.query_row("SELECT 2 IN (1, 2, 3);").await.unwrap();
2689 assert_eq!(row_values(&row), vec![SqliteValue::Integer(1)]);
2690 });
2691 }
2692
2693 #[test]
2694 fn between_expression_only() {
2695 asupersync::test_utils::run_test(|| async {
2696 let conn = Connection::open(":memory:").await.unwrap();
2697 let row = conn.query_row("SELECT 2 BETWEEN 1 AND 3;").await.unwrap();
2698 assert_eq!(row_values(&row), vec![SqliteValue::Integer(1)]);
2699 });
2700 }
2701
2702 #[test]
2703 fn where_in_operator() {
2704 asupersync::test_utils::run_test(|| async {
2705 let conn = Connection::open(":memory:").await.unwrap();
2706 setup_three_rows(&conn).await;
2707 let rows = conn
2708 .query("SELECT a FROM t3 WHERE a IN (1, 3);")
2709 .await
2710 .unwrap();
2711 let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
2712 assert_eq!(vals.len(), 2);
2713 assert!(vals.contains(&SqliteValue::Integer(1)));
2714 assert!(vals.contains(&SqliteValue::Integer(3)));
2715 });
2716 }
2717
2718 #[test]
2719 fn where_between_operator() {
2720 asupersync::test_utils::run_test(|| async {
2721 let conn = Connection::open(":memory:").await.unwrap();
2722 setup_three_rows(&conn).await;
2723 let rows = conn
2724 .query("SELECT a FROM t3 WHERE a BETWEEN 1 AND 2;")
2725 .await
2726 .unwrap();
2727 let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
2728 assert_eq!(vals.len(), 2);
2729 assert!(vals.contains(&SqliteValue::Integer(1)));
2730 assert!(vals.contains(&SqliteValue::Integer(2)));
2731 });
2732 }
2733
2734 #[test]
2735 fn where_like_operator() {
2736 asupersync::test_utils::run_test(|| async {
2737 let conn = Connection::open(":memory:").await.unwrap();
2738 setup_three_rows(&conn).await;
2739 let rows = conn
2740 .query("SELECT b FROM t3 WHERE b LIKE 't%';")
2741 .await
2742 .unwrap();
2743 let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
2744 assert_eq!(vals.len(), 2);
2745 assert!(vals.contains(&SqliteValue::Text("two".into())));
2746 assert!(vals.contains(&SqliteValue::Text("three".into())));
2747 });
2748 }
2749
2750 #[test]
2753 fn aggregate_count_star() {
2754 asupersync::test_utils::run_test(|| async {
2755 let conn = Connection::open(":memory:").await.unwrap();
2756 setup_three_rows(&conn).await;
2757 let row = conn.query_row("SELECT COUNT(*) FROM t3;").await.unwrap();
2758 assert_eq!(row_values(&row), vec![SqliteValue::Integer(3)]);
2759 });
2760 }
2761
2762 #[test]
2763 fn aggregate_sum_min_max() {
2764 asupersync::test_utils::run_test(|| async {
2765 let conn = Connection::open(":memory:").await.unwrap();
2766 setup_three_rows(&conn).await;
2767 let row = conn
2768 .query_row("SELECT SUM(a), MIN(a), MAX(a) FROM t3;")
2769 .await
2770 .unwrap();
2771 assert_eq!(
2772 row_values(&row),
2773 vec![
2774 SqliteValue::Integer(6),
2775 SqliteValue::Integer(1),
2776 SqliteValue::Integer(3),
2777 ]
2778 );
2779 });
2780 }
2781
2782 #[test]
2783 fn aggregate_avg() {
2784 asupersync::test_utils::run_test(|| async {
2785 let conn = Connection::open(":memory:").await.unwrap();
2786 setup_three_rows(&conn).await;
2787 let row = conn.query_row("SELECT AVG(a) FROM t3;").await.unwrap();
2788 assert_eq!(row_values(&row), vec![SqliteValue::Float(2.0)]);
2790 });
2791 }
2792
2793 #[test]
2796 fn update_all_rows() {
2797 asupersync::test_utils::run_test(|| async {
2798 let conn = Connection::open(":memory:").await.unwrap();
2799 conn.execute("CREATE TABLE tu (v INTEGER);").await.unwrap();
2800 conn.execute("INSERT INTO tu VALUES (1);").await.unwrap();
2801 conn.execute("INSERT INTO tu VALUES (2);").await.unwrap();
2802 conn.execute("UPDATE tu SET v = 0;").await.unwrap();
2803 let rows = conn.query("SELECT v FROM tu;").await.unwrap();
2804 assert!(
2805 rows.iter()
2806 .all(|r| row_values(r) == vec![SqliteValue::Integer(0)])
2807 );
2808 });
2809 }
2810
2811 async fn setup_bd2832(conn: &Connection) {
2816 conn.execute("CREATE TABLE tp (a INTEGER, b TEXT, c REAL);")
2817 .await
2818 .unwrap();
2819 conn.execute("INSERT INTO tp VALUES (1, 'alpha', 1.5);")
2820 .await
2821 .unwrap();
2822 conn.execute("INSERT INTO tp VALUES (2, 'beta', 2.5);")
2823 .await
2824 .unwrap();
2825 conn.execute("INSERT INTO tp VALUES (3, 'gamma', 3.5);")
2826 .await
2827 .unwrap();
2828 conn.execute("INSERT INTO tp VALUES (4, NULL, 4.5);")
2829 .await
2830 .unwrap();
2831 conn.execute("INSERT INTO tp VALUES (5, 'delta', 5.5);")
2832 .await
2833 .unwrap();
2834 }
2835
2836 #[test]
2839 fn where_not_predicate() {
2840 asupersync::test_utils::run_test(|| async {
2841 let conn = Connection::open(":memory:").await.unwrap();
2842 setup_bd2832(&conn).await;
2843 let rows = conn
2844 .query("SELECT a FROM tp WHERE NOT (a > 3);")
2845 .await
2846 .unwrap();
2847 assert_eq!(rows.len(), 3);
2848 let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
2849 assert!(vals.contains(&SqliteValue::Integer(1)));
2850 assert!(vals.contains(&SqliteValue::Integer(2)));
2851 assert!(vals.contains(&SqliteValue::Integer(3)));
2852 });
2853 }
2854
2855 #[test]
2858 fn where_greater_equal() {
2859 asupersync::test_utils::run_test(|| async {
2860 let conn = Connection::open(":memory:").await.unwrap();
2861 setup_bd2832(&conn).await;
2862 let rows = conn.query("SELECT a FROM tp WHERE a >= 4;").await.unwrap();
2863 assert_eq!(rows.len(), 2);
2864 });
2865 }
2866
2867 #[test]
2868 fn where_less_than() {
2869 asupersync::test_utils::run_test(|| async {
2870 let conn = Connection::open(":memory:").await.unwrap();
2871 setup_bd2832(&conn).await;
2872 let rows = conn.query("SELECT a FROM tp WHERE a < 3;").await.unwrap();
2873 assert_eq!(rows.len(), 2);
2874 });
2875 }
2876
2877 #[test]
2880 fn table_order_by_asc() {
2881 asupersync::test_utils::run_test(|| async {
2882 let conn = Connection::open(":memory:").await.unwrap();
2883 conn.execute("CREATE TABLE tord (v INTEGER);")
2884 .await
2885 .unwrap();
2886 conn.execute("INSERT INTO tord VALUES (3);").await.unwrap();
2887 conn.execute("INSERT INTO tord VALUES (1);").await.unwrap();
2888 conn.execute("INSERT INTO tord VALUES (2);").await.unwrap();
2889 let rows = conn.query("SELECT v FROM tord ORDER BY v;").await.unwrap();
2890 let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
2891 assert_eq!(
2892 vals,
2893 vec![
2894 SqliteValue::Integer(1),
2895 SqliteValue::Integer(2),
2896 SqliteValue::Integer(3),
2897 ]
2898 );
2899 });
2900 }
2901
2902 #[test]
2903 fn table_order_by_desc() {
2904 asupersync::test_utils::run_test(|| async {
2905 let conn = Connection::open(":memory:").await.unwrap();
2906 conn.execute("CREATE TABLE tord2 (v INTEGER);")
2907 .await
2908 .unwrap();
2909 conn.execute("INSERT INTO tord2 VALUES (3);").await.unwrap();
2910 conn.execute("INSERT INTO tord2 VALUES (1);").await.unwrap();
2911 conn.execute("INSERT INTO tord2 VALUES (2);").await.unwrap();
2912 let rows = conn
2913 .query("SELECT v FROM tord2 ORDER BY v DESC;")
2914 .await
2915 .unwrap();
2916 let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
2917 assert_eq!(
2918 vals,
2919 vec![
2920 SqliteValue::Integer(3),
2921 SqliteValue::Integer(2),
2922 SqliteValue::Integer(1),
2923 ]
2924 );
2925 });
2926 }
2927
2928 #[test]
2931 fn table_limit() {
2932 asupersync::test_utils::run_test(|| async {
2933 let conn = Connection::open(":memory:").await.unwrap();
2934 setup_bd2832(&conn).await;
2935 let rows = conn.query("SELECT a FROM tp LIMIT 3;").await.unwrap();
2936 assert_eq!(rows.len(), 3);
2937 });
2938 }
2939
2940 #[test]
2941 fn table_limit_offset() {
2942 asupersync::test_utils::run_test(|| async {
2943 let conn = Connection::open(":memory:").await.unwrap();
2944 setup_bd2832(&conn).await;
2945 let rows = conn
2946 .query("SELECT a FROM tp LIMIT 2 OFFSET 2;")
2947 .await
2948 .unwrap();
2949 assert_eq!(rows.len(), 2);
2950 let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
2951 assert_eq!(vals, vec![SqliteValue::Integer(3), SqliteValue::Integer(4)]);
2952 });
2953 }
2954
2955 #[test]
2958 fn where_with_limit() {
2959 asupersync::test_utils::run_test(|| async {
2960 let conn = Connection::open(":memory:").await.unwrap();
2961 setup_bd2832(&conn).await;
2962 let rows = conn
2963 .query("SELECT a FROM tp WHERE a > 1 LIMIT 2;")
2964 .await
2965 .unwrap();
2966 assert_eq!(rows.len(), 2);
2967 let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
2968 assert_eq!(vals, vec![SqliteValue::Integer(2), SqliteValue::Integer(3)]);
2969 });
2970 }
2971
2972 #[test]
2975 fn case_when_table_backed() {
2976 asupersync::test_utils::run_test(|| async {
2977 let conn = Connection::open(":memory:").await.unwrap();
2978 setup_bd2832(&conn).await;
2979 let rows = conn
2980 .query("SELECT CASE WHEN a > 3 THEN 'big' ELSE 'small' END FROM tp;")
2981 .await
2982 .unwrap();
2983 assert_eq!(rows.len(), 5);
2984 assert_eq!(rows[0].values()[0], SqliteValue::Text("small".into()));
2985 assert_eq!(rows[3].values()[0], SqliteValue::Text("big".into()));
2986 });
2987 }
2988
2989 #[test]
2992 fn cast_table_backed() {
2993 asupersync::test_utils::run_test(|| async {
2994 let conn = Connection::open(":memory:").await.unwrap();
2995 conn.execute("CREATE TABLE tcast (v INTEGER);")
2996 .await
2997 .unwrap();
2998 conn.execute("INSERT INTO tcast VALUES (42);")
2999 .await
3000 .unwrap();
3001 let row = conn
3002 .query_row("SELECT CAST(v AS TEXT) FROM tcast;")
3003 .await
3004 .unwrap();
3005 assert_eq!(row_values(&row), vec![SqliteValue::Text("42".into())]);
3006 });
3007 }
3008
3009 #[test]
3012 fn where_column_is_null_correct() {
3013 asupersync::test_utils::run_test(|| async {
3014 let conn = Connection::open(":memory:").await.unwrap();
3015 setup_bd2832(&conn).await;
3016 let rows = conn
3017 .query("SELECT a FROM tp WHERE b IS NULL;")
3018 .await
3019 .unwrap();
3020 assert_eq!(rows.len(), 1);
3021 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(4)]);
3022 });
3023 }
3024
3025 #[test]
3026 fn where_column_is_not_null_correct() {
3027 asupersync::test_utils::run_test(|| async {
3028 let conn = Connection::open(":memory:").await.unwrap();
3029 setup_bd2832(&conn).await;
3030 let rows = conn
3031 .query("SELECT a FROM tp WHERE b IS NOT NULL;")
3032 .await
3033 .unwrap();
3034 assert_eq!(rows.len(), 4);
3035 });
3036 }
3037
3038 #[test]
3041 fn unary_minus_table_column() {
3042 asupersync::test_utils::run_test(|| async {
3043 let conn = Connection::open(":memory:").await.unwrap();
3044 conn.execute("CREATE TABLE tneg (x INTEGER);")
3045 .await
3046 .unwrap();
3047 conn.execute("INSERT INTO tneg VALUES (42);").await.unwrap();
3048 let row = conn.query_row("SELECT -x FROM tneg;").await.unwrap();
3049 assert_eq!(row_values(&row), vec![SqliteValue::Integer(-42)]);
3050 });
3051 }
3052
3053 #[test]
3056 fn builtin_typeof_all_types() {
3057 asupersync::test_utils::run_test(|| async {
3058 let conn = Connection::open(":memory:").await.unwrap();
3059 assert_eq!(
3060 row_values(&conn.query_row("SELECT typeof(3.14);").await.unwrap()),
3061 vec![SqliteValue::Text("real".into())]
3062 );
3063 assert_eq!(
3064 row_values(&conn.query_row("SELECT typeof('abc');").await.unwrap()),
3065 vec![SqliteValue::Text("text".into())]
3066 );
3067 assert_eq!(
3068 row_values(&conn.query_row("SELECT typeof(NULL);").await.unwrap()),
3069 vec![SqliteValue::Text("null".into())]
3070 );
3071 assert_eq!(
3072 row_values(&conn.query_row("SELECT typeof(X'FF');").await.unwrap()),
3073 vec![SqliteValue::Text("blob".into())]
3074 );
3075 });
3076 }
3077
3078 #[test]
3079 fn builtin_substr() {
3080 asupersync::test_utils::run_test(|| async {
3081 let conn = Connection::open(":memory:").await.unwrap();
3082 let row = conn
3083 .query_row("SELECT substr('hello world', 7, 5);")
3084 .await
3085 .unwrap();
3086 assert_eq!(row_values(&row), vec![SqliteValue::Text("world".into())]);
3087 });
3088 }
3089
3090 #[test]
3091 fn builtin_replace() {
3092 asupersync::test_utils::run_test(|| async {
3093 let conn = Connection::open(":memory:").await.unwrap();
3094 let row = conn
3095 .query_row("SELECT replace('hello world', 'world', 'rust');")
3096 .await
3097 .unwrap();
3098 assert_eq!(
3099 row_values(&row),
3100 vec![SqliteValue::Text("hello rust".into())]
3101 );
3102 });
3103 }
3104
3105 #[test]
3106 fn builtin_trim() {
3107 asupersync::test_utils::run_test(|| async {
3108 let conn = Connection::open(":memory:").await.unwrap();
3109 let row = conn.query_row("SELECT trim(' hello ');").await.unwrap();
3110 assert_eq!(row_values(&row), vec![SqliteValue::Text("hello".into())]);
3111 });
3112 }
3113
3114 #[test]
3115 fn builtin_instr() {
3116 asupersync::test_utils::run_test(|| async {
3117 let conn = Connection::open(":memory:").await.unwrap();
3118 let row = conn
3119 .query_row("SELECT instr('hello world', 'world');")
3120 .await
3121 .unwrap();
3122 assert_eq!(row_values(&row), vec![SqliteValue::Integer(7)]);
3123 });
3124 }
3125
3126 #[test]
3127 fn builtin_hex() {
3128 asupersync::test_utils::run_test(|| async {
3129 let conn = Connection::open(":memory:").await.unwrap();
3130 let row = conn.query_row("SELECT hex(X'CAFE');").await.unwrap();
3131 assert_eq!(row_values(&row), vec![SqliteValue::Text("CAFE".into())]);
3132 });
3133 }
3134
3135 #[test]
3138 fn is_null_expression() {
3139 asupersync::test_utils::run_test(|| async {
3140 let conn = Connection::open(":memory:").await.unwrap();
3141 let row = conn.query_row("SELECT NULL IS NULL;").await.unwrap();
3142 assert_eq!(row_values(&row), vec![SqliteValue::Integer(1)]);
3143 let row = conn.query_row("SELECT 42 IS NULL;").await.unwrap();
3144 assert_eq!(row_values(&row), vec![SqliteValue::Integer(0)]);
3145 });
3146 }
3147
3148 #[test]
3151 fn soundex_null_returns_question_marks() {
3152 asupersync::test_utils::run_test(|| async {
3153 let conn = Connection::open(":memory:").await.unwrap();
3154 let row = conn.query_row("SELECT soundex(NULL);").await.unwrap();
3155 assert_eq!(row_values(&row), vec![SqliteValue::Text("?000".into())]);
3156 });
3157 }
3158
3159 #[test]
3162 fn like_underscore_wildcard() {
3163 asupersync::test_utils::run_test(|| async {
3164 let conn = Connection::open(":memory:").await.unwrap();
3165 setup_bd2832(&conn).await;
3166 let rows = conn
3167 .query("SELECT b FROM tp WHERE b LIKE 'b_ta';")
3168 .await
3169 .unwrap();
3170 assert_eq!(rows.len(), 1);
3171 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Text("beta".into())]);
3172 });
3173 }
3174
3175 #[test]
3178 fn where_not_in() {
3179 asupersync::test_utils::run_test(|| async {
3180 let conn = Connection::open(":memory:").await.unwrap();
3181 setup_bd2832(&conn).await;
3182 let rows = conn
3183 .query("SELECT a FROM tp WHERE a NOT IN (1, 3, 5);")
3184 .await
3185 .unwrap();
3186 assert_eq!(rows.len(), 2);
3187 let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
3188 assert!(vals.contains(&SqliteValue::Integer(2)));
3189 assert!(vals.contains(&SqliteValue::Integer(4)));
3190 });
3191 }
3192
3193 #[test]
3194 fn where_in_subquery() {
3195 asupersync::test_utils::run_test(|| async {
3196 let conn = Connection::open(":memory:").await.unwrap();
3197 conn.execute("CREATE TABLE t1 (a INTEGER);").await.unwrap();
3198 conn.execute("CREATE TABLE t2 (b INTEGER);").await.unwrap();
3199 conn.execute("INSERT INTO t1 VALUES (1), (2), (3);")
3200 .await
3201 .unwrap();
3202 conn.execute("INSERT INTO t2 VALUES (2), (3), (4);")
3203 .await
3204 .unwrap();
3205
3206 let rows = conn
3207 .query("SELECT a FROM t1 WHERE a IN (SELECT b FROM t2) ORDER BY a;")
3208 .await
3209 .unwrap();
3210 assert_eq!(rows.len(), 2);
3211 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(2)]);
3212 assert_eq!(row_values(&rows[1]), vec![SqliteValue::Integer(3)]);
3213 });
3214 }
3215
3216 #[test]
3217 fn where_in_table_name() {
3218 asupersync::test_utils::run_test(|| async {
3219 let conn = Connection::open(":memory:").await.unwrap();
3220 conn.execute("CREATE TABLE t1 (a INTEGER);").await.unwrap();
3221 conn.execute("CREATE TABLE t2 (b INTEGER);").await.unwrap();
3222 conn.execute("INSERT INTO t1 VALUES (1), (2), (3);")
3223 .await
3224 .unwrap();
3225 conn.execute("INSERT INTO t2 VALUES (2), (3), (4);")
3226 .await
3227 .unwrap();
3228
3229 let rows = conn
3230 .query("SELECT a FROM t1 WHERE a IN t2 ORDER BY a;")
3231 .await
3232 .unwrap();
3233 assert_eq!(rows.len(), 2);
3234 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(2)]);
3235 assert_eq!(row_values(&rows[1]), vec![SqliteValue::Integer(3)]);
3236 });
3237 }
3238
3239 #[test]
3240 fn where_not_in_table_name() {
3241 asupersync::test_utils::run_test(|| async {
3242 let conn = Connection::open(":memory:").await.unwrap();
3243 conn.execute("CREATE TABLE t1 (a INTEGER);").await.unwrap();
3244 conn.execute("CREATE TABLE t2 (b INTEGER);").await.unwrap();
3245 conn.execute("INSERT INTO t1 VALUES (1), (2), (3);")
3246 .await
3247 .unwrap();
3248 conn.execute("INSERT INTO t2 VALUES (2), (3), (4);")
3249 .await
3250 .unwrap();
3251
3252 let rows = conn
3253 .query("SELECT a FROM t1 WHERE a NOT IN t2 ORDER BY a;")
3254 .await
3255 .unwrap();
3256 assert_eq!(rows.len(), 1);
3257 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(1)]);
3258 });
3259 }
3260
3261 #[test]
3262 fn where_exists_subquery() {
3263 asupersync::test_utils::run_test(|| async {
3264 let conn = Connection::open(":memory:").await.unwrap();
3265 conn.execute("CREATE TABLE t1 (a INTEGER);").await.unwrap();
3266 conn.execute("CREATE TABLE t2 (b INTEGER);").await.unwrap();
3267 conn.execute("INSERT INTO t1 VALUES (1), (2);")
3268 .await
3269 .unwrap();
3270 conn.execute("INSERT INTO t2 VALUES (7);").await.unwrap();
3271
3272 let rows = conn
3273 .query("SELECT a FROM t1 WHERE EXISTS (SELECT b FROM t2) ORDER BY a;")
3274 .await
3275 .unwrap();
3276 assert_eq!(rows.len(), 2);
3277
3278 conn.execute("DELETE FROM t2;").await.unwrap();
3279 let rows = conn
3280 .query("SELECT a FROM t1 WHERE EXISTS (SELECT b FROM t2);")
3281 .await
3282 .unwrap();
3283 assert_eq!(rows.len(), 0);
3284 });
3285 }
3286
3287 #[test]
3288 fn scalar_subquery_expression() {
3289 asupersync::test_utils::run_test(|| async {
3290 let conn = Connection::open(":memory:").await.unwrap();
3291 conn.execute("CREATE TABLE s (v INTEGER);").await.unwrap();
3292 conn.execute("INSERT INTO s VALUES (41);").await.unwrap();
3293
3294 let row = conn
3295 .query_row("SELECT (SELECT v FROM s) + 1;")
3296 .await
3297 .unwrap();
3298 assert_eq!(row_values(&row), vec![SqliteValue::Integer(42)]);
3299 });
3300 }
3301
3302 #[test]
3303 fn update_where_in_table_name() {
3304 asupersync::test_utils::run_test(|| async {
3305 let conn = Connection::open(":memory:").await.unwrap();
3306 conn.execute("CREATE TABLE t1 (a INTEGER, flag TEXT);")
3307 .await
3308 .unwrap();
3309 conn.execute("CREATE TABLE t2 (b INTEGER);").await.unwrap();
3310 conn.execute("INSERT INTO t1 VALUES (1, 'orig'), (2, 'orig'), (3, 'orig');")
3311 .await
3312 .unwrap();
3313 conn.execute("INSERT INTO t2 VALUES (2), (3);")
3314 .await
3315 .unwrap();
3316
3317 conn.execute("UPDATE t1 SET flag='hit' WHERE a IN t2;")
3318 .await
3319 .unwrap();
3320
3321 let rows = conn
3322 .query("SELECT a, flag FROM t1 ORDER BY a;")
3323 .await
3324 .unwrap();
3325 assert_eq!(rows.len(), 3);
3326 assert_eq!(
3327 row_values(&rows[0]),
3328 vec![SqliteValue::Integer(1), SqliteValue::Text("orig".into())]
3329 );
3330 assert_eq!(
3331 row_values(&rows[1]),
3332 vec![SqliteValue::Integer(2), SqliteValue::Text("hit".into())]
3333 );
3334 assert_eq!(
3335 row_values(&rows[2]),
3336 vec![SqliteValue::Integer(3), SqliteValue::Text("hit".into())]
3337 );
3338 });
3339 }
3340
3341 #[test]
3342 fn delete_where_in_table_name() {
3343 asupersync::test_utils::run_test(|| async {
3344 let conn = Connection::open(":memory:").await.unwrap();
3345 conn.execute("CREATE TABLE t1 (a INTEGER);").await.unwrap();
3346 conn.execute("CREATE TABLE t2 (b INTEGER);").await.unwrap();
3347 conn.execute("INSERT INTO t1 VALUES (1), (2), (3);")
3348 .await
3349 .unwrap();
3350 conn.execute("INSERT INTO t2 VALUES (2), (3);")
3351 .await
3352 .unwrap();
3353
3354 conn.execute("DELETE FROM t1 WHERE a IN t2;").await.unwrap();
3355
3356 let rows = conn.query("SELECT a FROM t1 ORDER BY a;").await.unwrap();
3357 assert_eq!(rows.len(), 1);
3358 assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(1)]);
3359 });
3360 }
3361
3362 #[test]
3363 fn where_not_between() {
3364 asupersync::test_utils::run_test(|| async {
3365 let conn = Connection::open(":memory:").await.unwrap();
3366 setup_bd2832(&conn).await;
3367 let rows = conn
3368 .query("SELECT a FROM tp WHERE a NOT BETWEEN 2 AND 4;")
3369 .await
3370 .unwrap();
3371 assert_eq!(rows.len(), 2);
3372 let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
3373 assert!(vals.contains(&SqliteValue::Integer(1)));
3374 assert!(vals.contains(&SqliteValue::Integer(5)));
3375 });
3376 }
3377
3378 #[test]
3381 fn between_null_operand_returns_null() {
3382 asupersync::test_utils::run_test(|| async {
3383 let conn = Connection::open(":memory:").await.unwrap();
3384 let row = conn
3386 .query_row("SELECT NULL BETWEEN 1 AND 5;")
3387 .await
3388 .unwrap();
3389 assert_eq!(row_values(&row), vec![SqliteValue::Null]);
3390 });
3391 }
3392
3393 #[test]
3394 fn between_null_low_bound() {
3395 asupersync::test_utils::run_test(|| async {
3396 let conn = Connection::open(":memory:").await.unwrap();
3397 let row = conn
3399 .query_row("SELECT 3 BETWEEN NULL AND 5;")
3400 .await
3401 .unwrap();
3402 assert_eq!(row_values(&row), vec![SqliteValue::Null]);
3403 });
3404 }
3405
3406 #[test]
3407 fn between_null_high_bound() {
3408 asupersync::test_utils::run_test(|| async {
3409 let conn = Connection::open(":memory:").await.unwrap();
3410 let row = conn
3412 .query_row("SELECT 3 BETWEEN 1 AND NULL;")
3413 .await
3414 .unwrap();
3415 assert_eq!(row_values(&row), vec![SqliteValue::Null]);
3416 });
3417 }
3418
3419 #[test]
3420 fn between_null_bound_out_of_range_returns_false() {
3421 asupersync::test_utils::run_test(|| async {
3422 let conn = Connection::open(":memory:").await.unwrap();
3423 let row = conn
3425 .query_row("SELECT 3 BETWEEN 4 AND NULL;")
3426 .await
3427 .unwrap();
3428 assert_eq!(row_values(&row), vec![SqliteValue::Integer(0)]);
3429 });
3430 }
3431
3432 #[test]
3433 fn in_null_operand_returns_null() {
3434 asupersync::test_utils::run_test(|| async {
3435 let conn = Connection::open(":memory:").await.unwrap();
3436 let row = conn.query_row("SELECT NULL IN (1, 2, 3);").await.unwrap();
3438 assert_eq!(row_values(&row), vec![SqliteValue::Null]);
3439 });
3440 }
3441
3442 #[test]
3443 fn in_list_with_null_no_match_returns_null() {
3444 asupersync::test_utils::run_test(|| async {
3445 let conn = Connection::open(":memory:").await.unwrap();
3446 let row = conn.query_row("SELECT 2 IN (1, NULL, 3);").await.unwrap();
3448 assert_eq!(row_values(&row), vec![SqliteValue::Null]);
3449 });
3450 }
3451
3452 #[test]
3453 fn in_list_with_null_match_returns_true() {
3454 asupersync::test_utils::run_test(|| async {
3455 let conn = Connection::open(":memory:").await.unwrap();
3456 let row = conn.query_row("SELECT 1 IN (1, NULL, 3);").await.unwrap();
3458 assert_eq!(row_values(&row), vec![SqliteValue::Integer(1)]);
3459 });
3460 }
3461
3462 #[test]
3463 fn not_in_null_operand_returns_null() {
3464 asupersync::test_utils::run_test(|| async {
3465 let conn = Connection::open(":memory:").await.unwrap();
3466 let row = conn.query_row("SELECT NULL NOT IN (1, 2);").await.unwrap();
3468 assert_eq!(row_values(&row), vec![SqliteValue::Null]);
3469 });
3470 }
3471
3472 #[test]
3475 fn distinct_table_backed_select() {
3476 asupersync::test_utils::run_test(|| async {
3477 let conn = Connection::open(":memory:").await.unwrap();
3478 conn.execute("CREATE TABLE td (id INTEGER, flag INTEGER);")
3479 .await
3480 .unwrap();
3481 conn.execute("INSERT INTO td VALUES (1, 1);").await.unwrap();
3482 conn.execute("INSERT INTO td VALUES (2, 0);").await.unwrap();
3483 conn.execute("INSERT INTO td VALUES (3, 1);").await.unwrap();
3484 conn.execute("INSERT INTO td VALUES (4, 0);").await.unwrap();
3485 conn.execute("INSERT INTO td VALUES (5, 1);").await.unwrap();
3486
3487 let rows = conn.query("SELECT DISTINCT flag FROM td;").await.unwrap();
3488 assert_eq!(rows.len(), 2);
3489 let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
3490 assert!(vals.contains(&SqliteValue::Integer(0)));
3491 assert!(vals.contains(&SqliteValue::Integer(1)));
3492 });
3493 }
3494
3495 #[test]
3498 fn aggregate_group_by_count() {
3499 asupersync::test_utils::run_test(|| async {
3500 let conn = Connection::open(":memory:").await.unwrap();
3501 conn.execute("CREATE TABLE tg (k TEXT);").await.unwrap();
3502 conn.execute("INSERT INTO tg VALUES ('a');").await.unwrap();
3503 conn.execute("INSERT INTO tg VALUES ('a');").await.unwrap();
3504 conn.execute("INSERT INTO tg VALUES ('b');").await.unwrap();
3505
3506 let rows = conn
3507 .query("SELECT k, COUNT(*) FROM tg GROUP BY k ORDER BY k;")
3508 .await
3509 .unwrap();
3510 assert_eq!(rows.len(), 2);
3511 assert_eq!(
3512 row_values(&rows[0]),
3513 vec![SqliteValue::Text("a".into()), SqliteValue::Integer(2)]
3514 );
3515 assert_eq!(
3516 row_values(&rows[1]),
3517 vec![SqliteValue::Text("b".into()), SqliteValue::Integer(1)]
3518 );
3519 });
3520 }
3521
3522 #[test]
3523 fn group_by_alias_star_expansion() {
3524 asupersync::test_utils::run_test(|| async {
3525 let conn = Connection::open(":memory:").await.unwrap();
3526 conn.execute("CREATE TABLE ga (k TEXT, v INTEGER);")
3527 .await
3528 .unwrap();
3529 conn.execute("INSERT INTO ga VALUES ('a', 10);")
3530 .await
3531 .unwrap();
3532 conn.execute("INSERT INTO ga VALUES ('a', 10);")
3533 .await
3534 .unwrap();
3535 conn.execute("INSERT INTO ga VALUES ('b', 20);")
3536 .await
3537 .unwrap();
3538
3539 let rows = conn
3540 .query("SELECT t.* FROM ga AS t GROUP BY t.k, t.v ORDER BY t.k, t.v;")
3541 .await
3542 .unwrap();
3543 assert_eq!(rows.len(), 2);
3544 assert_eq!(
3545 row_values(&rows[0]),
3546 vec![SqliteValue::Text("a".into()), SqliteValue::Integer(10)]
3547 );
3548 assert_eq!(
3549 row_values(&rows[1]),
3550 vec![SqliteValue::Text("b".into()), SqliteValue::Integer(20)]
3551 );
3552 });
3553 }
3554
3555 #[test]
3556 fn right_join_null_extension() {
3557 asupersync::test_utils::run_test(|| async {
3558 let conn = Connection::open(":memory:").await.unwrap();
3559 conn.execute("CREATE TABLE l (id INTEGER, name TEXT);")
3560 .await
3561 .unwrap();
3562 conn.execute("CREATE TABLE r (l_id INTEGER, tag TEXT);")
3563 .await
3564 .unwrap();
3565 conn.execute("INSERT INTO l VALUES (1, 'left-a'), (2, 'left-b');")
3566 .await
3567 .unwrap();
3568 conn.execute("INSERT INTO r VALUES (2, 'right-b'), (3, 'right-c');")
3569 .await
3570 .unwrap();
3571
3572 let rows = conn
3573 .query("SELECT l.name, r.tag FROM l RIGHT JOIN r ON l.id = r.l_id;")
3574 .await
3575 .unwrap();
3576 assert_eq!(rows.len(), 2);
3577
3578 let projected: Vec<Vec<SqliteValue>> = rows.iter().map(row_values).collect();
3579 assert!(projected.contains(&vec![
3580 SqliteValue::Text("left-b".into()),
3581 SqliteValue::Text("right-b".into())
3582 ]));
3583 assert!(projected.contains(&vec![
3584 SqliteValue::Null,
3585 SqliteValue::Text("right-c".into())
3586 ]));
3587 });
3588 }
3589
3590 #[test]
3591 fn full_outer_join_null_extension() {
3592 asupersync::test_utils::run_test(|| async {
3593 let conn = Connection::open(":memory:").await.unwrap();
3594 conn.execute("CREATE TABLE l (id INTEGER, name TEXT);")
3595 .await
3596 .unwrap();
3597 conn.execute("CREATE TABLE r (l_id INTEGER, tag TEXT);")
3598 .await
3599 .unwrap();
3600 conn.execute("INSERT INTO l VALUES (1, 'left-a'), (2, 'left-b');")
3601 .await
3602 .unwrap();
3603 conn.execute("INSERT INTO r VALUES (2, 'right-b'), (3, 'right-c');")
3604 .await
3605 .unwrap();
3606
3607 let rows = conn
3608 .query("SELECT l.name, r.tag FROM l FULL OUTER JOIN r ON l.id = r.l_id;")
3609 .await
3610 .unwrap();
3611 assert_eq!(rows.len(), 3);
3612
3613 let projected: Vec<Vec<SqliteValue>> = rows.iter().map(row_values).collect();
3614 assert!(
3615 projected.contains(&vec![SqliteValue::Text("left-a".into()), SqliteValue::Null])
3616 );
3617 assert!(projected.contains(&vec![
3618 SqliteValue::Text("left-b".into()),
3619 SqliteValue::Text("right-b".into())
3620 ]));
3621 assert!(projected.contains(&vec![
3622 SqliteValue::Null,
3623 SqliteValue::Text("right-c".into())
3624 ]));
3625 });
3626 }
3627
3628 #[test]
3629 fn right_join_using_nulls_do_not_match() {
3630 asupersync::test_utils::run_test(|| async {
3631 let conn = Connection::open(":memory:").await.unwrap();
3632 conn.execute("CREATE TABLE l (id INTEGER, name TEXT);")
3633 .await
3634 .unwrap();
3635 conn.execute("CREATE TABLE r (id INTEGER, tag TEXT);")
3636 .await
3637 .unwrap();
3638 conn.execute("INSERT INTO l VALUES (NULL, 'left-null'), (1, 'left-one');")
3639 .await
3640 .unwrap();
3641 conn.execute("INSERT INTO r VALUES (NULL, 'right-null'), (1, 'right-one');")
3642 .await
3643 .unwrap();
3644
3645 let rows = conn
3646 .query("SELECT l.name, r.tag FROM l RIGHT JOIN r USING (id);")
3647 .await
3648 .unwrap();
3649 assert_eq!(rows.len(), 2);
3650 let projected: Vec<Vec<SqliteValue>> = rows.iter().map(row_values).collect();
3651 assert!(projected.contains(&vec![
3652 SqliteValue::Text("left-one".into()),
3653 SqliteValue::Text("right-one".into())
3654 ]));
3655 assert!(projected.contains(&vec![
3656 SqliteValue::Null,
3657 SqliteValue::Text("right-null".into())
3658 ]));
3659 });
3660 }
3661
3662 #[test]
3663 fn aggregate_group_by_sum() {
3664 asupersync::test_utils::run_test(|| async {
3665 let conn = Connection::open(":memory:").await.unwrap();
3666 conn.execute("CREATE TABLE gs (dept TEXT, salary INTEGER);")
3667 .await
3668 .unwrap();
3669 conn.execute("INSERT INTO gs VALUES ('eng', 100);")
3670 .await
3671 .unwrap();
3672 conn.execute("INSERT INTO gs VALUES ('eng', 200);")
3673 .await
3674 .unwrap();
3675 conn.execute("INSERT INTO gs VALUES ('sales', 50);")
3676 .await
3677 .unwrap();
3678
3679 let rows = conn
3680 .query("SELECT dept, SUM(salary) FROM gs GROUP BY dept;")
3681 .await
3682 .unwrap();
3683 assert_eq!(rows.len(), 2);
3684 let vals: Vec<(SqliteValue, SqliteValue)> = rows
3685 .iter()
3686 .map(|r| {
3687 let v = row_values(r);
3688 (v[0].clone(), v[1].clone())
3689 })
3690 .collect();
3691 assert!(vals.contains(&(SqliteValue::Text("eng".into()), SqliteValue::Integer(300))));
3692 assert!(vals.contains(&(SqliteValue::Text("sales".into()), SqliteValue::Integer(50))));
3693 });
3694 }
3695
3696 #[test]
3697 fn aggregate_group_by_multiple_aggs() {
3698 asupersync::test_utils::run_test(|| async {
3699 let conn = Connection::open(":memory:").await.unwrap();
3700 conn.execute("CREATE TABLE gm (cat TEXT, val INTEGER);")
3701 .await
3702 .unwrap();
3703 conn.execute("INSERT INTO gm VALUES ('a', 10);")
3704 .await
3705 .unwrap();
3706 conn.execute("INSERT INTO gm VALUES ('a', 20);")
3707 .await
3708 .unwrap();
3709 conn.execute("INSERT INTO gm VALUES ('a', 30);")
3710 .await
3711 .unwrap();
3712 conn.execute("INSERT INTO gm VALUES ('b', 5);")
3713 .await
3714 .unwrap();
3715
3716 let rows = conn
3717 .query("SELECT cat, COUNT(*), MIN(val), MAX(val) FROM gm GROUP BY cat;")
3718 .await
3719 .unwrap();
3720 assert_eq!(rows.len(), 2);
3721 let a_row = rows
3722 .iter()
3723 .find(|r| row_values(r)[0] == SqliteValue::Text("a".into()))
3724 .unwrap();
3725 assert_eq!(
3726 row_values(a_row),
3727 vec![
3728 SqliteValue::Text("a".into()),
3729 SqliteValue::Integer(3),
3730 SqliteValue::Integer(10),
3731 SqliteValue::Integer(30),
3732 ]
3733 );
3734 let b_row = rows
3735 .iter()
3736 .find(|r| row_values(r)[0] == SqliteValue::Text("b".into()))
3737 .unwrap();
3738 assert_eq!(
3739 row_values(b_row),
3740 vec![
3741 SqliteValue::Text("b".into()),
3742 SqliteValue::Integer(1),
3743 SqliteValue::Integer(5),
3744 SqliteValue::Integer(5),
3745 ]
3746 );
3747 });
3748 }
3749
3750 #[test]
3753 fn aggregate_count_column_excludes_null() {
3754 asupersync::test_utils::run_test(|| async {
3755 let conn = Connection::open(":memory:").await.unwrap();
3756 setup_bd2832(&conn).await;
3757 let row = conn.query_row("SELECT count(b) FROM tp;").await.unwrap();
3758 assert_eq!(row_values(&row), vec![SqliteValue::Integer(4)]);
3759 });
3760 }
3761
3762 #[test]
3765 fn execute_with_params_insert_returns_count() {
3766 asupersync::test_utils::run_test(|| async {
3767 let conn = Connection::open(":memory:").await.unwrap();
3768 conn.execute("CREATE TABLE ewp (v INTEGER);").await.unwrap();
3769 let count = conn
3770 .execute_with_params("INSERT INTO ewp VALUES (?1);", &[SqliteValue::Integer(42)])
3771 .await
3772 .unwrap();
3773 assert_eq!(count, 1, "INSERT via execute_with_params should return 1");
3774 });
3775 }
3776
3777 #[test]
3778 fn execute_with_params_insert_respects_explicit_column_order() {
3779 asupersync::test_utils::run_test(|| async {
3780 let conn = Connection::open(":memory:").await.unwrap();
3781 conn.execute(
3782 "CREATE TABLE message_payloads(
3783 id INTEGER PRIMARY KEY,
3784 attachments TEXT NOT NULL DEFAULT '[]',
3785 recipients_json TEXT NOT NULL DEFAULT '{}'
3786 );",
3787 )
3788 .await
3789 .unwrap();
3790
3791 let recipients = r#"{"to":["BlueLake"],"cc":[],"bcc":[]}"#;
3792 let attachments = r#"[{"name":"artifact.txt","path":"attachments/demo.txt","content_type":"text/plain","size":"128"}]"#;
3793
3794 conn.execute_with_params(
3795 "INSERT INTO message_payloads(recipients_json, attachments) VALUES (?1, ?2);",
3796 &[
3797 SqliteValue::Text(recipients.into()),
3798 SqliteValue::Text(attachments.into()),
3799 ],
3800 )
3801 .await
3802 .unwrap();
3803
3804 let row = conn
3805 .query_row("SELECT recipients_json, attachments FROM message_payloads LIMIT 1;")
3806 .await
3807 .unwrap();
3808 assert_eq!(
3809 row_values(&row),
3810 vec![
3811 SqliteValue::Text(recipients.into()),
3812 SqliteValue::Text(attachments.into())
3813 ]
3814 );
3815 });
3816 }
3817
3818 #[test]
3819 fn execute_with_params_insert_duplicate_target_columns_keep_first_assignment() {
3820 asupersync::test_utils::run_test(|| async {
3821 let conn = Connection::open(":memory:").await.unwrap();
3822 conn.execute("CREATE TABLE dup_targets(a INTEGER, b INTEGER);")
3823 .await
3824 .unwrap();
3825
3826 conn.execute_with_params(
3827 "INSERT INTO dup_targets(a, a, b) VALUES (?1, ?2, ?3);",
3828 &[
3829 SqliteValue::Integer(1),
3830 SqliteValue::Integer(2),
3831 SqliteValue::Integer(3),
3832 ],
3833 )
3834 .await
3835 .unwrap();
3836
3837 let row = conn
3838 .query_row("SELECT a, b FROM dup_targets;")
3839 .await
3840 .unwrap();
3841 assert_eq!(
3842 row_values(&row),
3843 vec![SqliteValue::Integer(1), SqliteValue::Integer(3)]
3844 );
3845 });
3846 }
3847
3848 #[test]
3849 fn execute_select_returns_row_count() {
3850 asupersync::test_utils::run_test(|| async {
3851 let conn = Connection::open(":memory:").await.unwrap();
3852 conn.execute("CREATE TABLE es (v INTEGER);").await.unwrap();
3853 conn.execute("INSERT INTO es VALUES (1);").await.unwrap();
3854 conn.execute("INSERT INTO es VALUES (2);").await.unwrap();
3855 let count = conn.execute("SELECT * FROM es;").await.unwrap();
3856 assert_eq!(count, 2, "SELECT via execute() should return row count");
3857 });
3858 }
3859
3860 #[test]
3863 fn savepoint_release_ends_implicit_transaction() {
3864 asupersync::test_utils::run_test(|| async {
3865 let conn = Connection::open(":memory:").await.unwrap();
3866 conn.execute("CREATE TABLE sr (v INTEGER);").await.unwrap();
3867
3868 conn.execute("SAVEPOINT sp1;").await.unwrap();
3870 assert!(conn.in_transaction());
3871 conn.execute("INSERT INTO sr VALUES (1);").await.unwrap();
3872
3873 conn.execute("RELEASE sp1;").await.unwrap();
3875 assert!(
3876 !conn.in_transaction(),
3877 "RELEASE of last implicit savepoint should end transaction"
3878 );
3879
3880 let rows = conn.query("SELECT * FROM sr;").await.unwrap();
3882 assert_eq!(rows.len(), 1);
3883 });
3884 }
3885
3886 #[test]
3887 fn explicit_begin_savepoint_release_keeps_transaction() {
3888 asupersync::test_utils::run_test(|| async {
3889 let conn = Connection::open(":memory:").await.unwrap();
3890 conn.execute("CREATE TABLE bsr (v INTEGER);").await.unwrap();
3891
3892 conn.execute("BEGIN;").await.unwrap();
3894 conn.execute("SAVEPOINT sp1;").await.unwrap();
3895 conn.execute("INSERT INTO bsr VALUES (1);").await.unwrap();
3896 conn.execute("RELEASE sp1;").await.unwrap();
3897
3898 assert!(
3900 conn.in_transaction(),
3901 "RELEASE after explicit BEGIN should not end the transaction"
3902 );
3903 conn.execute("COMMIT;").await.unwrap();
3904 assert!(!conn.in_transaction());
3905 });
3906 }
3907
3908 #[test]
3911 fn probe_update_self_ref_expr() {
3912 asupersync::test_utils::run_test(|| async {
3913 let conn = Connection::open(":memory:").await.unwrap();
3914 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val INTEGER);")
3915 .await
3916 .unwrap();
3917 conn.execute("INSERT INTO t VALUES (1, 10);").await.unwrap();
3918 conn.execute("INSERT INTO t VALUES (2, 20);").await.unwrap();
3919 conn.execute("UPDATE t SET val = val + 5;").await.unwrap();
3920 let rows = conn
3921 .query("SELECT id, val FROM t ORDER BY id;")
3922 .await
3923 .unwrap();
3924 assert_eq!(
3925 row_values(&rows[0]),
3926 vec![SqliteValue::Integer(1), SqliteValue::Integer(15)]
3927 );
3928 assert_eq!(
3929 row_values(&rows[1]),
3930 vec![SqliteValue::Integer(2), SqliteValue::Integer(25)]
3931 );
3932 });
3933 }
3934
3935 #[test]
3936 fn probe_delete_compound_where() {
3937 asupersync::test_utils::run_test(|| async {
3938 let conn = Connection::open(":memory:").await.unwrap();
3939 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
3940 .await
3941 .unwrap();
3942 conn.execute("INSERT INTO t VALUES (1, 'a');")
3943 .await
3944 .unwrap();
3945 conn.execute("INSERT INTO t VALUES (2, 'b');")
3946 .await
3947 .unwrap();
3948 conn.execute("INSERT INTO t VALUES (3, 'c');")
3949 .await
3950 .unwrap();
3951 conn.execute("DELETE FROM t WHERE id > 1 AND val = 'b';")
3952 .await
3953 .unwrap();
3954 let rows = conn.query("SELECT id FROM t ORDER BY id;").await.unwrap();
3955 assert_eq!(rows.len(), 2);
3956 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
3957 assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(3));
3958 });
3959 }
3960
3961 #[test]
3962 fn probe_coalesce_nulls() {
3963 asupersync::test_utils::run_test(|| async {
3964 let conn = Connection::open(":memory:").await.unwrap();
3965 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, a TEXT, b TEXT);")
3966 .await
3967 .unwrap();
3968 conn.execute("INSERT INTO t VALUES (1, NULL, 'fallback');")
3969 .await
3970 .unwrap();
3971 conn.execute("INSERT INTO t VALUES (2, 'present', 'fallback');")
3972 .await
3973 .unwrap();
3974 let rows = conn
3975 .query("SELECT id, COALESCE(a, b) FROM t ORDER BY id;")
3976 .await
3977 .unwrap();
3978 assert_eq!(rows.len(), 2);
3979 assert_eq!(
3980 row_values(&rows[0])[1],
3981 SqliteValue::Text("fallback".into())
3982 );
3983 assert_eq!(row_values(&rows[1])[1], SqliteValue::Text("present".into()));
3984 });
3985 }
3986
3987 #[test]
3988 fn probe_case_when_null() {
3989 asupersync::test_utils::run_test(|| async {
3990 let conn = Connection::open(":memory:").await.unwrap();
3991 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val INTEGER);")
3992 .await
3993 .unwrap();
3994 conn.execute("INSERT INTO t VALUES (1, NULL);")
3995 .await
3996 .unwrap();
3997 conn.execute("INSERT INTO t VALUES (2, 5);").await.unwrap();
3998 conn.execute("INSERT INTO t VALUES (3, 15);").await.unwrap();
3999 let rows = conn
4000 .query(
4001 "SELECT id, CASE WHEN val IS NULL THEN 'null' WHEN val < 10 THEN 'small' ELSE 'big' END FROM t ORDER BY id;",
4002 )
4003 .await
4004 .unwrap();
4005 assert_eq!(rows.len(), 3);
4006 assert_eq!(row_values(&rows[0])[1], SqliteValue::Text("null".into()));
4007 assert_eq!(row_values(&rows[1])[1], SqliteValue::Text("small".into()));
4008 assert_eq!(row_values(&rows[2])[1], SqliteValue::Text("big".into()));
4009 });
4010 }
4011
4012 #[test]
4013 fn probe_union_all() {
4014 asupersync::test_utils::run_test(|| async {
4015 let conn = Connection::open(":memory:").await.unwrap();
4016 conn.execute("CREATE TABLE t1 (id INTEGER PRIMARY KEY, val TEXT);")
4017 .await
4018 .unwrap();
4019 conn.execute("CREATE TABLE t2 (id INTEGER PRIMARY KEY, val TEXT);")
4020 .await
4021 .unwrap();
4022 conn.execute("INSERT INTO t1 VALUES (1, 'a');")
4023 .await
4024 .unwrap();
4025 conn.execute("INSERT INTO t2 VALUES (2, 'b');")
4026 .await
4027 .unwrap();
4028 let rows = conn
4029 .query("SELECT val FROM t1 UNION ALL SELECT val FROM t2;")
4030 .await
4031 .unwrap();
4032 assert_eq!(rows.len(), 2);
4033 });
4034 }
4035
4036 #[test]
4037 fn probe_union_dedup() {
4038 asupersync::test_utils::run_test(|| async {
4039 let conn = Connection::open(":memory:").await.unwrap();
4040 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
4041 .await
4042 .unwrap();
4043 conn.execute("INSERT INTO t VALUES (1, 'a');")
4044 .await
4045 .unwrap();
4046 conn.execute("INSERT INTO t VALUES (2, 'a');")
4047 .await
4048 .unwrap();
4049 conn.execute("INSERT INTO t VALUES (3, 'b');")
4050 .await
4051 .unwrap();
4052 let rows = conn
4053 .query("SELECT val FROM t UNION SELECT val FROM t;")
4054 .await
4055 .unwrap();
4056 assert_eq!(
4057 rows.len(),
4058 2,
4059 "UNION should deduplicate: got {:?}",
4060 rows.iter().map(row_values).collect::<Vec<_>>()
4061 );
4062 });
4063 }
4064
4065 #[test]
4066 fn probe_except() {
4067 asupersync::test_utils::run_test(|| async {
4068 let conn = Connection::open(":memory:").await.unwrap();
4069 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
4070 .await
4071 .unwrap();
4072 conn.execute("INSERT INTO t VALUES (1, 'a');")
4073 .await
4074 .unwrap();
4075 conn.execute("INSERT INTO t VALUES (2, 'b');")
4076 .await
4077 .unwrap();
4078 conn.execute("INSERT INTO t VALUES (3, 'c');")
4079 .await
4080 .unwrap();
4081 let rows = conn
4082 .query("SELECT val FROM t EXCEPT SELECT val FROM t WHERE id = 2;")
4083 .await
4084 .unwrap();
4085 assert_eq!(
4086 rows.len(),
4087 2,
4088 "EXCEPT should remove 'b': got {:?}",
4089 rows.iter().map(row_values).collect::<Vec<_>>()
4090 );
4091 });
4092 }
4093
4094 #[test]
4095 fn probe_intersect() {
4096 asupersync::test_utils::run_test(|| async {
4097 let conn = Connection::open(":memory:").await.unwrap();
4098 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
4099 .await
4100 .unwrap();
4101 conn.execute("INSERT INTO t VALUES (1, 'a');")
4102 .await
4103 .unwrap();
4104 conn.execute("INSERT INTO t VALUES (2, 'b');")
4105 .await
4106 .unwrap();
4107 conn.execute("INSERT INTO t VALUES (3, 'c');")
4108 .await
4109 .unwrap();
4110 let rows = conn
4111 .query("SELECT val FROM t INTERSECT SELECT val FROM t WHERE id <= 2;")
4112 .await
4113 .unwrap();
4114 assert_eq!(
4115 rows.len(),
4116 2,
4117 "INTERSECT should keep 'a' and 'b': got {:?}",
4118 rows.iter().map(row_values).collect::<Vec<_>>()
4119 );
4120 });
4121 }
4122
4123 #[test]
4124 fn probe_insert_select() {
4125 asupersync::test_utils::run_test(|| async {
4126 let conn = Connection::open(":memory:").await.unwrap();
4127 conn.execute("CREATE TABLE src (id INTEGER PRIMARY KEY, val TEXT);")
4128 .await
4129 .unwrap();
4130 conn.execute("CREATE TABLE dst (id INTEGER PRIMARY KEY, val TEXT);")
4131 .await
4132 .unwrap();
4133 conn.execute("INSERT INTO src VALUES (1, 'a');")
4134 .await
4135 .unwrap();
4136 conn.execute("INSERT INTO src VALUES (2, 'b');")
4137 .await
4138 .unwrap();
4139 conn.execute("INSERT INTO dst SELECT * FROM src;")
4140 .await
4141 .unwrap();
4142 let rows = conn
4143 .query("SELECT id, val FROM dst ORDER BY id;")
4144 .await
4145 .unwrap();
4146 assert_eq!(rows.len(), 2);
4147 assert_eq!(row_values(&rows[0])[1], SqliteValue::Text("a".into()));
4148 });
4149 }
4150
4151 #[test]
4152 fn probe_limit_offset() {
4153 asupersync::test_utils::run_test(|| async {
4154 let conn = Connection::open(":memory:").await.unwrap();
4155 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
4156 .await
4157 .unwrap();
4158 for i in 1..=10 {
4159 conn.execute(&format!("INSERT INTO t VALUES ({i}, 'v{i}');"))
4160 .await
4161 .unwrap();
4162 }
4163 let rows = conn
4164 .query("SELECT id FROM t ORDER BY id LIMIT 3 OFFSET 2;")
4165 .await
4166 .unwrap();
4167 assert_eq!(rows.len(), 3, "LIMIT 3 OFFSET 2 should return 3 rows");
4168 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(3));
4169 assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(4));
4170 assert_eq!(row_values(&rows[2])[0], SqliteValue::Integer(5));
4171 });
4172 }
4173
4174 #[test]
4175 fn probe_group_by_multi_col() {
4176 asupersync::test_utils::run_test(|| async {
4177 let conn = Connection::open(":memory:").await.unwrap();
4178 conn.execute(
4179 "CREATE TABLE t (id INTEGER PRIMARY KEY, cat TEXT, sub TEXT, val INTEGER);",
4180 )
4181 .await
4182 .unwrap();
4183 conn.execute("INSERT INTO t VALUES (1, 'A', 'x', 10);")
4184 .await
4185 .unwrap();
4186 conn.execute("INSERT INTO t VALUES (2, 'A', 'x', 20);")
4187 .await
4188 .unwrap();
4189 conn.execute("INSERT INTO t VALUES (3, 'A', 'y', 30);")
4190 .await
4191 .unwrap();
4192 conn.execute("INSERT INTO t VALUES (4, 'B', 'x', 40);")
4193 .await
4194 .unwrap();
4195 let rows = conn
4196 .query("SELECT cat, sub, SUM(val) FROM t GROUP BY cat, sub ORDER BY cat, sub;")
4197 .await
4198 .unwrap();
4199 assert_eq!(
4200 rows.len(),
4201 3,
4202 "Should have 3 groups: got {:?}",
4203 rows.iter().map(row_values).collect::<Vec<_>>()
4204 );
4205 assert_eq!(row_values(&rows[0])[2], SqliteValue::Integer(30));
4206 assert_eq!(row_values(&rows[1])[2], SqliteValue::Integer(30));
4207 assert_eq!(row_values(&rows[2])[2], SqliteValue::Integer(40));
4208 });
4209 }
4210
4211 #[test]
4212 fn probe_having_aggregate() {
4213 asupersync::test_utils::run_test(|| async {
4214 let conn = Connection::open(":memory:").await.unwrap();
4215 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, cat TEXT, val INTEGER);")
4216 .await
4217 .unwrap();
4218 conn.execute("INSERT INTO t VALUES (1, 'A', 10);")
4219 .await
4220 .unwrap();
4221 conn.execute("INSERT INTO t VALUES (2, 'A', 20);")
4222 .await
4223 .unwrap();
4224 conn.execute("INSERT INTO t VALUES (3, 'B', 30);")
4225 .await
4226 .unwrap();
4227 let rows = conn
4228 .query("SELECT cat, COUNT(*) as cnt FROM t GROUP BY cat HAVING cnt > 1;")
4229 .await
4230 .unwrap();
4231 assert_eq!(rows.len(), 1, "Only group A has count > 1");
4232 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("A".into()));
4233 });
4234 }
4235
4236 #[test]
4237 fn having_between_filters_groups() {
4238 asupersync::test_utils::run_test(|| async {
4239 let conn = Connection::open(":memory:").await.unwrap();
4240 conn.execute("CREATE TABLE hb (grp INTEGER, val INTEGER);")
4241 .await
4242 .unwrap();
4243 conn.execute(
4244 "INSERT INTO hb VALUES (1, 10), (1, 20), (2, 30), (3, 40), (3, 50), (3, 60);",
4245 )
4246 .await
4247 .unwrap();
4248 let rows = conn
4250 .query(
4251 "SELECT grp, COUNT(*) as cnt FROM hb GROUP BY grp HAVING cnt BETWEEN 2 AND 3;",
4252 )
4253 .await
4254 .unwrap();
4255 assert_eq!(rows.len(), 2);
4256 let grps: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
4257 assert!(grps.contains(&SqliteValue::Integer(1)));
4258 assert!(grps.contains(&SqliteValue::Integer(3)));
4259 });
4260 }
4261
4262 #[test]
4263 fn having_in_filters_groups() {
4264 asupersync::test_utils::run_test(|| async {
4265 let conn = Connection::open(":memory:").await.unwrap();
4266 conn.execute("CREATE TABLE hi (grp TEXT, val INTEGER);")
4267 .await
4268 .unwrap();
4269 conn.execute("INSERT INTO hi VALUES ('A', 1), ('B', 2), ('C', 3);")
4270 .await
4271 .unwrap();
4272 let rows = conn
4273 .query("SELECT grp FROM hi GROUP BY grp HAVING grp IN ('A', 'C');")
4274 .await
4275 .unwrap();
4276 assert_eq!(rows.len(), 2);
4277 let grps: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
4278 assert!(grps.contains(&SqliteValue::Text("A".into())));
4279 assert!(grps.contains(&SqliteValue::Text("C".into())));
4280 });
4281 }
4282
4283 #[test]
4284 fn having_case_expression() {
4285 asupersync::test_utils::run_test(|| async {
4286 let conn = Connection::open(":memory:").await.unwrap();
4287 conn.execute("CREATE TABLE hc (grp TEXT, val INTEGER);")
4288 .await
4289 .unwrap();
4290 conn.execute("INSERT INTO hc VALUES ('X', 1), ('Y', 2), ('X', 3);")
4291 .await
4292 .unwrap();
4293 let rows = conn
4295 .query("SELECT grp, SUM(val) FROM hc GROUP BY grp HAVING CASE grp WHEN 'X' THEN 1 ELSE 0 END = 1;")
4296 .await
4297 .unwrap();
4298 assert_eq!(rows.len(), 1);
4299 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("X".into()));
4300 });
4301 }
4302
4303 #[test]
4304 fn like_null_operand_returns_null() {
4305 asupersync::test_utils::run_test(|| async {
4306 let conn = Connection::open(":memory:").await.unwrap();
4307 let rows = conn.query("SELECT NULL LIKE 'abc';").await.unwrap();
4308 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
4309 });
4310 }
4311
4312 #[test]
4313 fn like_null_pattern_returns_null() {
4314 asupersync::test_utils::run_test(|| async {
4315 let conn = Connection::open(":memory:").await.unwrap();
4316 let rows = conn.query("SELECT 'abc' LIKE NULL;").await.unwrap();
4317 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
4318 });
4319 }
4320
4321 #[test]
4322 fn like_null_both_returns_null() {
4323 asupersync::test_utils::run_test(|| async {
4324 let conn = Connection::open(":memory:").await.unwrap();
4325 let rows = conn.query("SELECT NULL LIKE NULL;").await.unwrap();
4326 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
4327 });
4328 }
4329
4330 #[test]
4331 fn not_like_null_returns_null() {
4332 asupersync::test_utils::run_test(|| async {
4333 let conn = Connection::open(":memory:").await.unwrap();
4334 let rows = conn.query("SELECT 'abc' NOT LIKE NULL;").await.unwrap();
4335 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
4336 });
4337 }
4338
4339 #[test]
4340 fn like_integer_coercion() {
4341 asupersync::test_utils::run_test(|| async {
4342 let conn = Connection::open(":memory:").await.unwrap();
4343 let rows = conn.query("SELECT 123 LIKE '123';").await.unwrap();
4345 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
4346 });
4347 }
4348
4349 #[test]
4350 fn like_null_in_join_where() {
4351 asupersync::test_utils::run_test(|| async {
4352 let conn = Connection::open(":memory:").await.unwrap();
4353 conn.execute("CREATE TABLE lnj (id INTEGER PRIMARY KEY, name TEXT);")
4354 .await
4355 .unwrap();
4356 conn.execute("INSERT INTO lnj VALUES (1, 'alice'), (2, NULL), (3, 'bob');")
4357 .await
4358 .unwrap();
4359 let rows = conn
4361 .query("SELECT id FROM lnj WHERE name LIKE '%' ORDER BY id;")
4362 .await
4363 .unwrap();
4364 assert_eq!(rows.len(), 2);
4365 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
4366 assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(3));
4367 });
4368 }
4369
4370 #[test]
4371 fn having_like_filters_groups() {
4372 asupersync::test_utils::run_test(|| async {
4373 let conn = Connection::open(":memory:").await.unwrap();
4374 conn.execute("CREATE TABLE hlk (grp TEXT, val INTEGER);")
4375 .await
4376 .unwrap();
4377 conn.execute("INSERT INTO hlk VALUES ('apple', 1), ('banana', 2), ('apricot', 3);")
4378 .await
4379 .unwrap();
4380 let rows = conn
4382 .query("SELECT grp, SUM(val) FROM hlk GROUP BY grp HAVING grp LIKE 'ap%';")
4383 .await
4384 .unwrap();
4385 assert_eq!(rows.len(), 2);
4386 let grps: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
4387 assert!(grps.contains(&SqliteValue::Text("apple".into())));
4388 assert!(grps.contains(&SqliteValue::Text("apricot".into())));
4389 });
4390 }
4391
4392 #[test]
4393 fn case_null_base_does_not_match_null() {
4394 asupersync::test_utils::run_test(|| async {
4395 let conn = Connection::open(":memory:").await.unwrap();
4396 let rows = conn
4398 .query("SELECT CASE NULL WHEN NULL THEN 'match' ELSE 'no match' END;")
4399 .await
4400 .unwrap();
4401 assert_eq!(
4402 row_values(&rows[0])[0],
4403 SqliteValue::Text("no match".into())
4404 );
4405 });
4406 }
4407
4408 #[test]
4409 fn case_null_base_skips_all_whens() {
4410 asupersync::test_utils::run_test(|| async {
4411 let conn = Connection::open(":memory:").await.unwrap();
4412 let rows = conn
4413 .query("SELECT CASE NULL WHEN 1 THEN 'one' WHEN 2 THEN 'two' ELSE 'none' END;")
4414 .await
4415 .unwrap();
4416 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("none".into()));
4417 });
4418 }
4419
4420 #[test]
4421 fn case_null_when_value_skipped() {
4422 asupersync::test_utils::run_test(|| async {
4423 let conn = Connection::open(":memory:").await.unwrap();
4424 let rows = conn
4426 .query("SELECT CASE 1 WHEN NULL THEN 'bad' WHEN 1 THEN 'ok' ELSE 'miss' END;")
4427 .await
4428 .unwrap();
4429 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("ok".into()));
4430 });
4431 }
4432
4433 #[test]
4434 fn case_null_in_join_filter() {
4435 asupersync::test_utils::run_test(|| async {
4436 let conn = Connection::open(":memory:").await.unwrap();
4437 conn.execute("CREATE TABLE cj (id INTEGER PRIMARY KEY, val TEXT);")
4438 .await
4439 .unwrap();
4440 conn.execute("INSERT INTO cj VALUES (1, NULL), (2, 'x'), (3, 'y');")
4441 .await
4442 .unwrap();
4443 let rows = conn
4445 .query(
4446 "SELECT id, CASE val WHEN 'x' THEN 'found' ELSE 'other' END AS r FROM cj ORDER BY id;",
4447 )
4448 .await
4449 .unwrap();
4450 assert_eq!(row_values(&rows[0])[1], SqliteValue::Text("other".into()));
4451 assert_eq!(row_values(&rows[1])[1], SqliteValue::Text("found".into()));
4452 });
4453 }
4454
4455 #[test]
4456 fn cast_null_as_integer_returns_null() {
4457 asupersync::test_utils::run_test(|| async {
4458 let conn = Connection::open(":memory:").await.unwrap();
4459 let rows = conn.query("SELECT CAST(NULL AS INTEGER);").await.unwrap();
4460 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
4461 });
4462 }
4463
4464 #[test]
4465 fn cast_null_as_real_returns_null() {
4466 asupersync::test_utils::run_test(|| async {
4467 let conn = Connection::open(":memory:").await.unwrap();
4468 let rows = conn.query("SELECT CAST(NULL AS REAL);").await.unwrap();
4469 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
4470 });
4471 }
4472
4473 #[test]
4474 fn cast_null_as_text_returns_null() {
4475 asupersync::test_utils::run_test(|| async {
4476 let conn = Connection::open(":memory:").await.unwrap();
4477 let rows = conn.query("SELECT CAST(NULL AS TEXT);").await.unwrap();
4478 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
4479 });
4480 }
4481
4482 #[test]
4483 fn cast_null_from_table_returns_null() {
4484 asupersync::test_utils::run_test(|| async {
4485 let conn = Connection::open(":memory:").await.unwrap();
4486 conn.execute("CREATE TABLE cn (id INTEGER PRIMARY KEY, val TEXT);")
4487 .await
4488 .unwrap();
4489 conn.execute("INSERT INTO cn VALUES (1, NULL), (2, '5');")
4490 .await
4491 .unwrap();
4492 let rows = conn
4494 .query("SELECT id, CAST(val AS INTEGER) FROM cn ORDER BY id;")
4495 .await
4496 .unwrap();
4497 assert_eq!(row_values(&rows[0])[1], SqliteValue::Null);
4498 assert_eq!(row_values(&rows[1])[1], SqliteValue::Integer(5));
4499 });
4500 }
4501
4502 #[test]
4503 fn collate_in_join_does_not_return_null() {
4504 asupersync::test_utils::run_test(|| async {
4505 let conn = Connection::open(":memory:").await.unwrap();
4506 conn.execute("CREATE TABLE cl (id INTEGER PRIMARY KEY, name TEXT);")
4507 .await
4508 .unwrap();
4509 conn.execute("INSERT INTO cl VALUES (1, 'Alice'), (2, 'bob');")
4510 .await
4511 .unwrap();
4512 let rows = conn
4514 .query("SELECT id FROM cl WHERE name COLLATE NOCASE = 'alice' ORDER BY id;")
4515 .await
4516 .unwrap();
4517 assert!(!rows.is_empty());
4519 });
4520 }
4521
4522 #[test]
4523 fn probe_nested_functions() {
4524 asupersync::test_utils::run_test(|| async {
4525 let conn = Connection::open(":memory:").await.unwrap();
4526 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
4527 .await
4528 .unwrap();
4529 conn.execute("INSERT INTO t VALUES (1, ' hello ');")
4530 .await
4531 .unwrap();
4532 let rows = conn.query("SELECT UPPER(TRIM(val)) FROM t;").await.unwrap();
4533 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("HELLO".into()));
4534 });
4535 }
4536
4537 #[test]
4538 fn replace_null_arg_returns_null() {
4539 asupersync::test_utils::run_test(|| async {
4540 let conn = Connection::open(":memory:").await.unwrap();
4541 let rows = conn.query("SELECT REPLACE(NULL, 'a', 'b');").await.unwrap();
4542 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
4543 let rows = conn
4544 .query("SELECT REPLACE('hello', NULL, 'b');")
4545 .await
4546 .unwrap();
4547 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
4548 });
4549 }
4550
4551 #[test]
4552 fn trim_null_returns_null() {
4553 asupersync::test_utils::run_test(|| async {
4554 let conn = Connection::open(":memory:").await.unwrap();
4555 let rows = conn.query("SELECT TRIM(NULL);").await.unwrap();
4556 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
4557 let rows = conn.query("SELECT LTRIM(NULL);").await.unwrap();
4558 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
4559 let rows = conn.query("SELECT RTRIM(NULL);").await.unwrap();
4560 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
4561 });
4562 }
4563
4564 #[test]
4565 fn hex_null_returns_empty_string() {
4566 asupersync::test_utils::run_test(|| async {
4567 let conn = Connection::open(":memory:").await.unwrap();
4569 let rows = conn.query("SELECT HEX(NULL);").await.unwrap();
4570 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("".into()));
4571 });
4572 }
4573
4574 #[test]
4575 fn instr_null_returns_null() {
4576 asupersync::test_utils::run_test(|| async {
4577 let conn = Connection::open(":memory:").await.unwrap();
4578 let rows = conn.query("SELECT INSTR(NULL, 'x');").await.unwrap();
4579 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
4580 let rows = conn.query("SELECT INSTR('hello', NULL);").await.unwrap();
4581 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
4582 });
4583 }
4584
4585 #[test]
4586 fn substr_null_returns_null() {
4587 asupersync::test_utils::run_test(|| async {
4588 let conn = Connection::open(":memory:").await.unwrap();
4589 let rows = conn.query("SELECT SUBSTR(NULL, 1, 3);").await.unwrap();
4590 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
4591 });
4592 }
4593
4594 #[test]
4595 fn substr_negative_start() {
4596 asupersync::test_utils::run_test(|| async {
4597 let conn = Connection::open(":memory:").await.unwrap();
4598 let rows = conn.query("SELECT SUBSTR('hello', -1);").await.unwrap();
4600 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("o".into()));
4601 let rows = conn.query("SELECT SUBSTR('hello', -3);").await.unwrap();
4602 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("llo".into()));
4603 });
4604 }
4605
4606 #[test]
4607 fn limit_negative_returns_all_rows() {
4608 asupersync::test_utils::run_test(|| async {
4609 let conn = Connection::open(":memory:").await.unwrap();
4610 conn.execute("CREATE TABLE ln (id INTEGER PRIMARY KEY);")
4611 .await
4612 .unwrap();
4613 conn.execute("INSERT INTO ln VALUES (1), (2), (3), (4), (5);")
4614 .await
4615 .unwrap();
4616 let rows = conn
4618 .query("SELECT id FROM ln ORDER BY id LIMIT -1;")
4619 .await
4620 .unwrap();
4621 assert_eq!(rows.len(), 5);
4622 });
4623 }
4624
4625 #[test]
4626 fn offset_negative_treated_as_zero() {
4627 asupersync::test_utils::run_test(|| async {
4628 let conn = Connection::open(":memory:").await.unwrap();
4629 conn.execute("CREATE TABLE on_ (id INTEGER PRIMARY KEY);")
4630 .await
4631 .unwrap();
4632 conn.execute("INSERT INTO on_ VALUES (1), (2), (3);")
4633 .await
4634 .unwrap();
4635 let rows = conn
4637 .query("SELECT id FROM on_ ORDER BY id LIMIT 2 OFFSET -5;")
4638 .await
4639 .unwrap();
4640 assert_eq!(rows.len(), 2);
4641 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
4642 });
4643 }
4644
4645 #[test]
4646 fn null_comparison_returns_null_in_join() {
4647 asupersync::test_utils::run_test(|| async {
4648 let conn = Connection::open(":memory:").await.unwrap();
4649 conn.execute("CREATE TABLE nc (id INTEGER PRIMARY KEY, val INTEGER);")
4650 .await
4651 .unwrap();
4652 conn.execute("INSERT INTO nc VALUES (1, NULL), (2, 5), (3, NULL);")
4653 .await
4654 .unwrap();
4655 let rows = conn
4658 .query("SELECT id FROM nc WHERE val = 5 ORDER BY id;")
4659 .await
4660 .unwrap();
4661 assert_eq!(rows.len(), 1);
4662 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(2));
4663 });
4664 }
4665
4666 #[test]
4667 fn null_and_true_returns_null() {
4668 asupersync::test_utils::run_test(|| async {
4669 let conn = Connection::open(":memory:").await.unwrap();
4670 let rows = conn.query("SELECT NULL AND 1;").await.unwrap();
4672 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
4673 });
4674 }
4675
4676 #[test]
4677 fn null_or_false_returns_null() {
4678 asupersync::test_utils::run_test(|| async {
4679 let conn = Connection::open(":memory:").await.unwrap();
4680 let rows = conn.query("SELECT NULL OR 0;").await.unwrap();
4682 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
4683 });
4684 }
4685
4686 #[test]
4687 fn false_and_null_returns_false() {
4688 asupersync::test_utils::run_test(|| async {
4689 let conn = Connection::open(":memory:").await.unwrap();
4690 let rows = conn.query("SELECT 0 AND NULL;").await.unwrap();
4692 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(0));
4693 });
4694 }
4695
4696 #[test]
4697 fn null_ne_in_where_excludes_row() {
4698 asupersync::test_utils::run_test(|| async {
4699 let conn = Connection::open(":memory:").await.unwrap();
4700 conn.execute("CREATE TABLE nne (id INTEGER PRIMARY KEY, val INTEGER);")
4701 .await
4702 .unwrap();
4703 conn.execute("INSERT INTO nne VALUES (1, NULL), (2, 5), (3, 10);")
4704 .await
4705 .unwrap();
4706 let rows = conn
4708 .query("SELECT id FROM nne WHERE val != 5 ORDER BY id;")
4709 .await
4710 .unwrap();
4711 assert_eq!(rows.len(), 1);
4712 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(3));
4713 });
4714 }
4715
4716 #[test]
4717 fn mixed_type_comparison_uses_type_ordering() {
4718 asupersync::test_utils::run_test(|| async {
4719 let conn = Connection::open(":memory:").await.unwrap();
4720 conn.execute("CREATE TABLE mt (id INTEGER PRIMARY KEY, val);")
4721 .await
4722 .unwrap();
4723 conn.execute("INSERT INTO mt VALUES (1, 5), (2, 'hello'), (3, 10);")
4725 .await
4726 .unwrap();
4727 let rows = conn
4729 .query("SELECT id FROM mt WHERE val = 'hello';")
4730 .await
4731 .unwrap();
4732 assert_eq!(rows.len(), 1);
4733 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(2));
4734 });
4735 }
4736
4737 #[test]
4738 fn integer_less_than_text_in_type_ordering() {
4739 asupersync::test_utils::run_test(|| async {
4740 let conn = Connection::open(":memory:").await.unwrap();
4741 conn.execute("CREATE TABLE ilt (id INTEGER PRIMARY KEY, val);")
4742 .await
4743 .unwrap();
4744 conn.execute("INSERT INTO ilt VALUES (1, 42), (2, 'abc');")
4745 .await
4746 .unwrap();
4747 let rows = conn
4749 .query("SELECT id FROM ilt WHERE val < 'abc';")
4750 .await
4751 .unwrap();
4752 assert_eq!(rows.len(), 1);
4753 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
4754 });
4755 }
4756
4757 #[test]
4758 fn blob_greater_than_text_in_type_ordering() {
4759 asupersync::test_utils::run_test(|| async {
4760 let conn = Connection::open(":memory:").await.unwrap();
4761 conn.execute("CREATE TABLE bgt (id INTEGER PRIMARY KEY, val);")
4762 .await
4763 .unwrap();
4764 conn.execute("INSERT INTO bgt VALUES (1, 'text'), (2, X'DEADBEEF');")
4765 .await
4766 .unwrap();
4767 let rows = conn
4769 .query("SELECT id FROM bgt WHERE val > 'text';")
4770 .await
4771 .unwrap();
4772 assert_eq!(rows.len(), 1);
4773 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(2));
4774 });
4775 }
4776
4777 #[test]
4778 fn large_integer_float_precision_comparison() {
4779 asupersync::test_utils::run_test(|| async {
4780 let conn = Connection::open(":memory:").await.unwrap();
4781 conn.execute("CREATE TABLE lip (id INTEGER PRIMARY KEY, ival INTEGER, fval REAL);")
4784 .await
4785 .unwrap();
4786 conn.execute("INSERT INTO lip VALUES (1, 9007199254740993, 9007199254740992.0);")
4787 .await
4788 .unwrap();
4789 let rows = conn
4790 .query("SELECT id FROM lip WHERE ival > fval;")
4791 .await
4792 .unwrap();
4793 assert_eq!(rows.len(), 1, "large integer should be greater than float");
4794 });
4795 }
4796
4797 #[test]
4798 fn not_null_returns_null() {
4799 asupersync::test_utils::run_test(|| async {
4800 let conn = Connection::open(":memory:").await.unwrap();
4801 let rows = conn.query("SELECT NOT NULL;").await.unwrap();
4803 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
4804 });
4805 }
4806
4807 #[test]
4808 fn not_null_in_where_excludes_row() {
4809 asupersync::test_utils::run_test(|| async {
4810 let conn = Connection::open(":memory:").await.unwrap();
4811 conn.execute("CREATE TABLE nn (id INTEGER PRIMARY KEY, flag INTEGER);")
4812 .await
4813 .unwrap();
4814 conn.execute("INSERT INTO nn VALUES (1, NULL), (2, 0), (3, 1);")
4815 .await
4816 .unwrap();
4817 let rows = conn
4819 .query("SELECT id FROM nn WHERE NOT flag ORDER BY id;")
4820 .await
4821 .unwrap();
4822 assert_eq!(rows.len(), 1);
4823 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(2));
4824 });
4825 }
4826
4827 #[test]
4828 fn bitnot_null_returns_null() {
4829 asupersync::test_utils::run_test(|| async {
4830 let conn = Connection::open(":memory:").await.unwrap();
4831 let rows = conn.query("SELECT ~NULL;").await.unwrap();
4833 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
4834 });
4835 }
4836
4837 #[test]
4838 fn probe_update_where_column_cmp() {
4839 asupersync::test_utils::run_test(|| async {
4840 let conn = Connection::open(":memory:").await.unwrap();
4841 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, a INTEGER, b INTEGER);")
4842 .await
4843 .unwrap();
4844 conn.execute("INSERT INTO t VALUES (1, 5, 10);")
4845 .await
4846 .unwrap();
4847 conn.execute("INSERT INTO t VALUES (2, 15, 10);")
4848 .await
4849 .unwrap();
4850 conn.execute("UPDATE t SET a = a * 2 WHERE a < b;")
4851 .await
4852 .unwrap();
4853 let rows = conn
4854 .query("SELECT id, a FROM t ORDER BY id;")
4855 .await
4856 .unwrap();
4857 assert_eq!(row_values(&rows[0])[1], SqliteValue::Integer(10));
4858 assert_eq!(row_values(&rows[1])[1], SqliteValue::Integer(15));
4859 });
4860 }
4861
4862 #[test]
4863 fn probe_nullif() {
4864 asupersync::test_utils::run_test(|| async {
4865 let conn = Connection::open(":memory:").await.unwrap();
4866 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
4867 .await
4868 .unwrap();
4869 conn.execute("INSERT INTO t VALUES (1, 'x');")
4870 .await
4871 .unwrap();
4872 conn.execute("INSERT INTO t VALUES (2, 'skip');")
4873 .await
4874 .unwrap();
4875 let rows = conn
4876 .query("SELECT id, NULLIF(val, 'skip') FROM t ORDER BY id;")
4877 .await
4878 .unwrap();
4879 assert_eq!(row_values(&rows[0])[1], SqliteValue::Text("x".into()));
4880 assert_eq!(row_values(&rows[1])[1], SqliteValue::Null);
4881 });
4882 }
4883
4884 #[test]
4885 fn probe_iif() {
4886 asupersync::test_utils::run_test(|| async {
4887 let conn = Connection::open(":memory:").await.unwrap();
4888 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val INTEGER);")
4889 .await
4890 .unwrap();
4891 conn.execute("INSERT INTO t VALUES (1, 5);").await.unwrap();
4892 conn.execute("INSERT INTO t VALUES (2, 15);").await.unwrap();
4893 let rows = conn
4894 .query("SELECT id, IIF(val > 10, 'big', 'small') FROM t ORDER BY id;")
4895 .await
4896 .unwrap();
4897 assert_eq!(row_values(&rows[0])[1], SqliteValue::Text("small".into()));
4898 assert_eq!(row_values(&rows[1])[1], SqliteValue::Text("big".into()));
4899 });
4900 }
4901
4902 #[test]
4903 fn probe_select_distinct() {
4904 asupersync::test_utils::run_test(|| async {
4905 let conn = Connection::open(":memory:").await.unwrap();
4906 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
4907 .await
4908 .unwrap();
4909 conn.execute("INSERT INTO t VALUES (1, 'a');")
4910 .await
4911 .unwrap();
4912 conn.execute("INSERT INTO t VALUES (2, 'b');")
4913 .await
4914 .unwrap();
4915 conn.execute("INSERT INTO t VALUES (3, 'a');")
4916 .await
4917 .unwrap();
4918 conn.execute("INSERT INTO t VALUES (4, 'b');")
4919 .await
4920 .unwrap();
4921 conn.execute("INSERT INTO t VALUES (5, 'c');")
4922 .await
4923 .unwrap();
4924 let rows = conn
4925 .query("SELECT DISTINCT val FROM t ORDER BY val;")
4926 .await
4927 .unwrap();
4928 assert_eq!(rows.len(), 3, "DISTINCT should return 3 unique values");
4929 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("a".into()));
4930 assert_eq!(row_values(&rows[1])[0], SqliteValue::Text("b".into()));
4931 assert_eq!(row_values(&rows[2])[0], SqliteValue::Text("c".into()));
4932 });
4933 }
4934
4935 #[test]
4936 fn probe_order_by_desc() {
4937 asupersync::test_utils::run_test(|| async {
4938 let conn = Connection::open(":memory:").await.unwrap();
4939 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val INTEGER);")
4940 .await
4941 .unwrap();
4942 conn.execute("INSERT INTO t VALUES (1, 30);").await.unwrap();
4943 conn.execute("INSERT INTO t VALUES (2, 10);").await.unwrap();
4944 conn.execute("INSERT INTO t VALUES (3, 20);").await.unwrap();
4945 let rows = conn
4946 .query("SELECT id, val FROM t ORDER BY val DESC;")
4947 .await
4948 .unwrap();
4949 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
4950 assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(3));
4951 assert_eq!(row_values(&rows[2])[0], SqliteValue::Integer(2));
4952 });
4953 }
4954
4955 #[test]
4956 fn probe_insert_or_replace() {
4957 asupersync::test_utils::run_test(|| async {
4958 let conn = Connection::open(":memory:").await.unwrap();
4959 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
4960 .await
4961 .unwrap();
4962 conn.execute("INSERT INTO t VALUES (1, 'old');")
4963 .await
4964 .unwrap();
4965 conn.execute("INSERT OR REPLACE INTO t VALUES (1, 'new');")
4966 .await
4967 .unwrap();
4968 let rows = conn.query("SELECT id, val FROM t;").await.unwrap();
4969 assert_eq!(rows.len(), 1);
4970 assert_eq!(row_values(&rows[0])[1], SqliteValue::Text("new".into()));
4971 });
4972 }
4973
4974 #[test]
4975 fn probe_insert_or_ignore() {
4976 asupersync::test_utils::run_test(|| async {
4977 let conn = Connection::open(":memory:").await.unwrap();
4978 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
4979 .await
4980 .unwrap();
4981 conn.execute("INSERT INTO t VALUES (1, 'first');")
4982 .await
4983 .unwrap();
4984 conn.execute("INSERT OR IGNORE INTO t VALUES (1, 'second');")
4985 .await
4986 .unwrap();
4987 let rows = conn.query("SELECT id, val FROM t;").await.unwrap();
4988 assert_eq!(rows.len(), 1);
4989 assert_eq!(row_values(&rows[0])[1], SqliteValue::Text("first".into()));
4990 });
4991 }
4992
4993 #[test]
4994 fn probe_between() {
4995 asupersync::test_utils::run_test(|| async {
4996 let conn = Connection::open(":memory:").await.unwrap();
4997 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val INTEGER);")
4998 .await
4999 .unwrap();
5000 for i in 1..=10 {
5001 conn.execute(&format!("INSERT INTO t VALUES ({i}, {i});"))
5002 .await
5003 .unwrap();
5004 }
5005 let rows = conn
5006 .query("SELECT val FROM t WHERE val BETWEEN 3 AND 7 ORDER BY val;")
5007 .await
5008 .unwrap();
5009 assert_eq!(rows.len(), 5);
5010 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(3));
5011 assert_eq!(row_values(&rows[4])[0], SqliteValue::Integer(7));
5012 });
5013 }
5014
5015 #[test]
5016 fn probe_in_list() {
5017 asupersync::test_utils::run_test(|| async {
5018 let conn = Connection::open(":memory:").await.unwrap();
5019 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
5020 .await
5021 .unwrap();
5022 conn.execute("INSERT INTO t VALUES (1, 'a');")
5023 .await
5024 .unwrap();
5025 conn.execute("INSERT INTO t VALUES (2, 'b');")
5026 .await
5027 .unwrap();
5028 conn.execute("INSERT INTO t VALUES (3, 'c');")
5029 .await
5030 .unwrap();
5031 let rows = conn
5032 .query("SELECT id FROM t WHERE val IN ('a', 'c') ORDER BY id;")
5033 .await
5034 .unwrap();
5035 assert_eq!(rows.len(), 2);
5036 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
5037 assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(3));
5038 });
5039 }
5040
5041 #[test]
5042 fn probe_like_pattern() {
5043 asupersync::test_utils::run_test(|| async {
5044 let conn = Connection::open(":memory:").await.unwrap();
5045 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
5046 .await
5047 .unwrap();
5048 conn.execute("INSERT INTO t VALUES (1, 'Alice');")
5049 .await
5050 .unwrap();
5051 conn.execute("INSERT INTO t VALUES (2, 'Bob');")
5052 .await
5053 .unwrap();
5054 conn.execute("INSERT INTO t VALUES (3, 'Charlie');")
5055 .await
5056 .unwrap();
5057 let rows = conn
5058 .query("SELECT name FROM t WHERE name LIKE '%li%' ORDER BY name;")
5059 .await
5060 .unwrap();
5061 assert_eq!(rows.len(), 2);
5062 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("Alice".into()));
5063 assert_eq!(row_values(&rows[1])[0], SqliteValue::Text("Charlie".into()));
5064 });
5065 }
5066
5067 #[test]
5068 fn probe_subquery_in_where() {
5069 asupersync::test_utils::run_test(|| async {
5070 let conn = Connection::open(":memory:").await.unwrap();
5071 conn.execute("CREATE TABLE t1 (id INTEGER PRIMARY KEY, val TEXT);")
5072 .await
5073 .unwrap();
5074 conn.execute("CREATE TABLE t2 (id INTEGER PRIMARY KEY, t1_id INTEGER);")
5075 .await
5076 .unwrap();
5077 conn.execute("INSERT INTO t1 VALUES (1, 'a');")
5078 .await
5079 .unwrap();
5080 conn.execute("INSERT INTO t1 VALUES (2, 'b');")
5081 .await
5082 .unwrap();
5083 conn.execute("INSERT INTO t1 VALUES (3, 'c');")
5084 .await
5085 .unwrap();
5086 conn.execute("INSERT INTO t2 VALUES (1, 1);").await.unwrap();
5087 conn.execute("INSERT INTO t2 VALUES (2, 3);").await.unwrap();
5088 let result = conn
5090 .query("SELECT val FROM t1 WHERE id IN (SELECT t1_id FROM t2) ORDER BY val;")
5091 .await;
5092 if let Ok(rows) = result {
5093 assert_eq!(rows.len(), 2);
5094 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("a".into()));
5095 assert_eq!(row_values(&rows[1])[0], SqliteValue::Text("c".into()));
5096 } else {
5097 }
5099 });
5100 }
5101
5102 #[test]
5104 fn probe_insert_returning_star() {
5105 asupersync::test_utils::run_test(|| async {
5106 let conn = Connection::open(":memory:").await.unwrap();
5107 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT, age INTEGER);")
5108 .await
5109 .unwrap();
5110 let rows = conn
5111 .query("INSERT INTO t VALUES (1, 'Alice', 30) RETURNING *;")
5112 .await
5113 .unwrap();
5114 assert_eq!(rows.len(), 1, "RETURNING * should produce 1 row");
5115 assert_eq!(
5117 row_values(&rows[0]),
5118 vec![
5119 SqliteValue::Integer(1),
5120 SqliteValue::Text("Alice".into()),
5121 SqliteValue::Integer(30),
5122 ]
5123 );
5124 });
5125 }
5126
5127 #[test]
5129 fn probe_insert_returning_columns() {
5130 asupersync::test_utils::run_test(|| async {
5131 let conn = Connection::open(":memory:").await.unwrap();
5132 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT, age INTEGER);")
5133 .await
5134 .unwrap();
5135 let rows = conn
5136 .query("INSERT INTO t VALUES (1, 'Bob', 25) RETURNING name, age;")
5137 .await
5138 .unwrap();
5139 assert_eq!(rows.len(), 1);
5140 assert_eq!(
5141 row_values(&rows[0]),
5142 vec![SqliteValue::Text("Bob".into()), SqliteValue::Integer(25),]
5143 );
5144 });
5145 }
5146
5147 #[test]
5149 fn probe_insert_returning_rowid() {
5150 asupersync::test_utils::run_test(|| async {
5151 let conn = Connection::open(":memory:").await.unwrap();
5152 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
5153 .await
5154 .unwrap();
5155 let rows = conn
5156 .query("INSERT INTO t VALUES (42, 'test') RETURNING id;")
5157 .await
5158 .unwrap();
5159 assert_eq!(rows.len(), 1);
5160 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(42));
5161 });
5162 }
5163
5164 #[test]
5166 fn probe_insert_returning_multi_row() {
5167 asupersync::test_utils::run_test(|| async {
5168 let conn = Connection::open(":memory:").await.unwrap();
5169 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
5170 .await
5171 .unwrap();
5172 conn.execute("INSERT INTO t VALUES (1, 'a');")
5173 .await
5174 .unwrap();
5175 conn.execute("INSERT INTO t VALUES (2, 'b');")
5176 .await
5177 .unwrap();
5178 conn.execute("CREATE TABLE t2 (id INTEGER PRIMARY KEY, val TEXT);")
5180 .await
5181 .unwrap();
5182 let rows = conn
5183 .query("INSERT INTO t2 SELECT * FROM t RETURNING *;")
5184 .await
5185 .unwrap();
5186 assert_eq!(
5187 rows.len(),
5188 2,
5189 "Multi-row INSERT RETURNING should produce 2 rows"
5190 );
5191 });
5192 }
5193
5194 #[test]
5196 fn probe_update_returning_star() {
5197 asupersync::test_utils::run_test(|| async {
5198 let conn = Connection::open(":memory:").await.unwrap();
5199 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT, age INTEGER);")
5200 .await
5201 .unwrap();
5202 conn.execute("INSERT INTO t VALUES (1, 'Alice', 30);")
5203 .await
5204 .unwrap();
5205 conn.execute("INSERT INTO t VALUES (2, 'Bob', 25);")
5206 .await
5207 .unwrap();
5208 let rows = conn
5209 .query("UPDATE t SET age = age + 1 WHERE id = 1 RETURNING *;")
5210 .await
5211 .unwrap();
5212 assert_eq!(rows.len(), 1, "UPDATE RETURNING should produce 1 row");
5213 assert_eq!(
5214 row_values(&rows[0]),
5215 vec![
5216 SqliteValue::Integer(1),
5217 SqliteValue::Text("Alice".into()),
5218 SqliteValue::Integer(31),
5219 ]
5220 );
5221 });
5222 }
5223
5224 #[test]
5226 fn probe_update_returning_columns() {
5227 asupersync::test_utils::run_test(|| async {
5228 let conn = Connection::open(":memory:").await.unwrap();
5229 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val INTEGER);")
5230 .await
5231 .unwrap();
5232 conn.execute("INSERT INTO t VALUES (1, 10);").await.unwrap();
5233 conn.execute("INSERT INTO t VALUES (2, 20);").await.unwrap();
5234 let rows = conn
5235 .query("UPDATE t SET val = val * 2 RETURNING id, val;")
5236 .await
5237 .unwrap();
5238 assert_eq!(rows.len(), 2, "UPDATE RETURNING should produce 2 rows");
5239 });
5240 }
5241
5242 #[test]
5244 fn probe_delete_returning_star() {
5245 asupersync::test_utils::run_test(|| async {
5246 let conn = Connection::open(":memory:").await.unwrap();
5247 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
5248 .await
5249 .unwrap();
5250 conn.execute("INSERT INTO t VALUES (1, 'Alice');")
5251 .await
5252 .unwrap();
5253 conn.execute("INSERT INTO t VALUES (2, 'Bob');")
5254 .await
5255 .unwrap();
5256 let rows = conn
5257 .query("DELETE FROM t WHERE id = 2 RETURNING *;")
5258 .await
5259 .unwrap();
5260 assert_eq!(rows.len(), 1, "DELETE RETURNING should produce 1 row");
5261 assert_eq!(
5262 row_values(&rows[0]),
5263 vec![SqliteValue::Integer(2), SqliteValue::Text("Bob".into()),]
5264 );
5265 let remaining = conn.query("SELECT COUNT(*) FROM t;").await.unwrap();
5267 assert_eq!(row_values(&remaining[0])[0], SqliteValue::Integer(1));
5268 });
5269 }
5270
5271 #[test]
5273 fn probe_delete_returning_column() {
5274 asupersync::test_utils::run_test(|| async {
5275 let conn = Connection::open(":memory:").await.unwrap();
5276 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
5277 .await
5278 .unwrap();
5279 conn.execute("INSERT INTO t VALUES (1, 'a');")
5280 .await
5281 .unwrap();
5282 conn.execute("INSERT INTO t VALUES (2, 'b');")
5283 .await
5284 .unwrap();
5285 conn.execute("INSERT INTO t VALUES (3, 'c');")
5286 .await
5287 .unwrap();
5288 let rows = conn
5289 .query("DELETE FROM t WHERE id > 1 RETURNING val;")
5290 .await
5291 .unwrap();
5292 assert_eq!(rows.len(), 2, "DELETE RETURNING should produce 2 rows");
5293 });
5294 }
5295
5296 #[test]
5298 fn probe_insert_default_values() {
5299 asupersync::test_utils::run_test(|| async {
5300 let conn = Connection::open(":memory:").await.unwrap();
5301 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT, val INTEGER);")
5302 .await
5303 .unwrap();
5304 conn.execute("INSERT INTO t DEFAULT VALUES;").await.unwrap();
5305 let rows = conn.query("SELECT id, name, val FROM t;").await.unwrap();
5306 assert_eq!(rows.len(), 1, "DEFAULT VALUES should insert 1 row");
5307 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
5308 assert_eq!(row_values(&rows[0])[1], SqliteValue::Null);
5310 assert_eq!(row_values(&rows[0])[2], SqliteValue::Null);
5311 });
5312 }
5313
5314 #[test]
5315 fn insert_default_values_uses_column_defaults() {
5316 asupersync::test_utils::run_test(|| async {
5317 let conn = Connection::open(":memory:").await.unwrap();
5318 conn.execute(
5319 "CREATE TABLE td (id INTEGER PRIMARY KEY, status TEXT DEFAULT 'active', count INTEGER DEFAULT 42, ratio REAL DEFAULT 2.5);",
5320 )
5321 .await
5322 .unwrap();
5323 conn.execute("INSERT INTO td DEFAULT VALUES;")
5324 .await
5325 .unwrap();
5326 let rows = conn
5327 .query("SELECT id, status, count, ratio FROM td;")
5328 .await
5329 .unwrap();
5330 assert_eq!(rows.len(), 1);
5331 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
5332 assert_eq!(
5333 row_values(&rows[0])[1],
5334 SqliteValue::Text("active".to_string().into()),
5335 "status should use DEFAULT 'active'"
5336 );
5337 assert_eq!(
5338 row_values(&rows[0])[2],
5339 SqliteValue::Integer(42),
5340 "count should use DEFAULT 42"
5341 );
5342 assert_eq!(
5343 row_values(&rows[0])[3],
5344 SqliteValue::Float(2.5),
5345 "ratio should use DEFAULT 2.5"
5346 );
5347 });
5348 }
5349
5350 #[test]
5351 fn insert_default_values_uses_expression_defaults() {
5352 asupersync::test_utils::run_test(|| async {
5353 let conn = Connection::open(":memory:").await.unwrap();
5354 conn.execute(
5355 "CREATE TABLE td (id INTEGER PRIMARY KEY, total INTEGER DEFAULT (40 + 2), label TEXT DEFAULT lower('HELLO'));",
5356 )
5357 .await
5358 .unwrap();
5359 conn.execute("INSERT INTO td DEFAULT VALUES;")
5360 .await
5361 .unwrap();
5362 let rows = conn
5363 .query("SELECT id, total, label FROM td;")
5364 .await
5365 .unwrap();
5366 assert_eq!(rows.len(), 1);
5367 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
5368 assert_eq!(row_values(&rows[0])[1], SqliteValue::Integer(42));
5369 assert_eq!(row_values(&rows[0])[2], SqliteValue::Text("hello".into()));
5370 });
5371 }
5372
5373 #[test]
5374 fn create_table_rejects_non_constant_default_expression() {
5375 asupersync::test_utils::run_test(|| async {
5376 let conn = Connection::open(":memory:").await.unwrap();
5377 let err = conn
5378 .execute("CREATE TABLE t(a INTEGER, b INTEGER DEFAULT (a + 1));")
5379 .await
5380 .expect_err("column-reference DEFAULT should be rejected");
5381 let msg = err.to_string();
5382 assert!(
5383 msg.contains("default value of column [b] is not constant"),
5384 "unexpected error: {msg}"
5385 );
5386 });
5387 }
5388
5389 #[test]
5390 fn create_table_rejects_aggregate_default_expression() {
5391 asupersync::test_utils::run_test(|| async {
5392 let conn = Connection::open(":memory:").await.unwrap();
5393 let err = conn
5394 .execute("CREATE TABLE t(a INTEGER, b INTEGER DEFAULT (sum(1)));")
5395 .await
5396 .expect_err("aggregate DEFAULT should be rejected");
5397 let msg = err.to_string();
5398 assert!(
5399 msg.contains("default value of column [b] is not constant"),
5400 "unexpected error: {msg}"
5401 );
5402 });
5403 }
5404
5405 #[test]
5406 fn create_table_rejects_hidden_aggregate_default_expression() {
5407 asupersync::test_utils::run_test(|| async {
5408 let conn = Connection::open(":memory:").await.unwrap();
5409 let err = conn
5410 .execute("CREATE TABLE t(a INTEGER, b INTEGER DEFAULT (1 IN (sum(1))));")
5411 .await
5412 .expect_err("aggregate hidden inside IN DEFAULT should be rejected");
5413 let msg = err.to_string();
5414 assert!(
5415 msg.contains("default value of column [b] is not constant"),
5416 "unexpected error: {msg}"
5417 );
5418 });
5419 }
5420
5421 #[test]
5422 fn alter_table_add_column_rejects_non_constant_default_expression() {
5423 asupersync::test_utils::run_test(|| async {
5424 let conn = Connection::open(":memory:").await.unwrap();
5425 conn.execute("CREATE TABLE t(a INTEGER);").await.unwrap();
5426 let err = conn
5427 .execute("ALTER TABLE t ADD COLUMN b INTEGER DEFAULT (a + 1);")
5428 .await
5429 .expect_err("column-reference DEFAULT should be rejected");
5430 let msg = err.to_string();
5431 assert!(
5432 msg.contains("default value of column [b] is not constant"),
5433 "unexpected error: {msg}"
5434 );
5435 });
5436 }
5437
5438 #[test]
5439 fn alter_table_add_column_rejects_hidden_aggregate_default_expression() {
5440 asupersync::test_utils::run_test(|| async {
5441 let conn = Connection::open(":memory:").await.unwrap();
5442 conn.execute("CREATE TABLE t(a INTEGER);").await.unwrap();
5443 let err = conn
5444 .execute("ALTER TABLE t ADD COLUMN b INTEGER DEFAULT (1 IN (sum(1)));")
5445 .await
5446 .expect_err("aggregate hidden inside IN DEFAULT should be rejected");
5447 let msg = err.to_string();
5448 assert!(
5449 msg.contains("default value of column [b] is not constant"),
5450 "unexpected error: {msg}"
5451 );
5452 });
5453 }
5454
5455 #[test]
5456 fn alter_table_add_column_rejects_aggregate_default_expression() {
5457 asupersync::test_utils::run_test(|| async {
5458 let conn = Connection::open(":memory:").await.unwrap();
5459 conn.execute("CREATE TABLE t(a INTEGER);").await.unwrap();
5460 let err = conn
5461 .execute("ALTER TABLE t ADD COLUMN b INTEGER DEFAULT (sum(1));")
5462 .await
5463 .expect_err("aggregate DEFAULT should be rejected");
5464 let msg = err.to_string();
5465 assert!(
5466 msg.contains("default value of column [b] is not constant"),
5467 "unexpected error: {msg}"
5468 );
5469 });
5470 }
5471
5472 #[test]
5473 fn insert_explicit_cols_uses_defaults_for_omitted() {
5474 asupersync::test_utils::run_test(|| async {
5475 let conn = Connection::open(":memory:").await.unwrap();
5476 conn.execute(
5477 "CREATE TABLE te (id INTEGER PRIMARY KEY, name TEXT, status TEXT DEFAULT 'pending');",
5478 )
5479 .await
5480 .unwrap();
5481 conn.execute("INSERT INTO te (name) VALUES ('alice');")
5483 .await
5484 .unwrap();
5485 let rows = conn
5486 .query("SELECT id, name, status FROM te;")
5487 .await
5488 .unwrap();
5489 assert_eq!(rows.len(), 1);
5490 assert_eq!(
5491 row_values(&rows[0])[1],
5492 SqliteValue::Text("alice".to_string().into())
5493 );
5494 assert_eq!(
5495 row_values(&rows[0])[2],
5496 SqliteValue::Text("pending".to_string().into()),
5497 "omitted column should use DEFAULT 'pending'"
5498 );
5499 });
5500 }
5501
5502 #[test]
5504 fn probe_insert_default_values_returning() {
5505 asupersync::test_utils::run_test(|| async {
5506 let conn = Connection::open(":memory:").await.unwrap();
5507 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
5508 .await
5509 .unwrap();
5510 let rows = conn
5513 .query("INSERT INTO t DEFAULT VALUES RETURNING id;")
5514 .await
5515 .unwrap();
5516 assert_eq!(
5517 rows.len(),
5518 1,
5519 "DEFAULT VALUES RETURNING should produce 1 row"
5520 );
5521 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
5523 });
5524 }
5525
5526 #[test]
5532 fn ipk_null_auto_generates_rowid() {
5533 asupersync::test_utils::run_test(|| async {
5534 let conn = Connection::open(":memory:").await.unwrap();
5535 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
5536 .await
5537 .unwrap();
5538 let r1 = conn
5539 .query("INSERT INTO t VALUES (NULL, 'a') RETURNING id;")
5540 .await
5541 .unwrap();
5542 let r2 = conn
5543 .query("INSERT INTO t VALUES (NULL, 'b') RETURNING id;")
5544 .await
5545 .unwrap();
5546 let id1 = &row_values(&r1[0])[0];
5547 let id2 = &row_values(&r2[0])[0];
5548 assert!(
5550 matches!(id1, SqliteValue::Integer(n) if *n > 0),
5551 "NULL IPK should auto-generate positive id, got {id1:?}"
5552 );
5553 if let (SqliteValue::Integer(a), SqliteValue::Integer(b)) = (id1, id2) {
5555 assert!(
5556 b > a,
5557 "successive NULL IPK inserts should increment: {a} < {b}"
5558 );
5559 }
5560 });
5561 }
5562
5563 #[test]
5565 fn ipk_zero_is_valid_rowid() {
5566 asupersync::test_utils::run_test(|| async {
5567 let conn = Connection::open(":memory:").await.unwrap();
5568 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
5569 .await
5570 .unwrap();
5571 let rows = conn
5572 .query("INSERT INTO t VALUES (0, 'zero') RETURNING id;")
5573 .await
5574 .unwrap();
5575 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(0));
5576 });
5577 }
5578
5579 #[test]
5581 fn ipk_negative_is_valid_rowid() {
5582 asupersync::test_utils::run_test(|| async {
5583 let conn = Connection::open(":memory:").await.unwrap();
5584 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
5585 .await
5586 .unwrap();
5587 let rows = conn
5588 .query("INSERT INTO t VALUES (-5, 'neg') RETURNING id;")
5589 .await
5590 .unwrap();
5591 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(-5));
5592 });
5593 }
5594
5595 #[test]
5597 fn ipk_multi_row_explicit_values() {
5598 asupersync::test_utils::run_test(|| async {
5599 let conn = Connection::open(":memory:").await.unwrap();
5600 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
5601 .await
5602 .unwrap();
5603 let rows = conn
5604 .query("INSERT INTO t VALUES (10,'a'),(20,'b'),(30,'c') RETURNING id;")
5605 .await
5606 .unwrap();
5607 assert_eq!(rows.len(), 3);
5608 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(10));
5609 assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(20));
5610 assert_eq!(row_values(&rows[2])[0], SqliteValue::Integer(30));
5611 });
5612 }
5613
5614 #[test]
5616 fn ipk_mixed_null_and_explicit() {
5617 asupersync::test_utils::run_test(|| async {
5618 let conn = Connection::open(":memory:").await.unwrap();
5619 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
5620 .await
5621 .unwrap();
5622 conn.execute("INSERT INTO t VALUES (10, 'explicit');")
5623 .await
5624 .unwrap();
5625 let rows = conn
5626 .query("INSERT INTO t VALUES (NULL, 'auto') RETURNING id;")
5627 .await
5628 .unwrap();
5629 if let SqliteValue::Integer(id) = &row_values(&rows[0])[0] {
5631 assert!(
5632 *id > 10,
5633 "auto-generated id after max=10 should be > 10, got {id}"
5634 );
5635 } else {
5636 panic!("expected Integer, got {:?}", row_values(&rows[0])[0]);
5637 }
5638 });
5639 }
5640
5641 #[test]
5643 fn ipk_returning_star_includes_correct_id() {
5644 asupersync::test_utils::run_test(|| async {
5645 let conn = Connection::open(":memory:").await.unwrap();
5646 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
5647 .await
5648 .unwrap();
5649 let rows = conn
5650 .query("INSERT INTO t VALUES (42, 'x') RETURNING *;")
5651 .await
5652 .unwrap();
5653 let vals = row_values(&rows[0]);
5654 assert_eq!(vals[0], SqliteValue::Integer(42));
5655 assert_eq!(vals[1], SqliteValue::Text("x".into()));
5656 });
5657 }
5658
5659 #[test]
5661 fn ipk_roundtrip_select_after_insert() {
5662 asupersync::test_utils::run_test(|| async {
5663 let conn = Connection::open(":memory:").await.unwrap();
5664 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
5665 .await
5666 .unwrap();
5667 conn.execute("INSERT INTO t VALUES (42, 'Alice');")
5668 .await
5669 .unwrap();
5670 conn.execute("INSERT INTO t VALUES (100, 'Bob');")
5671 .await
5672 .unwrap();
5673 let rows = conn.query("SELECT * FROM t ORDER BY id;").await.unwrap();
5674 assert_eq!(rows.len(), 2);
5675 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(42));
5676 assert_eq!(row_values(&rows[0])[1], SqliteValue::Text("Alice".into()));
5677 assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(100));
5678 });
5679 }
5680
5681 #[test]
5683 fn ipk_column_list_reorder() {
5684 asupersync::test_utils::run_test(|| async {
5685 let conn = Connection::open(":memory:").await.unwrap();
5686 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
5687 .await
5688 .unwrap();
5689 let rows = conn
5691 .query("INSERT INTO t(name, id) VALUES ('Alice', 42) RETURNING *;")
5692 .await
5693 .unwrap();
5694 let vals = row_values(&rows[0]);
5695 assert_eq!(
5696 vals[0],
5697 SqliteValue::Integer(42),
5698 "id should be 42 (from column-list position 1)"
5699 );
5700 assert_eq!(
5701 vals[1],
5702 SqliteValue::Text("Alice".into()),
5703 "name should be Alice (from column-list position 0)"
5704 );
5705 let sel = conn.query("SELECT id, name FROM t;").await.unwrap();
5707 let sv = row_values(&sel[0]);
5708 assert_eq!(sv[0], SqliteValue::Integer(42));
5709 assert_eq!(sv[1], SqliteValue::Text("Alice".into()));
5710 });
5711 }
5712
5713 #[test]
5714 fn insert_select_without_from_reorders_targets_and_fills_defaults() {
5715 asupersync::test_utils::run_test(|| async {
5716 let conn = Connection::open(":memory:").await.unwrap();
5717 conn.execute(
5718 "CREATE TABLE dst (id INTEGER PRIMARY KEY, label TEXT DEFAULT 'seed', qty INTEGER);",
5719 )
5720 .await
5721 .unwrap();
5722
5723 let rows = conn
5724 .query("INSERT INTO dst(qty) SELECT 11 RETURNING label, qty;")
5725 .await
5726 .unwrap();
5727 assert_eq!(
5728 row_values(&rows[0]),
5729 vec![SqliteValue::Text("seed".into()), SqliteValue::Integer(11)]
5730 );
5731
5732 let rows = conn
5733 .query("INSERT INTO dst(qty, label) SELECT 22, 'fresh' RETURNING label, qty;")
5734 .await
5735 .unwrap();
5736 assert_eq!(
5737 row_values(&rows[0]),
5738 vec![SqliteValue::Text("fresh".into()), SqliteValue::Integer(22)]
5739 );
5740 });
5741 }
5742
5743 #[test]
5744 fn insert_select_without_from_explicit_rowid_is_preserved() {
5745 asupersync::test_utils::run_test(|| async {
5746 let conn = Connection::open(":memory:").await.unwrap();
5747 conn.execute("CREATE TABLE t (payload TEXT);")
5748 .await
5749 .unwrap();
5750
5751 let rows = conn
5752 .query("INSERT INTO t(rowid, payload) SELECT 7, 'x' RETURNING rowid, payload;")
5753 .await
5754 .unwrap();
5755 assert_eq!(
5756 row_values(&rows[0]),
5757 vec![SqliteValue::Integer(7), SqliteValue::Text("x".into())]
5758 );
5759 });
5760 }
5761
5762 #[test]
5763 fn insert_values_rowid_family_uses_last_target_assignment() {
5764 asupersync::test_utils::run_test(|| async {
5765 let conn = Connection::open(":memory:").await.unwrap();
5766 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, payload TEXT);")
5767 .await
5768 .unwrap();
5769
5770 let rows = conn
5771 .query("INSERT INTO t(rowid, id, payload) VALUES (7, 8, 'x') RETURNING rowid, id;")
5772 .await
5773 .unwrap();
5774 assert_eq!(
5775 row_values(&rows[0]),
5776 vec![SqliteValue::Integer(8), SqliteValue::Integer(8)]
5777 );
5778
5779 let rows = conn
5780 .query("INSERT INTO t(id, rowid, payload) VALUES (9, 10, 'y') RETURNING rowid, id;")
5781 .await
5782 .unwrap();
5783 assert_eq!(
5784 row_values(&rows[0]),
5785 vec![SqliteValue::Integer(10), SqliteValue::Integer(10)]
5786 );
5787 });
5788 }
5789
5790 #[test]
5791 fn ipk_insert_select_column_list_reorder() {
5792 asupersync::test_utils::run_test(|| async {
5793 let conn = Connection::open(":memory:").await.unwrap();
5794 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
5795 .await
5796 .unwrap();
5797 let rows = conn
5798 .query("INSERT INTO t(name, id) SELECT 'Alice', 42 RETURNING id, name;")
5799 .await
5800 .unwrap();
5801 let vals = row_values(&rows[0]);
5802 assert_eq!(vals[0], SqliteValue::Integer(42));
5803 assert_eq!(vals[1], SqliteValue::Text("Alice".into()));
5804
5805 let sel = conn.query("SELECT id, name FROM t;").await.unwrap();
5806 let stored = row_values(&sel[0]);
5807 assert_eq!(stored[0], SqliteValue::Integer(42));
5808 assert_eq!(stored[1], SqliteValue::Text("Alice".into()));
5809 });
5810 }
5811
5812 #[test]
5813 fn ipk_insert_select_hidden_rowid_alias_honors_explicit_rowid() {
5814 asupersync::test_utils::run_test(|| async {
5815 let conn = Connection::open(":memory:").await.unwrap();
5816 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
5817 .await
5818 .unwrap();
5819 let rows = conn
5820 .query("INSERT INTO t(rowid, name) SELECT 7, 'Bob' RETURNING id, rowid, name;")
5821 .await
5822 .unwrap();
5823 let vals = row_values(&rows[0]);
5824 assert_eq!(vals[0], SqliteValue::Integer(7));
5825 assert_eq!(vals[1], SqliteValue::Integer(7));
5826 assert_eq!(vals[2], SqliteValue::Text("Bob".into()));
5827
5828 let sel = conn.query("SELECT id, rowid, name FROM t;").await.unwrap();
5829 let stored = row_values(&sel[0]);
5830 assert_eq!(stored[0], SqliteValue::Integer(7));
5831 assert_eq!(stored[1], SqliteValue::Integer(7));
5832 assert_eq!(stored[2], SqliteValue::Text("Bob".into()));
5833 });
5834 }
5835
5836 #[test]
5837 fn upsert_do_update_resolves_hidden_rowid_aliases() {
5838 asupersync::test_utils::run_test(|| async {
5839 let conn = Connection::open(":memory:").await.unwrap();
5840 conn.execute("CREATE TABLE t (u TEXT UNIQUE, v INTEGER);")
5841 .await
5842 .unwrap();
5843 conn.execute("INSERT INTO t(rowid, u, v) VALUES (1, 'dup', 10);")
5844 .await
5845 .unwrap();
5846 conn.execute(
5847 "INSERT INTO t(rowid, u, v) VALUES (7, 'dup', 99)
5848 ON CONFLICT(u) DO UPDATE SET v = excluded.rowid + rowid;",
5849 )
5850 .await
5851 .unwrap();
5852
5853 let rows = conn
5854 .query("SELECT rowid, u, v FROM t ORDER BY rowid;")
5855 .await
5856 .unwrap();
5857 let vals = row_values(&rows[0]);
5858 assert_eq!(vals[0], SqliteValue::Integer(1));
5859 assert_eq!(vals[1], SqliteValue::Text("dup".into()));
5860 assert_eq!(vals[2], SqliteValue::Integer(8));
5861 });
5862 }
5863
5864 #[test]
5865 fn alter_table_preserves_primary_key_sql_in_sqlite_master() {
5866 asupersync::test_utils::run_test(|| async {
5867 let conn = Connection::open(":memory:").await.unwrap();
5868 conn.execute("CREATE TABLE t(id TEXT PRIMARY KEY, body TEXT);")
5869 .await
5870 .unwrap();
5871 conn.execute("ALTER TABLE t RENAME COLUMN body TO payload;")
5872 .await
5873 .unwrap();
5874
5875 let rows = conn
5876 .query("SELECT sql FROM sqlite_master WHERE name = 't';")
5877 .await
5878 .unwrap();
5879 let row = row_values(&rows[0]);
5880 let sql = match &row[0] {
5881 SqliteValue::Text(sql) => sql,
5882 other => panic!("expected SQL text, got {other:?}"),
5883 };
5884 assert!(sql.contains("PRIMARY KEY"), "{sql}");
5885 assert!(!sql.contains("UNIQUE"), "{sql}");
5886 });
5887 }
5888
5889 #[test]
5890 fn alter_table_preserves_typeless_without_rowid_sql_in_sqlite_master() {
5891 asupersync::test_utils::run_test(|| async {
5892 let conn = Connection::open(":memory:").await.unwrap();
5893 conn.execute("CREATE TABLE wr(id TEXT, body, PRIMARY KEY(id, body)) WITHOUT ROWID;")
5894 .await
5895 .unwrap();
5896 conn.execute("ALTER TABLE wr ADD COLUMN note TEXT;")
5897 .await
5898 .unwrap();
5899
5900 let rows = conn
5901 .query("SELECT sql FROM sqlite_master WHERE name = 'wr';")
5902 .await
5903 .unwrap();
5904 let row = row_values(&rows[0]);
5905 let sql = match &row[0] {
5906 SqliteValue::Text(sql) => sql,
5907 other => panic!("expected SQL text, got {other:?}"),
5908 };
5909 assert!(sql.contains("PRIMARY KEY"), "{sql}");
5910 assert!(sql.contains("WITHOUT ROWID"), "{sql}");
5911 assert!(sql.contains("body"), "{sql}");
5912 assert!(!sql.contains("body BLOB"), "{sql}");
5913 });
5914 }
5915
5916 #[test]
5917 fn alter_table_drop_primary_key_column_is_rejected() {
5918 asupersync::test_utils::run_test(|| async {
5919 let conn = Connection::open(":memory:").await.unwrap();
5920 conn.execute("CREATE TABLE t(id TEXT PRIMARY KEY, body TEXT);")
5921 .await
5922 .unwrap();
5923 let err = conn
5924 .execute("ALTER TABLE t DROP COLUMN id;")
5925 .await
5926 .expect_err("dropping a primary key column must fail");
5927 let msg = err.to_string();
5928 assert!(
5929 msg.contains("cannot drop PRIMARY KEY column") && msg.contains("id"),
5930 "{msg}"
5931 );
5932 });
5933 }
5934
5935 #[test]
5937 fn ipk_column_list_omit_ipk() {
5938 asupersync::test_utils::run_test(|| async {
5939 let conn = Connection::open(":memory:").await.unwrap();
5940 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
5941 .await
5942 .unwrap();
5943 let rows = conn
5944 .query("INSERT INTO t(name) VALUES ('Bob') RETURNING id, name;")
5945 .await
5946 .unwrap();
5947 let vals = row_values(&rows[0]);
5948 assert!(
5950 matches!(vals[0], SqliteValue::Integer(n) if n > 0),
5951 "omitted IPK should auto-generate, got {:?}",
5952 vals[0]
5953 );
5954 assert_eq!(vals[1], SqliteValue::Text("Bob".into()));
5955 });
5956 }
5957
5958 #[test]
5960 fn ipk_delete_reinsert_same_id() {
5961 asupersync::test_utils::run_test(|| async {
5962 let conn = Connection::open(":memory:").await.unwrap();
5963 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
5964 .await
5965 .unwrap();
5966 conn.execute("INSERT INTO t VALUES (1, 'original');")
5967 .await
5968 .unwrap();
5969 conn.execute("DELETE FROM t WHERE id = 1;").await.unwrap();
5970 conn.execute("INSERT INTO t VALUES (1, 'reinserted');")
5971 .await
5972 .unwrap();
5973 let rows = conn.query("SELECT val FROM t WHERE id = 1;").await.unwrap();
5974 assert_eq!(rows.len(), 1);
5975 assert_eq!(
5976 row_values(&rows[0])[0],
5977 SqliteValue::Text("reinserted".into())
5978 );
5979 });
5980 }
5981
5982 #[test]
5990 fn index_insert_single_column() {
5991 asupersync::test_utils::run_test(|| async {
5992 let conn = Connection::open(":memory:").await.unwrap();
5993 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
5994 .await
5995 .unwrap();
5996 conn.execute("CREATE INDEX idx_name ON t(name);")
5997 .await
5998 .unwrap();
5999
6000 conn.execute("INSERT INTO t VALUES (1, 'alice');")
6001 .await
6002 .unwrap();
6003 conn.execute("INSERT INTO t VALUES (2, 'bob');")
6004 .await
6005 .unwrap();
6006 conn.execute("INSERT INTO t VALUES (3, 'charlie');")
6007 .await
6008 .unwrap();
6009
6010 let rows = conn
6012 .query("SELECT id FROM t WHERE name = 'bob';")
6013 .await
6014 .unwrap();
6015 assert_eq!(rows.len(), 1);
6016 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(2));
6017
6018 let rows = conn
6019 .query("SELECT id FROM t WHERE name = 'alice';")
6020 .await
6021 .unwrap();
6022 assert_eq!(rows.len(), 1);
6023 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
6024 });
6025 }
6026
6027 #[test]
6029 fn index_insert_multi_column() {
6030 asupersync::test_utils::run_test(|| async {
6031 let conn = Connection::open(":memory:").await.unwrap();
6032 conn.execute("CREATE TABLE t (a INT, b INT, c TEXT);")
6033 .await
6034 .unwrap();
6035 conn.execute("CREATE INDEX idx_ab ON t(a, b);")
6036 .await
6037 .unwrap();
6038
6039 conn.execute("INSERT INTO t VALUES (1, 10, 'x');")
6040 .await
6041 .unwrap();
6042 conn.execute("INSERT INTO t VALUES (1, 20, 'y');")
6043 .await
6044 .unwrap();
6045 conn.execute("INSERT INTO t VALUES (2, 10, 'z');")
6046 .await
6047 .unwrap();
6048
6049 let rows = conn
6051 .query("SELECT c FROM t WHERE a = 1 AND b = 20;")
6052 .await
6053 .unwrap();
6054 assert_eq!(rows.len(), 1);
6055 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("y".into()));
6056
6057 let rows = conn.query("SELECT c FROM t WHERE a = 1;").await.unwrap();
6059 assert_eq!(rows.len(), 2); });
6061 }
6062
6063 #[test]
6065 fn index_delete_removes_entry() {
6066 asupersync::test_utils::run_test(|| async {
6067 let conn = Connection::open(":memory:").await.unwrap();
6068 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
6069 .await
6070 .unwrap();
6071 conn.execute("CREATE INDEX idx_name ON t(name);")
6072 .await
6073 .unwrap();
6074
6075 conn.execute("INSERT INTO t VALUES (1, 'alice');")
6076 .await
6077 .unwrap();
6078 conn.execute("INSERT INTO t VALUES (2, 'bob');")
6079 .await
6080 .unwrap();
6081
6082 let rows = conn
6084 .query("SELECT id FROM t WHERE name = 'alice';")
6085 .await
6086 .unwrap();
6087 assert_eq!(rows.len(), 1);
6088
6089 conn.execute("DELETE FROM t WHERE id = 1;").await.unwrap();
6091
6092 let rows = conn
6094 .query("SELECT id FROM t WHERE name = 'alice';")
6095 .await
6096 .unwrap();
6097 assert_eq!(rows.len(), 0);
6098
6099 let rows = conn
6101 .query("SELECT id FROM t WHERE name = 'bob';")
6102 .await
6103 .unwrap();
6104 assert_eq!(rows.len(), 1);
6105 });
6106 }
6107
6108 #[test]
6110 fn index_update_indexed_column() {
6111 asupersync::test_utils::run_test(|| async {
6112 let conn = Connection::open(":memory:").await.unwrap();
6113 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
6114 .await
6115 .unwrap();
6116 conn.execute("CREATE INDEX idx_name ON t(name);")
6117 .await
6118 .unwrap();
6119
6120 conn.execute("INSERT INTO t VALUES (1, 'alice');")
6121 .await
6122 .unwrap();
6123
6124 let rows = conn
6126 .query("SELECT id FROM t WHERE name = 'alice';")
6127 .await
6128 .unwrap();
6129 assert_eq!(rows.len(), 1);
6130
6131 conn.execute("UPDATE t SET name = 'alicia' WHERE id = 1;")
6133 .await
6134 .unwrap();
6135
6136 let rows = conn
6138 .query("SELECT id FROM t WHERE name = 'alice';")
6139 .await
6140 .unwrap();
6141 assert_eq!(rows.len(), 0);
6142
6143 let rows = conn
6145 .query("SELECT id FROM t WHERE name = 'alicia';")
6146 .await
6147 .unwrap();
6148 assert_eq!(rows.len(), 1);
6149 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
6150 });
6151 }
6152
6153 #[test]
6155 fn index_update_non_indexed_column() {
6156 asupersync::test_utils::run_test(|| async {
6157 let conn = Connection::open(":memory:").await.unwrap();
6158 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT, age INT);")
6159 .await
6160 .unwrap();
6161 conn.execute("CREATE INDEX idx_name ON t(name);")
6162 .await
6163 .unwrap();
6164
6165 conn.execute("INSERT INTO t VALUES (1, 'alice', 30);")
6166 .await
6167 .unwrap();
6168
6169 conn.execute("UPDATE t SET age = 31 WHERE id = 1;")
6171 .await
6172 .unwrap();
6173
6174 let rows = conn
6176 .query("SELECT age FROM t WHERE name = 'alice';")
6177 .await
6178 .unwrap();
6179 assert_eq!(rows.len(), 1);
6180 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(31));
6181 });
6182 }
6183
6184 #[test]
6188 fn index_multiple_indexes_on_table() {
6189 asupersync::test_utils::run_test(|| async {
6190 let conn = Connection::open(":memory:").await.unwrap();
6191 conn.execute("CREATE TABLE t (a INT, b INT, c INT, d INT);")
6192 .await
6193 .unwrap();
6194 conn.execute("CREATE INDEX idx_a ON t(a);").await.unwrap();
6195 conn.execute("CREATE INDEX idx_b ON t(b);").await.unwrap();
6196 conn.execute("CREATE INDEX idx_ab ON t(a, b);")
6197 .await
6198 .unwrap();
6199 conn.execute("CREATE INDEX idx_cd ON t(c, d);")
6200 .await
6201 .unwrap();
6202
6203 conn.execute("INSERT INTO t VALUES (1, 2, 3, 4);")
6204 .await
6205 .unwrap();
6206 conn.execute("INSERT INTO t VALUES (5, 6, 7, 8);")
6207 .await
6208 .unwrap();
6209
6210 let rows = conn.query("SELECT b FROM t WHERE a = 1;").await.unwrap();
6212 assert_eq!(rows.len(), 1);
6213 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(2));
6214
6215 let rows = conn.query("SELECT a FROM t WHERE b = 6;").await.unwrap();
6216 assert_eq!(rows.len(), 1);
6217 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(5));
6218
6219 let rows = conn
6220 .query("SELECT c FROM t WHERE a = 1 AND b = 2;")
6221 .await
6222 .unwrap();
6223 assert_eq!(rows.len(), 1);
6224 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(3));
6225
6226 let rows = conn
6227 .query("SELECT a FROM t WHERE c = 7 AND d = 8;")
6228 .await
6229 .unwrap();
6230 assert_eq!(rows.len(), 1);
6231 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(5));
6232 });
6233 }
6234
6235 #[test]
6237 fn index_delete_removes_from_all_indexes() {
6238 asupersync::test_utils::run_test(|| async {
6239 let conn = Connection::open(":memory:").await.unwrap();
6240 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, a INT, b INT);")
6241 .await
6242 .unwrap();
6243 conn.execute("CREATE INDEX idx_a ON t(a);").await.unwrap();
6244 conn.execute("CREATE INDEX idx_b ON t(b);").await.unwrap();
6245
6246 conn.execute("INSERT INTO t VALUES (1, 10, 100);")
6247 .await
6248 .unwrap();
6249 conn.execute("INSERT INTO t VALUES (2, 20, 200);")
6250 .await
6251 .unwrap();
6252
6253 conn.execute("DELETE FROM t WHERE id = 1;").await.unwrap();
6255
6256 let rows = conn.query("SELECT id FROM t WHERE a = 10;").await.unwrap();
6258 assert_eq!(rows.len(), 0);
6259
6260 let rows = conn.query("SELECT id FROM t WHERE b = 100;").await.unwrap();
6261 assert_eq!(rows.len(), 0);
6262
6263 let rows = conn.query("SELECT id FROM t WHERE a = 20;").await.unwrap();
6265 assert_eq!(rows.len(), 1);
6266
6267 let rows = conn.query("SELECT id FROM t WHERE b = 200;").await.unwrap();
6268 assert_eq!(rows.len(), 1);
6269 });
6270 }
6271
6272 #[test]
6274 fn index_update_maintains_all_indexes() {
6275 asupersync::test_utils::run_test(|| async {
6276 let conn = Connection::open(":memory:").await.unwrap();
6277 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, a INT, b INT);")
6278 .await
6279 .unwrap();
6280 conn.execute("CREATE INDEX idx_a ON t(a);").await.unwrap();
6281 conn.execute("CREATE INDEX idx_b ON t(b);").await.unwrap();
6282
6283 conn.execute("INSERT INTO t VALUES (1, 10, 100);")
6284 .await
6285 .unwrap();
6286
6287 conn.execute("UPDATE t SET a = 11, b = 101 WHERE id = 1;")
6289 .await
6290 .unwrap();
6291
6292 let rows = conn.query("SELECT id FROM t WHERE a = 10;").await.unwrap();
6294 assert_eq!(rows.len(), 0);
6295 let rows = conn.query("SELECT id FROM t WHERE b = 100;").await.unwrap();
6296 assert_eq!(rows.len(), 0);
6297
6298 let rows = conn.query("SELECT id FROM t WHERE a = 11;").await.unwrap();
6300 assert_eq!(rows.len(), 1);
6301 let rows = conn.query("SELECT id FROM t WHERE b = 101;").await.unwrap();
6302 assert_eq!(rows.len(), 1);
6303 });
6304 }
6305
6306 #[test]
6313 fn index_with_null_values() {
6314 asupersync::test_utils::run_test(|| async {
6315 let conn = Connection::open(":memory:").await.unwrap();
6316 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
6317 .await
6318 .unwrap();
6319 conn.execute("CREATE INDEX idx_name ON t(name);")
6320 .await
6321 .unwrap();
6322
6323 conn.execute("INSERT INTO t VALUES (1, NULL);")
6324 .await
6325 .unwrap();
6326 conn.execute("INSERT INTO t VALUES (2, 'alice');")
6327 .await
6328 .unwrap();
6329 conn.execute("INSERT INTO t VALUES (3, NULL);")
6330 .await
6331 .unwrap();
6332
6333 let rows = conn
6335 .query("SELECT id FROM t WHERE name IS NULL;")
6336 .await
6337 .unwrap();
6338 assert_eq!(rows.len(), 2);
6339
6340 let rows = conn
6342 .query("SELECT id FROM t WHERE name = 'alice';")
6343 .await
6344 .unwrap();
6345 assert_eq!(rows.len(), 1);
6346 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(2));
6347 });
6348 }
6349
6350 #[test]
6352 fn index_update_null_to_non_null() {
6353 asupersync::test_utils::run_test(|| async {
6354 let conn = Connection::open(":memory:").await.unwrap();
6355 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
6356 .await
6357 .unwrap();
6358 conn.execute("CREATE INDEX idx_name ON t(name);")
6359 .await
6360 .unwrap();
6361
6362 conn.execute("INSERT INTO t VALUES (1, NULL);")
6363 .await
6364 .unwrap();
6365 conn.execute("INSERT INTO t VALUES (2, NULL);")
6366 .await
6367 .unwrap();
6368
6369 let rows = conn
6371 .query("SELECT id FROM t WHERE name IS NULL;")
6372 .await
6373 .unwrap();
6374 assert_eq!(rows.len(), 2);
6375
6376 conn.execute("UPDATE t SET name = 'bob' WHERE id = 1;")
6378 .await
6379 .unwrap();
6380
6381 let rows = conn
6383 .query("SELECT id FROM t WHERE name IS NULL;")
6384 .await
6385 .unwrap();
6386 assert_eq!(rows.len(), 1);
6387 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(2));
6388
6389 let rows = conn
6391 .query("SELECT id FROM t WHERE name = 'bob';")
6392 .await
6393 .unwrap();
6394 assert_eq!(rows.len(), 1);
6395 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
6396 });
6397 }
6398
6399 #[test]
6401 fn index_update_non_null_to_null() {
6402 asupersync::test_utils::run_test(|| async {
6403 let conn = Connection::open(":memory:").await.unwrap();
6404 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
6405 .await
6406 .unwrap();
6407 conn.execute("CREATE INDEX idx_name ON t(name);")
6408 .await
6409 .unwrap();
6410
6411 conn.execute("INSERT INTO t VALUES (1, 'alice');")
6412 .await
6413 .unwrap();
6414
6415 let rows = conn
6417 .query("SELECT id FROM t WHERE name = 'alice';")
6418 .await
6419 .unwrap();
6420 assert_eq!(rows.len(), 1);
6421
6422 conn.execute("UPDATE t SET name = NULL WHERE id = 1;")
6424 .await
6425 .unwrap();
6426
6427 let rows = conn
6429 .query("SELECT id FROM t WHERE name = 'alice';")
6430 .await
6431 .unwrap();
6432 assert_eq!(rows.len(), 0);
6433
6434 let rows = conn
6436 .query("SELECT id FROM t WHERE name IS NULL;")
6437 .await
6438 .unwrap();
6439 assert_eq!(rows.len(), 1);
6440 });
6441 }
6442
6443 #[test]
6447 fn index_bulk_insert() {
6448 asupersync::test_utils::run_test(|| async {
6449 let conn = Connection::open(":memory:").await.unwrap();
6450 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value INT);")
6451 .await
6452 .unwrap();
6453 conn.execute("CREATE INDEX idx_value ON t(value);")
6454 .await
6455 .unwrap();
6456
6457 for i in 0..100 {
6459 conn.execute(&format!("INSERT INTO t VALUES ({}, {});", i, i * 2))
6460 .await
6461 .unwrap();
6462 }
6463
6464 for i in [0, 25, 50, 75, 99] {
6466 let rows = conn
6467 .query(&format!("SELECT id FROM t WHERE value = {};", i * 2))
6468 .await
6469 .unwrap();
6470 assert_eq!(rows.len(), 1, "Should find row with value={}", i * 2);
6471 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(i));
6472 }
6473 });
6474 }
6475
6476 #[test]
6478 fn index_bulk_delete() {
6479 asupersync::test_utils::run_test(|| async {
6480 let conn = Connection::open(":memory:").await.unwrap();
6481 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value INT);")
6482 .await
6483 .unwrap();
6484 conn.execute("CREATE INDEX idx_value ON t(value);")
6485 .await
6486 .unwrap();
6487
6488 for i in 0..50 {
6490 conn.execute(&format!("INSERT INTO t VALUES ({}, {});", i, i))
6491 .await
6492 .unwrap();
6493 }
6494
6495 for i in (0..50).step_by(2) {
6497 conn.execute(&format!("DELETE FROM t WHERE id = {};", i))
6498 .await
6499 .unwrap();
6500 }
6501
6502 for i in (0..50).step_by(2) {
6504 let rows = conn
6505 .query(&format!("SELECT id FROM t WHERE value = {};", i))
6506 .await
6507 .unwrap();
6508 assert_eq!(
6509 rows.len(),
6510 0,
6511 "Deleted row with value={} should not exist",
6512 i
6513 );
6514 }
6515
6516 for i in (1..50).step_by(2) {
6518 let rows = conn
6519 .query(&format!("SELECT id FROM t WHERE value = {};", i))
6520 .await
6521 .unwrap();
6522 assert_eq!(rows.len(), 1, "Row with value={} should exist", i);
6523 }
6524 });
6525 }
6526
6527 #[test]
6529 fn index_bulk_update() {
6530 asupersync::test_utils::run_test(|| async {
6531 let conn = Connection::open(":memory:").await.unwrap();
6532 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value INT);")
6533 .await
6534 .unwrap();
6535 conn.execute("CREATE INDEX idx_value ON t(value);")
6536 .await
6537 .unwrap();
6538
6539 for i in 0..50 {
6541 conn.execute(&format!("INSERT INTO t VALUES ({}, {});", i, i))
6542 .await
6543 .unwrap();
6544 }
6545
6546 conn.execute("UPDATE t SET value = value + 1000;")
6548 .await
6549 .unwrap();
6550
6551 for i in 0..50 {
6553 let rows = conn
6554 .query(&format!("SELECT id FROM t WHERE value = {};", i))
6555 .await
6556 .unwrap();
6557 assert_eq!(rows.len(), 0);
6558 }
6559
6560 for i in 0..50 {
6562 let rows = conn
6563 .query(&format!("SELECT id FROM t WHERE value = {};", i + 1000))
6564 .await
6565 .unwrap();
6566 assert_eq!(rows.len(), 1);
6567 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(i));
6568 }
6569 });
6570 }
6571
6572 #[test]
6576 fn index_rollback_insert() {
6577 asupersync::test_utils::run_test(|| async {
6578 let conn = Connection::open(":memory:").await.unwrap();
6579 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
6580 .await
6581 .unwrap();
6582 conn.execute("CREATE INDEX idx_name ON t(name);")
6583 .await
6584 .unwrap();
6585 conn.execute("INSERT INTO t VALUES (1, 'alice');")
6586 .await
6587 .unwrap();
6588
6589 conn.execute("BEGIN;").await.unwrap();
6590 conn.execute("INSERT INTO t VALUES (2, 'bob');")
6591 .await
6592 .unwrap();
6593
6594 let rows = conn
6596 .query("SELECT id FROM t WHERE name = 'bob';")
6597 .await
6598 .unwrap();
6599 assert_eq!(rows.len(), 1);
6600
6601 conn.execute("ROLLBACK;").await.unwrap();
6602
6603 let rows = conn
6605 .query("SELECT id FROM t WHERE name = 'bob';")
6606 .await
6607 .unwrap();
6608 assert_eq!(rows.len(), 0);
6609
6610 let rows = conn
6612 .query("SELECT id FROM t WHERE name = 'alice';")
6613 .await
6614 .unwrap();
6615 assert_eq!(rows.len(), 1);
6616 });
6617 }
6618
6619 #[test]
6621 fn index_rollback_delete() {
6622 asupersync::test_utils::run_test(|| async {
6623 let conn = Connection::open(":memory:").await.unwrap();
6624 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
6625 .await
6626 .unwrap();
6627 conn.execute("CREATE INDEX idx_name ON t(name);")
6628 .await
6629 .unwrap();
6630 conn.execute("INSERT INTO t VALUES (1, 'alice');")
6631 .await
6632 .unwrap();
6633
6634 conn.execute("BEGIN;").await.unwrap();
6635 conn.execute("DELETE FROM t WHERE id = 1;").await.unwrap();
6636
6637 let rows = conn
6639 .query("SELECT id FROM t WHERE name = 'alice';")
6640 .await
6641 .unwrap();
6642 assert_eq!(rows.len(), 0);
6643
6644 conn.execute("ROLLBACK;").await.unwrap();
6645
6646 let rows = conn
6648 .query("SELECT id FROM t WHERE name = 'alice';")
6649 .await
6650 .unwrap();
6651 assert_eq!(rows.len(), 1);
6652 });
6653 }
6654
6655 #[test]
6657 fn index_rollback_update() {
6658 asupersync::test_utils::run_test(|| async {
6659 let conn = Connection::open(":memory:").await.unwrap();
6660 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
6661 .await
6662 .unwrap();
6663 conn.execute("CREATE INDEX idx_name ON t(name);")
6664 .await
6665 .unwrap();
6666 conn.execute("INSERT INTO t VALUES (1, 'alice');")
6667 .await
6668 .unwrap();
6669
6670 conn.execute("BEGIN;").await.unwrap();
6671 conn.execute("UPDATE t SET name = 'bob' WHERE id = 1;")
6672 .await
6673 .unwrap();
6674
6675 let rows = conn
6677 .query("SELECT id FROM t WHERE name = 'bob';")
6678 .await
6679 .unwrap();
6680 assert_eq!(rows.len(), 1);
6681 let rows = conn
6682 .query("SELECT id FROM t WHERE name = 'alice';")
6683 .await
6684 .unwrap();
6685 assert_eq!(rows.len(), 0);
6686
6687 conn.execute("ROLLBACK;").await.unwrap();
6688
6689 let rows = conn
6691 .query("SELECT id FROM t WHERE name = 'alice';")
6692 .await
6693 .unwrap();
6694 assert_eq!(rows.len(), 1);
6695 let rows = conn
6696 .query("SELECT id FROM t WHERE name = 'bob';")
6697 .await
6698 .unwrap();
6699 assert_eq!(rows.len(), 0);
6700 });
6701 }
6702
6703 #[test]
6707 fn index_no_reindex_needed() {
6708 asupersync::test_utils::run_test(|| async {
6709 let conn = Connection::open(":memory:").await.unwrap();
6710 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
6711 .await
6712 .unwrap();
6713 conn.execute("CREATE INDEX idx_name ON t(name);")
6714 .await
6715 .unwrap();
6716
6717 for i in 0..100 {
6719 conn.execute(&format!("INSERT INTO t VALUES ({}, 'name{}');", i, i))
6720 .await
6721 .unwrap();
6722 }
6723 for i in 0..50 {
6724 conn.execute(&format!("DELETE FROM t WHERE id = {};", i))
6725 .await
6726 .unwrap();
6727 }
6728 for i in 50..100 {
6729 conn.execute(&format!(
6730 "UPDATE t SET name = 'updated{}' WHERE id = {};",
6731 i, i
6732 ))
6733 .await
6734 .unwrap();
6735 }
6736
6737 for i in 50..100 {
6739 let rows = conn
6740 .query(&format!("SELECT id FROM t WHERE name = 'updated{}';", i))
6741 .await
6742 .unwrap();
6743 assert_eq!(
6744 rows.len(),
6745 1,
6746 "Row with updated name for id={} should be findable",
6747 i
6748 );
6749 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(i));
6750 }
6751
6752 for i in 0..50 {
6754 let rows = conn
6755 .query(&format!("SELECT id FROM t WHERE name = 'name{}';", i))
6756 .await
6757 .unwrap();
6758 assert_eq!(
6759 rows.len(),
6760 0,
6761 "Deleted row with name{} should not be findable",
6762 i
6763 );
6764 }
6765
6766 let rows = conn.query("SELECT COUNT(*) FROM t;").await.unwrap();
6768 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(50));
6769 });
6770 }
6771
6772 #[test]
6776 fn index_on_ipk_column() {
6777 asupersync::test_utils::run_test(|| async {
6778 let conn = Connection::open(":memory:").await.unwrap();
6779 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
6780 .await
6781 .unwrap();
6782 conn.execute("CREATE INDEX idx_id ON t(id);").await.unwrap();
6783
6784 conn.execute("INSERT INTO t VALUES (100, 'alice');")
6785 .await
6786 .unwrap();
6787 conn.execute("INSERT INTO t VALUES (200, 'bob');")
6788 .await
6789 .unwrap();
6790
6791 let rows = conn
6793 .query("SELECT name FROM t WHERE id = 100;")
6794 .await
6795 .unwrap();
6796 assert_eq!(rows.len(), 1);
6797 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("alice".into()));
6798 });
6799 }
6800
6801 #[test]
6805 fn index_mixed_operations_sequence() {
6806 asupersync::test_utils::run_test(|| async {
6807 let conn = Connection::open(":memory:").await.unwrap();
6808 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, a INT, b TEXT);")
6809 .await
6810 .unwrap();
6811 conn.execute("CREATE INDEX idx_a ON t(a);").await.unwrap();
6812 conn.execute("CREATE INDEX idx_b ON t(b);").await.unwrap();
6813
6814 conn.execute("INSERT INTO t VALUES (1, 10, 'x');")
6816 .await
6817 .unwrap();
6818 conn.execute("INSERT INTO t VALUES (2, 20, 'y');")
6819 .await
6820 .unwrap();
6821 conn.execute("INSERT INTO t VALUES (3, 30, 'z');")
6822 .await
6823 .unwrap();
6824
6825 conn.execute("UPDATE t SET a = 15 WHERE id = 1;")
6827 .await
6828 .unwrap();
6829
6830 conn.execute("DELETE FROM t WHERE id = 2;").await.unwrap();
6832
6833 conn.execute("INSERT INTO t VALUES (4, 40, 'w');")
6835 .await
6836 .unwrap();
6837
6838 let rows = conn.query("SELECT id FROM t WHERE a = 10;").await.unwrap();
6840 assert_eq!(rows.len(), 0); let rows = conn.query("SELECT id FROM t WHERE a = 15;").await.unwrap();
6843 assert_eq!(rows.len(), 1);
6844 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
6845
6846 let rows = conn.query("SELECT id FROM t WHERE b = 'y';").await.unwrap();
6847 assert_eq!(rows.len(), 0); let rows = conn.query("SELECT id FROM t WHERE a = 40;").await.unwrap();
6850 assert_eq!(rows.len(), 1);
6851 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(4));
6852 });
6853 }
6854
6855 #[test]
6865 fn concurrent_writers_stress_conservation() {
6866 if supervise_concurrent_writer_stress() {
6867 return;
6868 }
6869
6870 use rand::prelude::*;
6871 use std::thread;
6872
6873 const NUM_ACCOUNTS: i64 = 100;
6874 const INITIAL_BALANCE: i64 = 1_000;
6875 const EXPECTED_TOTAL: i64 = NUM_ACCOUNTS * INITIAL_BALANCE;
6876 const NUM_WRITERS: usize = 8;
6877 const OPS_PER_WRITER: u64 = 20;
6878
6879 let dir = tempfile::tempdir().expect("create concurrent-stress temp dir");
6880 let db_path = dir.path().join("stress.db");
6881 let db_path_string = db_path.to_string_lossy().into_owned();
6882
6883 asupersync::test_utils::run_test(|| async {
6884 let conn = Connection::open(&db_path_string)
6885 .await
6886 .expect("open concurrent-stress database for setup");
6887 assert!(
6888 conn.is_concurrent_mode_default(),
6889 "setup connection must preserve the concurrent-writer default"
6890 );
6891 conn.execute(
6892 "CREATE TABLE accounts (
6893 id INTEGER PRIMARY KEY,
6894 balance INTEGER,
6895 payload TEXT NOT NULL
6896 );",
6897 )
6898 .await
6899 .expect("create accounts table");
6900 let payload = "x".repeat(512);
6901 for account_id in 0..NUM_ACCOUNTS {
6902 conn.execute_with_params(
6903 "INSERT INTO accounts VALUES (?1, ?2, ?3);",
6904 &[
6905 SqliteValue::Integer(account_id),
6906 SqliteValue::Integer(INITIAL_BALANCE),
6907 SqliteValue::Text(payload.clone().into()),
6908 ],
6909 )
6910 .await
6911 .expect("insert initial account");
6912 }
6913 let rows = conn
6914 .query(
6915 "SELECT COUNT(*), SUM(balance), MIN(length(payload)), MAX(length(payload))
6916 FROM accounts;",
6917 )
6918 .await
6919 .expect("query initial account invariants");
6920 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(NUM_ACCOUNTS));
6921 assert_eq!(
6922 row_values(&rows[0])[1],
6923 SqliteValue::Integer(EXPECTED_TOTAL)
6924 );
6925 assert_eq!(row_values(&rows[0])[2], SqliteValue::Integer(512));
6926 assert_eq!(row_values(&rows[0])[3], SqliteValue::Integer(512));
6927 let page_count = conn
6928 .query_row("PRAGMA page_count;")
6929 .await
6930 .expect("query multi-page setup size");
6931 assert!(
6932 matches!(row_values(&page_count).as_slice(), [SqliteValue::Integer(count)] if *count > 2),
6933 "fixed-width account payloads must span multiple database pages: {page_count:?}"
6934 );
6935 conn.close()
6936 .await
6937 .expect("close concurrent-stress setup connection");
6938 });
6939
6940 let (startup_tx, startup_rx) = mpsc::channel::<ConcurrentStressStartup>();
6941 let mut start_senders = Vec::with_capacity(NUM_WRITERS);
6942 let mut handles = Vec::with_capacity(NUM_WRITERS);
6943
6944 for worker_id in 0..NUM_WRITERS {
6945 let path = db_path_string.clone();
6946 let startup_tx = startup_tx.clone();
6947 let (start_tx, start_rx) = mpsc::sync_channel(1);
6948 start_senders.push(start_tx);
6949 handles.push(thread::spawn(move || {
6950 let mut outcome = ConcurrentStressWorkerOutcome::pending(worker_id);
6951 let started_at = Instant::now();
6952 asupersync::test_utils::run_test(|| async {
6953 let mut open_attempts = 0_u64;
6954 let mut last_open_error = None;
6955 let mut conn = loop {
6956 if open_attempts >= CONCURRENT_STRESS_MAX_ATTEMPTS_PER_COMMIT
6957 || outcome.attempts >= CONCURRENT_STRESS_MAX_ATTEMPTS_PER_WORKER
6958 || started_at.elapsed() >= CONCURRENT_STRESS_STARTUP_TIMEOUT
6959 {
6960 let message = format!(
6961 "connection open exhausted its bounded retry budget after {open_attempts} attempts; last transient error: {}",
6962 last_open_error.as_deref().unwrap_or("none")
6963 );
6964 let _ = startup_tx.send(ConcurrentStressStartup::Failed {
6965 worker_id,
6966 error: message.clone(),
6967 });
6968 outcome.failure = Some(message);
6969 return;
6970 }
6971
6972 open_attempts += 1;
6973 outcome.attempts += 1;
6974 match Connection::open(&path).await {
6975 Ok(conn) => break conn,
6976 Err(error) if outcome.retries.record(&error) => {
6977 last_open_error = Some(format!("connection open: {error:?}"));
6978 concurrent_stress_backoff(
6979 open_attempts,
6980 u64::try_from(worker_id).expect("worker id fits u64"),
6981 );
6982 }
6983 Err(error) => {
6984 let message = format!("connection open failed: {error:?}");
6985 let _ = startup_tx.send(ConcurrentStressStartup::Failed {
6986 worker_id,
6987 error: message.clone(),
6988 });
6989 outcome.failure = Some(message);
6990 return;
6991 }
6992 }
6993 };
6994 'worker: {
6995 outcome.concurrent_mode_default = conn.is_concurrent_mode_default();
6996 if !outcome.concurrent_mode_default {
6997 let message = "concurrent mode is not enabled by default".to_owned();
6998 let _ = startup_tx.send(ConcurrentStressStartup::Failed {
6999 worker_id,
7000 error: message.clone(),
7001 });
7002 outcome.failure = Some(message);
7003 break 'worker;
7004 }
7005 if startup_tx
7006 .send(ConcurrentStressStartup::Ready { worker_id })
7007 .is_err()
7008 {
7009 outcome.failure = Some("startup coordinator disconnected".to_owned());
7010 break 'worker;
7011 }
7012 match start_rx.recv() {
7013 Ok(ConcurrentStressStartDecision::Run) => {}
7014 Ok(ConcurrentStressStartDecision::Abort) => {
7015 outcome.failure = Some("startup coordinator aborted the run".to_owned());
7016 break 'worker;
7017 }
7018 Err(error) => {
7019 outcome.failure =
7020 Some(format!("startup decision channel disconnected: {error}"));
7021 break 'worker;
7022 }
7023 }
7024
7025 outcome.failure = None;
7026 let mut attempts_for_commit = 0_u64;
7027 let mut last_transient_error: Option<String> = None;
7028 let mut rng = rand::rngs::StdRng::seed_from_u64(worker_id as u64);
7029
7030 'transfers: while outcome.commits < OPS_PER_WRITER {
7031 if let Some(budget_error) = concurrent_stress_attempt_budget_error(
7032 attempts_for_commit,
7033 outcome.attempts,
7034 started_at.elapsed(),
7035 ) {
7036 outcome.failure = Some(format!(
7037 "{budget_error}; last transient error: {}",
7038 last_transient_error.as_deref().unwrap_or("none")
7039 ));
7040 break;
7041 }
7042
7043 outcome.attempts += 1;
7044 attempts_for_commit += 1;
7045 outcome.max_attempts_for_commit =
7046 outcome.max_attempts_for_commit.max(attempts_for_commit);
7047
7048 let from_id = rng.random_range(0..NUM_ACCOUNTS);
7049 let to_id = rng.random_range(0..NUM_ACCOUNTS);
7050 if from_id == to_id {
7051 continue;
7052 }
7053 let amount = rng.random_range(1..=10_i64);
7054
7055 if let Err(error) = conn.execute("BEGIN;").await {
7056 if outcome.retries.record(&error) {
7057 last_transient_error = Some(format!("BEGIN: {error:?}"));
7058 concurrent_stress_backoff(
7059 attempts_for_commit,
7060 u64::try_from(worker_id).expect("worker id fits u64"),
7061 );
7062 continue;
7063 }
7064 outcome.failure =
7065 Some(format!("unexpected BEGIN error: {error:?}"));
7066 break;
7067 }
7068 let begin_seq = conn
7069 .current_concurrent_snapshot_seq()
7070 .expect("successful concurrent BEGIN must bind its snapshot sequence");
7071
7072 let from_balance = match conn
7073 .query(&format!(
7074 "SELECT balance FROM accounts WHERE id = {from_id};"
7075 ))
7076 .await
7077 {
7078 Ok(rows) if rows.len() == 1 => match &row_values(&rows[0])[0] {
7079 SqliteValue::Integer(balance) => *balance,
7080 other => {
7081 if let Err(rollback_error) = conn.execute("ROLLBACK;").await {
7082 outcome.failure = Some(format!(
7083 "rollback after invalid balance type failed: {rollback_error:?}"
7084 ));
7085 } else {
7086 outcome.failure = Some(format!(
7087 "balance query returned invalid value: {other:?}"
7088 ));
7089 }
7090 break 'transfers;
7091 }
7092 },
7093 Ok(rows) => {
7094 if let Err(rollback_error) = conn.execute("ROLLBACK;").await {
7095 outcome.failure = Some(format!(
7096 "rollback after missing account failed: {rollback_error:?}"
7097 ));
7098 } else {
7099 outcome.failure = Some(format!(
7100 "balance query returned {} rows for account {from_id}",
7101 rows.len()
7102 ));
7103 }
7104 break;
7105 }
7106 Err(error) => {
7107 let transient_error = format!("balance query: {error:?}");
7108 match concurrent_stress_rollback_precommit_transient(
7109 &conn,
7110 &mut outcome,
7111 "balance query",
7112 &error,
7113 )
7114 .await
7115 {
7116 Ok(()) => {
7117 last_transient_error = Some(transient_error);
7118 concurrent_stress_backoff(
7119 attempts_for_commit,
7120 u64::try_from(worker_id).expect("worker id fits u64"),
7121 );
7122 continue;
7123 }
7124 Err(recovery_error) => {
7125 outcome.failure = Some(recovery_error);
7126 break;
7127 }
7128 }
7129 }
7130 };
7131
7132 if from_balance < amount {
7133 if let Err(error) = conn.execute("ROLLBACK;").await {
7134 outcome.failure = Some(format!(
7135 "rollback after insufficient balance failed: {error:?}"
7136 ));
7137 break;
7138 }
7139 continue;
7140 }
7141
7142 match conn
7143 .execute(&format!(
7144 "UPDATE accounts SET balance = balance - {amount} WHERE id = {from_id};"
7145 ))
7146 .await
7147 {
7148 Ok(1) => {}
7149 Ok(affected) => {
7150 if let Err(rollback_error) = conn.execute("ROLLBACK;").await {
7151 outcome.failure = Some(format!(
7152 "rollback after debit affected {affected} rows failed: {rollback_error:?}"
7153 ));
7154 } else {
7155 outcome.failure = Some(format!(
7156 "debit affected {affected} rows; expected 1"
7157 ));
7158 }
7159 break;
7160 }
7161 Err(error) => {
7162 let transient_error = format!("debit: {error:?}");
7163 match concurrent_stress_rollback_precommit_transient(
7164 &conn,
7165 &mut outcome,
7166 "debit",
7167 &error,
7168 )
7169 .await
7170 {
7171 Ok(()) => {
7172 last_transient_error = Some(transient_error);
7173 concurrent_stress_backoff(
7174 attempts_for_commit,
7175 u64::try_from(worker_id).expect("worker id fits u64"),
7176 );
7177 continue;
7178 }
7179 Err(recovery_error) => {
7180 outcome.failure = Some(recovery_error);
7181 break;
7182 }
7183 }
7184 }
7185 }
7186
7187 match conn
7188 .execute(&format!(
7189 "UPDATE accounts SET balance = balance + {amount} WHERE id = {to_id};"
7190 ))
7191 .await
7192 {
7193 Ok(1) => {}
7194 Ok(affected) => {
7195 if let Err(rollback_error) = conn.execute("ROLLBACK;").await {
7196 outcome.failure = Some(format!(
7197 "rollback after credit affected {affected} rows failed: {rollback_error:?}"
7198 ));
7199 } else {
7200 outcome.failure = Some(format!(
7201 "credit affected {affected} rows; expected 1"
7202 ));
7203 }
7204 break;
7205 }
7206 Err(error) => {
7207 let transient_error = format!("credit: {error:?}");
7208 match concurrent_stress_rollback_precommit_transient(
7209 &conn,
7210 &mut outcome,
7211 "credit",
7212 &error,
7213 )
7214 .await
7215 {
7216 Ok(()) => {
7217 last_transient_error = Some(transient_error);
7218 concurrent_stress_backoff(
7219 attempts_for_commit,
7220 u64::try_from(worker_id).expect("worker id fits u64"),
7221 );
7222 continue;
7223 }
7224 Err(recovery_error) => {
7225 outcome.failure = Some(recovery_error);
7226 break;
7227 }
7228 }
7229 }
7230 }
7231
7232 match conn.execute("COMMIT;").await {
7233 Ok(_) => {
7234 outcome.commits += 1;
7235 let commit_seq = conn.last_local_commit_seq().expect(
7236 "successful concurrent commit must publish its sequence",
7237 );
7238 outcome.committed_transfers.push(ConcurrentStressTransfer {
7239 from_id,
7240 to_id,
7241 amount,
7242 begin_seq,
7243 commit_seq,
7244 });
7245 attempts_for_commit = 0;
7246 last_transient_error = None;
7247 }
7248 Err(error) => {
7249 let transient_error = format!("commit: {error:?}");
7250 match concurrent_stress_rollback_precommit_transient(
7251 &conn,
7252 &mut outcome,
7253 "commit",
7254 &error,
7255 )
7256 .await
7257 {
7258 Ok(()) => {
7259 last_transient_error = Some(transient_error);
7260 concurrent_stress_backoff(
7261 attempts_for_commit,
7262 u64::try_from(worker_id).expect("worker id fits u64"),
7263 );
7264 }
7265 Err(recovery_error) => {
7266 outcome.failure = Some(recovery_error);
7267 break;
7268 }
7269 }
7270 }
7271 }
7272 }
7273
7274 outcome.elapsed = started_at.elapsed();
7275 if outcome.failure.is_none()
7276 && outcome.elapsed > CONCURRENT_STRESS_WORKER_TIMEOUT
7277 {
7278 outcome.failure = Some(format!(
7279 "completed after worker deadline {:?}: {:?}",
7280 CONCURRENT_STRESS_WORKER_TIMEOUT, outcome.elapsed
7281 ));
7282 }
7283 }
7284 if let Err(error) = conn.close_without_checkpoint_in_place().await {
7285 let close_failure = format!("worker connection close failed: {error:?}");
7286 if let Some(failure) = &mut outcome.failure {
7287 failure.push_str("; ");
7288 failure.push_str(&close_failure);
7289 } else {
7290 outcome.failure = Some(close_failure);
7291 }
7292 conn.close_best_effort_in_place().await;
7293 }
7294 });
7295 outcome
7296 }));
7297 }
7298 drop(startup_tx);
7299
7300 let mut ready = [false; NUM_WRITERS];
7301 let mut ready_count = 0_usize;
7302 let startup_deadline = Instant::now() + CONCURRENT_STRESS_STARTUP_TIMEOUT;
7303 let mut startup_failure = None;
7304 while ready_count < NUM_WRITERS {
7305 let remaining = startup_deadline.saturating_duration_since(Instant::now());
7306 if remaining.is_zero() {
7307 startup_failure = Some(format!(
7308 "startup timed out with {ready_count}/{NUM_WRITERS} workers ready"
7309 ));
7310 break;
7311 }
7312 match startup_rx.recv_timeout(remaining) {
7313 Ok(ConcurrentStressStartup::Ready { worker_id }) => {
7314 if worker_id >= NUM_WRITERS {
7315 startup_failure =
7316 Some(format!("out-of-range startup worker id {worker_id}"));
7317 break;
7318 }
7319 if std::mem::replace(&mut ready[worker_id], true) {
7320 startup_failure =
7321 Some(format!("duplicate startup receipt from worker {worker_id}"));
7322 break;
7323 }
7324 ready_count += 1;
7325 }
7326 Ok(ConcurrentStressStartup::Failed { worker_id, error }) => {
7327 startup_failure = Some(format!("worker {worker_id} startup failed: {error}"));
7328 break;
7329 }
7330 Err(mpsc::RecvTimeoutError::Timeout) => {
7331 startup_failure = Some(format!(
7332 "startup timed out with {ready_count}/{NUM_WRITERS} workers ready"
7333 ));
7334 break;
7335 }
7336 Err(mpsc::RecvTimeoutError::Disconnected) => {
7337 startup_failure = Some(format!(
7338 "startup channel closed with {ready_count}/{NUM_WRITERS} workers ready"
7339 ));
7340 break;
7341 }
7342 }
7343 }
7344
7345 let start_gate = ConcurrentStressStartGate::new(start_senders);
7346 let startup_result = if let Some(error) = startup_failure {
7347 drop(start_gate);
7348 Err(error)
7349 } else {
7350 start_gate.release()
7351 };
7352
7353 let mut results = Vec::with_capacity(NUM_WRITERS);
7354 let mut panics = Vec::new();
7355 for (worker_id, handle) in handles.into_iter().enumerate() {
7356 match handle.join() {
7357 Ok(outcome) => results.push(outcome),
7358 Err(payload) => panics.push(format!(
7359 "worker {worker_id} panicked: {}",
7360 concurrent_stress_panic_message(payload.as_ref())
7361 )),
7362 }
7363 }
7364 assert!(
7365 panics.is_empty(),
7366 "concurrent-stress worker panics: {panics:?}"
7367 );
7368 assert!(
7369 startup_result.is_ok(),
7370 "concurrent-stress startup failed: {}; outcomes: {results:#?}",
7371 startup_result
7372 .as_ref()
7373 .expect_err("failed startup must carry a diagnostic")
7374 );
7375 assert_eq!(results.len(), NUM_WRITERS, "missing worker outcomes");
7376 results.sort_by_key(|outcome| outcome.worker_id);
7377
7378 for outcome in &results {
7379 eprintln!("concurrent stress worker outcome: {outcome:#?}");
7380 }
7381
7382 let stock_diagnostic = (|| -> Result<ConcurrentStressStockDiagnostic, String> {
7389 let stock = rusqlite::Connection::open(&db_path).map_err(|error| error.to_string())?;
7390 let row_count = stock
7391 .query_row("SELECT COUNT(*) FROM accounts;", [], |row| row.get(0))
7392 .map_err(|error| error.to_string())?;
7393 let balance_sum = stock
7394 .query_row("SELECT SUM(balance) FROM accounts;", [], |row| row.get(0))
7395 .map_err(|error| error.to_string())?;
7396 let point_count = stock
7397 .query_row("SELECT COUNT(*) FROM accounts WHERE id = 9;", [], |row| {
7398 row.get(0)
7399 })
7400 .map_err(|error| error.to_string())?;
7401 let scan_count = stock
7402 .query_row(
7403 "SELECT COUNT(*) FROM accounts NOT INDEXED WHERE id + 0 = 9;",
7404 [],
7405 |row| row.get(0),
7406 )
7407 .map_err(|error| error.to_string())?;
7408 let mut statement = stock
7409 .prepare("PRAGMA integrity_check;")
7410 .map_err(|error| error.to_string())?;
7411 let integrity = statement
7412 .query_map([], |row| row.get::<_, String>(0))
7413 .map_err(|error| error.to_string())?
7414 .collect::<rusqlite::Result<Vec<_>>>()
7415 .map_err(|error| error.to_string())?;
7416 let mut statement = stock
7417 .prepare("SELECT id, balance FROM accounts ORDER BY id;")
7418 .map_err(|error| error.to_string())?;
7419 let balances = statement
7420 .query_map([], |row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)))
7421 .map_err(|error| error.to_string())?
7422 .collect::<rusqlite::Result<Vec<_>>>()
7423 .map_err(|error| error.to_string())?;
7424 Ok(ConcurrentStressStockDiagnostic {
7425 row_count,
7426 balance_sum,
7427 point_count,
7428 scan_count,
7429 integrity,
7430 balances,
7431 })
7432 })();
7433 eprintln!("concurrent stress stock SQLite diagnostic: {stock_diagnostic:#?}");
7434
7435 if let Ok(ConcurrentStressStockDiagnostic {
7436 balances: durable_balances,
7437 ..
7438 }) = &stock_diagnostic
7439 {
7440 let account_count =
7441 usize::try_from(NUM_ACCOUNTS).expect("concurrent stress account count fits usize");
7442 let mut expected_balances = vec![INITIAL_BALANCE; account_count];
7443 for transfer in results
7444 .iter()
7445 .flat_map(|outcome| &outcome.committed_transfers)
7446 {
7447 assert!(
7448 transfer.commit_seq > transfer.begin_seq,
7449 "committed transfer must advance its BEGIN snapshot: {transfer:?}"
7450 );
7451 let from_index = usize::try_from(transfer.from_id)
7452 .expect("concurrent stress source account fits usize");
7453 let to_index = usize::try_from(transfer.to_id)
7454 .expect("concurrent stress target account fits usize");
7455 expected_balances[from_index] -= transfer.amount;
7456 expected_balances[to_index] += transfer.amount;
7457 }
7458 let balance_mismatches = durable_balances
7459 .iter()
7460 .filter_map(|&(account_id, durable_balance)| {
7461 let account_index = usize::try_from(account_id)
7462 .expect("durable concurrent stress account id fits usize");
7463 let expected_balance = expected_balances[account_index];
7464 (durable_balance != expected_balance).then_some((
7465 account_id,
7466 expected_balance,
7467 durable_balance,
7468 durable_balance - expected_balance,
7469 ))
7470 })
7471 .collect::<Vec<_>>();
7472 eprintln!(
7473 "concurrent stress durable balance mismatches (id, expected, durable, delta): \
7474 {balance_mismatches:?}"
7475 );
7476 for (account_id, _, _, _) in &balance_mismatches {
7477 let mut touching_transfers = results
7478 .iter()
7479 .flat_map(|outcome| &outcome.committed_transfers)
7480 .filter(|transfer| {
7481 transfer.from_id == *account_id || transfer.to_id == *account_id
7482 })
7483 .collect::<Vec<_>>();
7484 touching_transfers.sort_by_key(|transfer| transfer.commit_seq);
7485 eprintln!(
7486 "concurrent stress committed touches for account {account_id}: \
7487 {touching_transfers:?}"
7488 );
7489 }
7490 }
7491
7492 let mut total_commits = 0_u64;
7493 let mut total_retries = 0_u64;
7494 for (expected_worker_id, outcome) in results.iter().enumerate() {
7495 assert_eq!(
7496 outcome.worker_id, expected_worker_id,
7497 "worker ids must be unique and contiguous"
7498 );
7499 assert!(
7500 outcome.concurrent_mode_default,
7501 "worker {} lost the concurrent-writer default",
7502 outcome.worker_id
7503 );
7504 assert!(
7505 outcome.failure.is_none(),
7506 "worker {} failed: {:?}",
7507 outcome.worker_id,
7508 outcome.failure
7509 );
7510 assert_eq!(
7511 outcome.commits, OPS_PER_WRITER,
7512 "worker {} committed the wrong number of transfers",
7513 outcome.worker_id
7514 );
7515 assert!(
7516 outcome.attempts <= CONCURRENT_STRESS_MAX_ATTEMPTS_PER_WORKER,
7517 "worker {} exceeded its attempt budget",
7518 outcome.worker_id
7519 );
7520 assert!(
7521 outcome.max_attempts_for_commit <= CONCURRENT_STRESS_MAX_ATTEMPTS_PER_COMMIT,
7522 "worker {} exceeded its per-commit attempt budget",
7523 outcome.worker_id
7524 );
7525 assert!(
7526 outcome.elapsed <= CONCURRENT_STRESS_WORKER_TIMEOUT,
7527 "worker {} exceeded its elapsed-time budget",
7528 outcome.worker_id
7529 );
7530 total_commits += outcome.commits;
7531 total_retries += outcome.retries.total();
7532 }
7533 assert_eq!(
7534 total_commits,
7535 u64::try_from(NUM_WRITERS).expect("writer count fits u64") * OPS_PER_WRITER
7536 );
7537 eprintln!("concurrent stress total retries: {total_retries}");
7538
7539 let stock_diagnostic = stock_diagnostic.expect("stock SQLite diagnostic must complete");
7540 assert_eq!(stock_diagnostic.row_count, NUM_ACCOUNTS);
7541 assert_eq!(stock_diagnostic.balance_sum, EXPECTED_TOTAL);
7542 assert_eq!(stock_diagnostic.point_count, 1);
7543 assert_eq!(stock_diagnostic.scan_count, 1);
7544 assert_eq!(stock_diagnostic.integrity, ["ok"]);
7545
7546 let mut final_invariants = None;
7547 let mut final_integrity = None;
7548 asupersync::test_utils::run_test(|| async {
7549 let conn = Connection::open(&db_path_string)
7550 .await
7551 .expect("reopen concurrent-stress database for verification");
7552 assert!(
7553 conn.is_concurrent_mode_default(),
7554 "verification connection must preserve the concurrent-writer default"
7555 );
7556 let rows = conn
7557 .query(
7558 "SELECT COUNT(*), SUM(balance),\
7559 SUM(CASE WHEN balance < 0 THEN 1 ELSE 0 END),\
7560 MIN(length(payload)), MAX(length(payload)) FROM accounts;",
7561 )
7562 .await
7563 .expect("query final account invariants");
7564 final_invariants = Some(rows.iter().map(row_values).collect::<Vec<_>>());
7565 let integrity = conn
7566 .query("PRAGMA integrity_check;")
7567 .await
7568 .expect("run final integrity check");
7569 final_integrity = Some(integrity.iter().map(row_values).collect::<Vec<_>>());
7570 conn.close()
7571 .await
7572 .expect("close concurrent-stress verification connection");
7573 });
7574 assert_eq!(
7575 final_invariants,
7576 Some(vec![vec![
7577 SqliteValue::Integer(NUM_ACCOUNTS),
7578 SqliteValue::Integer(EXPECTED_TOTAL),
7579 SqliteValue::Integer(0),
7580 SqliteValue::Integer(512),
7581 SqliteValue::Integer(512),
7582 ]]),
7583 "final multi-page aggregate invariants must have exact INTEGER storage classes"
7584 );
7585 assert_eq!(
7586 final_integrity,
7587 Some(vec![vec![SqliteValue::Text("ok".into())]]),
7588 "integrity_check must return exactly one TEXT ok row"
7589 );
7590
7591 let receipt_token = std::env::var(CONCURRENT_STRESS_RECEIPT_ENV)
7592 .expect("supervised child must inherit its receipt token");
7593 println!("{CONCURRENT_STRESS_RECEIPT_PREFIX}{receipt_token}");
7594 }
7595
7596 #[test]
7597 fn concurrent_credit_conflict_rollback_preserves_staged_debit() {
7598 asupersync::test_utils::run_test(|| async {
7599 const NUM_ACCOUNTS: i64 = 100;
7600 const INITIAL_BALANCE: i64 = 1_000;
7601 const EXPECTED_TOTAL: i64 = NUM_ACCOUNTS * INITIAL_BALANCE;
7602
7603 let dir = tempfile::tempdir().expect("create rollback-atomicity temp dir");
7604 let db_path = dir.path().join("rollback-atomicity.db");
7605 let db_path = db_path.to_string_lossy().into_owned();
7606 let setup = Connection::open(&db_path)
7607 .await
7608 .expect("open rollback-atomicity setup connection");
7609 setup
7610 .execute(
7611 "CREATE TABLE accounts (
7612 id INTEGER PRIMARY KEY,
7613 balance INTEGER,
7614 payload TEXT NOT NULL
7615 );",
7616 )
7617 .await
7618 .expect("create rollback-atomicity accounts table");
7619 let payload = "x".repeat(512);
7620 for account_id in 0..NUM_ACCOUNTS {
7621 setup
7622 .execute_with_params(
7623 "INSERT INTO accounts VALUES (?1, ?2, ?3);",
7624 &[
7625 SqliteValue::Integer(account_id),
7626 SqliteValue::Integer(INITIAL_BALANCE),
7627 SqliteValue::Text(payload.clone().into()),
7628 ],
7629 )
7630 .await
7631 .expect("insert rollback-atomicity account");
7632 }
7633 setup.close().await.expect("close setup connection");
7634
7635 let debit = Connection::open(&db_path)
7636 .await
7637 .expect("open debit connection");
7638 let credit_blocker = Connection::open(&db_path)
7639 .await
7640 .expect("open credit-blocker connection");
7641 debit.execute("BEGIN;").await.expect("begin debit txn");
7642 credit_blocker
7643 .execute("BEGIN;")
7644 .await
7645 .expect("begin credit-blocker txn");
7646 assert_eq!(
7647 debit
7648 .execute("UPDATE accounts SET balance = balance - 9 WHERE id = 0;")
7649 .await
7650 .expect("stage debit on first leaf"),
7651 1
7652 );
7653 assert_eq!(
7654 credit_blocker
7655 .execute("UPDATE accounts SET balance = balance + 1 WHERE id = 99;")
7656 .await
7657 .expect("lock credit leaf from peer txn"),
7658 1
7659 );
7660 let credit_error = debit
7661 .execute("UPDATE accounts SET balance = balance + 9 WHERE id = 99;")
7662 .await
7663 .expect_err("credit page held by peer must reject the partial transfer");
7664 assert!(
7665 matches!(
7666 credit_error,
7667 FrankenError::Busy | FrankenError::BusySnapshot { .. }
7668 ),
7669 "credit conflict must be retryable, got {credit_error:?}"
7670 );
7671 debit
7672 .execute("ROLLBACK;")
7673 .await
7674 .expect("rollback staged debit after credit conflict");
7675 credit_blocker
7676 .execute("ROLLBACK;")
7677 .await
7678 .expect("rollback credit blocker");
7679 debit.close().await.expect("close debit connection");
7680 credit_blocker
7681 .close()
7682 .await
7683 .expect("close credit-blocker connection");
7684
7685 let verify = Connection::open(&db_path)
7686 .await
7687 .expect("open rollback-atomicity verification connection");
7688 let row = verify
7689 .query_row(
7690 "SELECT COUNT(*), SUM(balance),
7691 (SELECT balance FROM accounts WHERE id = 0),
7692 (SELECT balance FROM accounts WHERE id = 99)
7693 FROM accounts;",
7694 )
7695 .await
7696 .expect("query rollback-atomicity invariants");
7697 assert_eq!(
7698 row_values(&row),
7699 vec![
7700 SqliteValue::Integer(NUM_ACCOUNTS),
7701 SqliteValue::Integer(EXPECTED_TOTAL),
7702 SqliteValue::Integer(INITIAL_BALANCE),
7703 SqliteValue::Integer(INITIAL_BALANCE),
7704 ],
7705 "rolling back a transfer after its credit conflicts must discard its staged debit"
7706 );
7707 verify
7708 .close()
7709 .await
7710 .expect("close rollback-atomicity verification connection");
7711 });
7712 }
7713
7714 #[test]
7715 fn concurrent_stress_start_gate_aborts_all_waiters_on_failure() {
7716 let mut senders = Vec::new();
7717 let mut handles = Vec::new();
7718 for _ in 0..3 {
7719 let (sender, receiver) = mpsc::sync_channel(1);
7720 senders.push(sender);
7721 handles.push(std::thread::spawn(move || {
7722 receiver
7723 .recv_timeout(Duration::from_millis(100))
7724 .expect("abort decision must reach every startup waiter")
7725 }));
7726 }
7727 drop(ConcurrentStressStartGate::new(senders));
7728 for handle in handles {
7729 assert_eq!(
7730 handle.join().expect("startup waiter must be joined"),
7731 ConcurrentStressStartDecision::Abort
7732 );
7733 }
7734 }
7735
7736 #[test]
7737 fn concurrent_stress_retry_budget_is_exact_and_finite() {
7738 let mut attempts = 0_u64;
7739 let mut retries = ConcurrentStressRetryCounts::default();
7740 let exhaustion = loop {
7741 if let Some(error) =
7742 concurrent_stress_attempt_budget_error(attempts, attempts, Duration::ZERO)
7743 {
7744 break error;
7745 }
7746 attempts += 1;
7747 assert!(retries.record(&FrankenError::Busy));
7748 };
7749 assert_eq!(attempts, CONCURRENT_STRESS_MAX_ATTEMPTS_PER_COMMIT);
7750 assert_eq!(retries.busy, CONCURRENT_STRESS_MAX_ATTEMPTS_PER_COMMIT);
7751 assert!(exhaustion.contains("attempts for one commit"));
7752 assert!(retries.record(&FrankenError::BusyRecovery));
7753 assert_eq!(retries.busy_recovery, 1);
7754 assert!(
7755 concurrent_stress_attempt_budget_error(
7756 0,
7757 CONCURRENT_STRESS_MAX_ATTEMPTS_PER_WORKER,
7758 Duration::ZERO,
7759 )
7760 .expect("the total-attempt budget must be finite")
7761 .contains("total attempts")
7762 );
7763 assert!(
7764 concurrent_stress_attempt_budget_error(0, 0, CONCURRENT_STRESS_WORKER_TIMEOUT,)
7765 .expect("the elapsed-time budget must be finite")
7766 .contains("worker deadline")
7767 );
7768 }
7769
7770 #[test]
7772 fn concurrent_readers_consistency() {
7773 asupersync::test_utils::run_test(|| async {
7774 use std::thread;
7775
7776 let dir = tempfile::tempdir().expect("create temp dir");
7777 let db_path = dir.path().join("readers.db");
7778 let db_path_str = db_path.to_str().unwrap();
7779
7780 {
7782 let conn = Connection::open(db_path_str)
7783 .await
7784 .expect("open db for setup");
7785 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val INTEGER);")
7786 .await
7787 .expect("create table");
7788 for i in 0..100 {
7789 conn.execute(&format!("INSERT INTO t VALUES ({}, {});", i, i * 10))
7790 .await
7791 .expect("insert row");
7792 }
7793 conn.close()
7794 .await
7795 .expect("close concurrent-reader setup connection");
7796 }
7797
7798 const NUM_READERS: usize = 4;
7799 const READS_PER_THREAD: usize = 50;
7800
7801 let (startup_tx, startup_rx) = mpsc::channel::<ConcurrentStressStartup>();
7802 let mut start_senders = Vec::with_capacity(NUM_READERS);
7803 let mut handles = Vec::with_capacity(NUM_READERS);
7804 for thread_id in 0..NUM_READERS {
7805 let path = db_path_str.to_owned();
7806 let startup_tx = startup_tx.clone();
7807 let (start_tx, start_rx) = mpsc::sync_channel(1);
7808 start_senders.push(start_tx);
7809
7810 handles.push(thread::spawn(move || {
7811 let mut consistent = true;
7812 asupersync::test_utils::run_test(|| async {
7813 let mut open_attempts = 0_u64;
7814 let mut conn = loop {
7815 open_attempts += 1;
7816 match Connection::open(&path).await {
7817 Ok(conn) => break conn,
7818 Err(error @ (FrankenError::Busy | FrankenError::BusyRecovery))
7819 if open_attempts < CONCURRENT_READER_MAX_OPEN_ATTEMPTS =>
7820 {
7821 eprintln!(
7822 "reader {thread_id} open attempt {open_attempts} hit transient {error:?}"
7823 );
7824 concurrent_stress_backoff(
7825 open_attempts,
7826 u64::try_from(thread_id).expect("reader id fits u64"),
7827 );
7828 }
7829 Err(error) => {
7830 let error = format!(
7831 "open failed after {open_attempts} attempt(s): {error:?}"
7832 );
7833 let _ = startup_tx.send(ConcurrentStressStartup::Failed {
7834 worker_id: thread_id,
7835 error,
7836 });
7837 consistent = false;
7838 return;
7839 }
7840 }
7841 };
7842
7843 if startup_tx
7844 .send(ConcurrentStressStartup::Ready {
7845 worker_id: thread_id,
7846 })
7847 .is_err()
7848 {
7849 eprintln!("reader {thread_id} startup coordinator disconnected");
7850 conn.close_best_effort_in_place().await;
7851 consistent = false;
7852 return;
7853 }
7854 if !matches!(
7855 start_rx.recv_timeout(CONCURRENT_STRESS_STARTUP_TIMEOUT),
7856 Ok(ConcurrentStressStartDecision::Run)
7857 ) {
7858 eprintln!("reader {thread_id} did not receive the run decision");
7859 conn.close_best_effort_in_place().await;
7860 consistent = false;
7861 return;
7862 }
7863
7864 for _ in 0..READS_PER_THREAD {
7865 conn.execute("BEGIN;").await.expect("begin");
7867
7868 let rows = conn
7870 .query("SELECT SUM(val) FROM t;")
7871 .await
7872 .expect("sum query");
7873 let sum = if let SqliteValue::Integer(n) = &row_values(&rows[0])[0] {
7874 *n
7875 } else {
7876 consistent = false;
7877 break;
7878 };
7879
7880 let expected = 10 * (99 * 100 / 2);
7882 if sum != expected {
7883 eprintln!(
7884 "Thread {} saw inconsistent sum: {} (expected {})",
7885 thread_id, sum, expected
7886 );
7887 consistent = false;
7888 }
7889
7890 conn.execute("COMMIT;").await.expect("commit");
7891 }
7892 if let Err(error) = conn.close_without_checkpoint_in_place().await {
7893 eprintln!("reader {thread_id} close failed: {error:?}");
7894 conn.close_best_effort_in_place().await;
7895 consistent = false;
7896 }
7897 });
7898
7899 consistent
7900 }));
7901 }
7902 drop(startup_tx);
7903
7904 let mut ready = [false; NUM_READERS];
7905 let mut ready_count = 0_usize;
7906 let startup_deadline = Instant::now() + CONCURRENT_STRESS_STARTUP_TIMEOUT;
7907 let mut startup_failure = None;
7908 while ready_count < NUM_READERS {
7909 let remaining = startup_deadline.saturating_duration_since(Instant::now());
7910 if remaining.is_zero() {
7911 startup_failure = Some(format!(
7912 "startup timed out with {ready_count}/{NUM_READERS} readers ready"
7913 ));
7914 break;
7915 }
7916 match startup_rx.recv_timeout(remaining) {
7917 Ok(ConcurrentStressStartup::Ready { worker_id }) => {
7918 if worker_id >= NUM_READERS {
7919 startup_failure = Some(format!("out-of-range reader id {worker_id}"));
7920 break;
7921 }
7922 if std::mem::replace(&mut ready[worker_id], true) {
7923 startup_failure =
7924 Some(format!("duplicate startup receipt from reader {worker_id}"));
7925 break;
7926 }
7927 ready_count += 1;
7928 }
7929 Ok(ConcurrentStressStartup::Failed { worker_id, error }) => {
7930 startup_failure =
7931 Some(format!("reader {worker_id} startup failed: {error}"));
7932 break;
7933 }
7934 Err(mpsc::RecvTimeoutError::Timeout) => {
7935 startup_failure = Some(format!(
7936 "startup timed out with {ready_count}/{NUM_READERS} readers ready"
7937 ));
7938 break;
7939 }
7940 Err(mpsc::RecvTimeoutError::Disconnected) => {
7941 startup_failure = Some(format!(
7942 "startup channel closed with {ready_count}/{NUM_READERS} readers ready"
7943 ));
7944 break;
7945 }
7946 }
7947 }
7948
7949 let start_gate = ConcurrentStressStartGate::new(start_senders);
7950 let startup_result = if let Some(error) = startup_failure {
7951 drop(start_gate);
7952 Err(error)
7953 } else {
7954 start_gate.release()
7955 };
7956
7957 let results = handles
7958 .into_iter()
7959 .map(|handle| handle.join())
7960 .collect::<Vec<_>>();
7961
7962 assert!(
7963 startup_result.is_ok(),
7964 "concurrent-reader startup failed: {}",
7965 startup_result
7966 .as_ref()
7967 .expect_err("failed startup must carry a diagnostic")
7968 );
7969
7970 for (i, result) in results.into_iter().enumerate() {
7973 let consistent = result.expect("reader thread panicked");
7974 assert!(consistent, "Reader thread {} saw inconsistent data", i);
7975 }
7976 });
7977 }
7978
7979 #[test]
7982 fn conformance_017_type_affinity_edge_numeric_coercion() {
7983 asupersync::test_utils::run_test(|| async {
7984 let conn = Connection::open(":memory:").await.unwrap();
7986 conn.execute("CREATE TABLE q1(a NUMERIC, b TEXT, c INTEGER)")
7987 .await
7988 .unwrap();
7989 conn.execute("INSERT INTO q1 VALUES('3.0e+5', 123, '0042')")
7990 .await
7991 .unwrap();
7992 let rows = conn
7993 .query("SELECT typeof(a), a, typeof(b), b, typeof(c), c FROM q1")
7994 .await
7995 .unwrap();
7996 assert_eq!(rows.len(), 1);
7997 let vals = row_values(&rows[0]);
7998 assert_eq!(vals[0], SqliteValue::Text("integer".into()));
8000 assert_eq!(vals[1], SqliteValue::Integer(300_000));
8001 assert_eq!(vals[2], SqliteValue::Text("text".into()));
8003 assert_eq!(vals[3], SqliteValue::Text("123".into()));
8004 assert_eq!(vals[4], SqliteValue::Text("integer".into()));
8006 assert_eq!(vals[5], SqliteValue::Integer(42));
8007 });
8008 }
8009
8010 #[test]
8011 fn conformance_018_collation_nocase_ascii_only() {
8012 asupersync::test_utils::run_test(|| async {
8013 let conn = Connection::open(":memory:").await.unwrap();
8016 let rows = conn.query("SELECT 'a' = 'A' COLLATE NOCASE").await.unwrap();
8017 assert_eq!(rows.len(), 1);
8018 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
8019 });
8020 }
8021
8022 #[test]
8023 fn conformance_018_collation_nocase_unicode_sensitive() {
8024 asupersync::test_utils::run_test(|| async {
8025 let conn = Connection::open(":memory:").await.unwrap();
8026 let rows = conn.query("SELECT 'æ' = 'Æ' COLLATE NOCASE").await.unwrap();
8028 assert_eq!(rows.len(), 1);
8029 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(0));
8030 });
8031 }
8032
8033 #[test]
8034 fn conformance_019_null_unique_allows_multiple_nulls() {
8035 asupersync::test_utils::run_test(|| async {
8036 let conn = Connection::open(":memory:").await.unwrap();
8037 conn.execute("CREATE TABLE q3(a INTEGER UNIQUE, note TEXT)")
8038 .await
8039 .unwrap();
8040 conn.execute("INSERT INTO q3(a, note) VALUES(NULL, 'first-null')")
8041 .await
8042 .unwrap();
8043 conn.execute("INSERT INTO q3(a, note) VALUES(NULL, 'second-null')")
8045 .await
8046 .unwrap();
8047 conn.execute("INSERT INTO q3(a, note) VALUES(7, 'first-seven')")
8048 .await
8049 .unwrap();
8050 let rows = conn.query("SELECT COUNT(*) FROM q3").await.unwrap();
8051 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(3));
8052 });
8053 }
8054
8055 #[test]
8056 fn conformance_019_null_unique_rejects_duplicate_non_null() {
8057 asupersync::test_utils::run_test(|| async {
8058 let conn = Connection::open(":memory:").await.unwrap();
8059 conn.execute("CREATE TABLE q3b(a INTEGER UNIQUE, note TEXT)")
8060 .await
8061 .unwrap();
8062 conn.execute("INSERT INTO q3b(a, note) VALUES(7, 'first-seven')")
8063 .await
8064 .unwrap();
8065 let result = conn
8067 .execute("INSERT INTO q3b(a, note) VALUES(7, 'dup')")
8068 .await;
8069 assert!(
8070 result.is_err(),
8071 "Duplicate non-NULL unique value should fail"
8072 );
8073 });
8074 }
8075
8076 #[test]
8077 fn conformance_020_integer_overflow_promotes_to_real() {
8078 asupersync::test_utils::run_test(|| async {
8079 let conn = Connection::open(":memory:").await.unwrap();
8080 let rows = conn
8082 .query("SELECT typeof(9223372036854775807 + 1)")
8083 .await
8084 .unwrap();
8085 assert_eq!(rows.len(), 1);
8086 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("real".into()));
8087 });
8088 }
8089
8090 #[test]
8091 fn conformance_020_integer_underflow_promotes_to_real() {
8092 asupersync::test_utils::run_test(|| async {
8093 let conn = Connection::open(":memory:").await.unwrap();
8094 let rows = conn
8096 .query("SELECT typeof(-9223372036854775808 - 1)")
8097 .await
8098 .unwrap();
8099 assert_eq!(rows.len(), 1);
8100 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("real".into()));
8101 });
8102 }
8103
8104 #[test]
8105 fn conformance_021_savepoint_rollback_preserves_outer() {
8106 asupersync::test_utils::run_test(|| async {
8107 let conn = Connection::open(":memory:").await.unwrap();
8108 conn.execute("CREATE TABLE q5(id INTEGER PRIMARY KEY, note TEXT)")
8109 .await
8110 .unwrap();
8111 conn.execute("BEGIN").await.unwrap();
8112 conn.execute("INSERT INTO q5 VALUES(1, 'outer')")
8113 .await
8114 .unwrap();
8115 conn.execute("SAVEPOINT s1").await.unwrap();
8116 conn.execute("INSERT INTO q5 VALUES(2, 'inner')")
8117 .await
8118 .unwrap();
8119 conn.execute("ROLLBACK TO s1").await.unwrap();
8120 conn.execute("RELEASE s1").await.unwrap();
8121 conn.execute("COMMIT").await.unwrap();
8122 let rows = conn
8123 .query("SELECT id, note FROM q5 ORDER BY id")
8124 .await
8125 .unwrap();
8126 assert_eq!(rows.len(), 1, "ROLLBACK TO s1 should undo inner insert");
8127 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
8128 assert_eq!(row_values(&rows[0])[1], SqliteValue::Text("outer".into()));
8129 });
8130 }
8131
8132 #[test]
8133 fn conformance_021_nested_begin_errors() {
8134 asupersync::test_utils::run_test(|| async {
8135 let conn = Connection::open(":memory:").await.unwrap();
8136 conn.execute("BEGIN").await.unwrap();
8137 let result = conn.execute("BEGIN").await;
8139 assert!(result.is_err(), "Nested BEGIN should produce an error");
8140 conn.execute("ROLLBACK").await.unwrap();
8141 });
8142 }
8143
8144 #[test]
8147 fn parity_replace_into_inserts_new_row() {
8148 asupersync::test_utils::run_test(|| async {
8149 let conn = Connection::open(":memory:").await.unwrap();
8150 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
8151 .await
8152 .unwrap();
8153 conn.execute("REPLACE INTO t VALUES (1, 'first');")
8154 .await
8155 .unwrap();
8156 let rows = conn.query("SELECT id, val FROM t;").await.unwrap();
8157 assert_eq!(rows.len(), 1);
8158 assert_eq!(row_values(&rows[0])[1], SqliteValue::Text("first".into()));
8159 });
8160 }
8161
8162 #[test]
8163 fn parity_replace_into_overwrites_existing() {
8164 asupersync::test_utils::run_test(|| async {
8165 let conn = Connection::open(":memory:").await.unwrap();
8166 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
8167 .await
8168 .unwrap();
8169 conn.execute("INSERT INTO t VALUES (1, 'old');")
8170 .await
8171 .unwrap();
8172 conn.execute("REPLACE INTO t VALUES (1, 'new');")
8173 .await
8174 .unwrap();
8175 let rows = conn.query("SELECT val FROM t WHERE id = 1;").await.unwrap();
8176 assert_eq!(rows.len(), 1);
8177 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("new".into()));
8178 });
8179 }
8180
8181 #[test]
8184 fn parity_insert_or_ignore_skips_conflict() {
8185 asupersync::test_utils::run_test(|| async {
8186 let conn = Connection::open(":memory:").await.unwrap();
8187 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
8188 .await
8189 .unwrap();
8190 conn.execute("INSERT INTO t VALUES (1, 'first');")
8191 .await
8192 .unwrap();
8193 conn.execute("INSERT OR IGNORE INTO t VALUES (1, 'dup');")
8195 .await
8196 .unwrap();
8197 let rows = conn.query("SELECT val FROM t WHERE id = 1;").await.unwrap();
8198 assert_eq!(rows.len(), 1);
8199 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("first".into()));
8200 });
8201 }
8202
8203 #[test]
8206 fn parity_multi_column_order_by() {
8207 asupersync::test_utils::run_test(|| async {
8208 let conn = Connection::open(":memory:").await.unwrap();
8209 conn.execute("CREATE TABLE t (a INTEGER, b INTEGER, c TEXT);")
8210 .await
8211 .unwrap();
8212 conn.execute("INSERT INTO t VALUES (2, 1, 'x');")
8213 .await
8214 .unwrap();
8215 conn.execute("INSERT INTO t VALUES (1, 2, 'y');")
8216 .await
8217 .unwrap();
8218 conn.execute("INSERT INTO t VALUES (1, 1, 'z');")
8219 .await
8220 .unwrap();
8221 let rows = conn.query("SELECT c FROM t ORDER BY a, b;").await.unwrap();
8222 assert_eq!(rows.len(), 3);
8223 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("z".into()));
8224 assert_eq!(row_values(&rows[1])[0], SqliteValue::Text("y".into()));
8225 assert_eq!(row_values(&rows[2])[0], SqliteValue::Text("x".into()));
8226 });
8227 }
8228
8229 #[test]
8232 fn parity_limit_with_offset() {
8233 asupersync::test_utils::run_test(|| async {
8234 let conn = Connection::open(":memory:").await.unwrap();
8235 conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
8236 .await
8237 .unwrap();
8238 for i in 1..=5 {
8239 conn.execute(&format!("INSERT INTO t VALUES ({i}, 'r{i}');"))
8240 .await
8241 .unwrap();
8242 }
8243 let rows = conn
8244 .query("SELECT val FROM t ORDER BY id LIMIT 2 OFFSET 2;")
8245 .await
8246 .unwrap();
8247 assert_eq!(rows.len(), 2);
8248 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("r3".into()));
8249 assert_eq!(row_values(&rows[1])[0], SqliteValue::Text("r4".into()));
8250 });
8251 }
8252
8253 #[test]
8256 fn parity_subquery_in_where() {
8257 asupersync::test_utils::run_test(|| async {
8258 let conn = Connection::open(":memory:").await.unwrap();
8259 conn.execute("CREATE TABLE t1 (id INTEGER, val TEXT);")
8260 .await
8261 .unwrap();
8262 conn.execute("CREATE TABLE t2 (ref_id INTEGER);")
8263 .await
8264 .unwrap();
8265 conn.execute("INSERT INTO t1 VALUES (1, 'a');")
8266 .await
8267 .unwrap();
8268 conn.execute("INSERT INTO t1 VALUES (2, 'b');")
8269 .await
8270 .unwrap();
8271 conn.execute("INSERT INTO t1 VALUES (3, 'c');")
8272 .await
8273 .unwrap();
8274 conn.execute("INSERT INTO t2 VALUES (1);").await.unwrap();
8275 conn.execute("INSERT INTO t2 VALUES (3);").await.unwrap();
8276 let rows = conn
8277 .query("SELECT val FROM t1 WHERE id IN (SELECT ref_id FROM t2) ORDER BY id;")
8278 .await
8279 .unwrap();
8280 assert_eq!(rows.len(), 2);
8281 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("a".into()));
8282 assert_eq!(row_values(&rows[1])[0], SqliteValue::Text("c".into()));
8283 });
8284 }
8285
8286 #[test]
8289 fn parity_cast_integer_to_text() {
8290 asupersync::test_utils::run_test(|| async {
8291 let conn = Connection::open(":memory:").await.unwrap();
8292 let row = conn.query_row("SELECT CAST(42 AS TEXT);").await.unwrap();
8293 assert_eq!(row_values(&row)[0], SqliteValue::Text("42".into()));
8294 });
8295 }
8296
8297 #[test]
8298 fn parity_cast_text_to_integer() {
8299 asupersync::test_utils::run_test(|| async {
8300 let conn = Connection::open(":memory:").await.unwrap();
8301 let row = conn
8302 .query_row("SELECT CAST('123' AS INTEGER);")
8303 .await
8304 .unwrap();
8305 assert_eq!(row_values(&row)[0], SqliteValue::Integer(123));
8306 });
8307 }
8308
8309 #[test]
8312 fn parity_exists_subquery() {
8313 asupersync::test_utils::run_test(|| async {
8314 let conn = Connection::open(":memory:").await.unwrap();
8315 conn.execute("CREATE TABLE t (id INTEGER);").await.unwrap();
8316 conn.execute("INSERT INTO t VALUES (1);").await.unwrap();
8317 let row = conn
8318 .query_row("SELECT EXISTS (SELECT 1 FROM t WHERE id = 1);")
8319 .await
8320 .unwrap();
8321 assert_eq!(row_values(&row)[0], SqliteValue::Integer(1));
8322 let row = conn
8323 .query_row("SELECT EXISTS (SELECT 1 FROM t WHERE id = 999);")
8324 .await
8325 .unwrap();
8326 assert_eq!(row_values(&row)[0], SqliteValue::Integer(0));
8327 });
8328 }
8329
8330 #[test]
8333 fn parity_count_distinct() {
8334 asupersync::test_utils::run_test(|| async {
8335 let conn = Connection::open(":memory:").await.unwrap();
8336 conn.execute("CREATE TABLE t (val INTEGER);").await.unwrap();
8337 conn.execute("INSERT INTO t VALUES (1);").await.unwrap();
8338 conn.execute("INSERT INTO t VALUES (2);").await.unwrap();
8339 conn.execute("INSERT INTO t VALUES (1);").await.unwrap();
8340 conn.execute("INSERT INTO t VALUES (3);").await.unwrap();
8341 conn.execute("INSERT INTO t VALUES (2);").await.unwrap();
8342 let row = conn
8343 .query_row("SELECT COUNT(DISTINCT val) FROM t;")
8344 .await
8345 .unwrap();
8346 assert_eq!(row_values(&row)[0], SqliteValue::Integer(3));
8347 });
8348 }
8349
8350 #[test]
8353 fn parity_group_concat_basic() {
8354 asupersync::test_utils::run_test(|| async {
8355 let conn = Connection::open(":memory:").await.unwrap();
8356 conn.execute("CREATE TABLE t (grp TEXT, val TEXT);")
8357 .await
8358 .unwrap();
8359 conn.execute("INSERT INTO t VALUES ('a', 'x');")
8360 .await
8361 .unwrap();
8362 conn.execute("INSERT INTO t VALUES ('a', 'y');")
8363 .await
8364 .unwrap();
8365 conn.execute("INSERT INTO t VALUES ('b', 'z');")
8366 .await
8367 .unwrap();
8368 let rows = conn
8369 .query("SELECT grp, GROUP_CONCAT(val, ',') FROM t GROUP BY grp ORDER BY grp;")
8370 .await
8371 .unwrap();
8372 assert_eq!(rows.len(), 2);
8373 let a_val = &row_values(&rows[0])[1];
8375 match a_val {
8376 SqliteValue::Text(s) => {
8377 assert!(&**s == "x,y" || &**s == "y,x", "group_concat for 'a' = {s}");
8378 }
8379 other => panic!("expected Text, got {other:?}"),
8380 }
8381 });
8382 }
8383
8384 #[test]
8387 fn parity_count_distinct_with_nulls() {
8388 asupersync::test_utils::run_test(|| async {
8389 let conn = Connection::open(":memory:").await.unwrap();
8390 conn.execute("CREATE TABLE d2(x INTEGER);").await.unwrap();
8391 conn.execute("INSERT INTO d2 VALUES(1);").await.unwrap();
8392 conn.execute("INSERT INTO d2 VALUES(NULL);").await.unwrap();
8393 conn.execute("INSERT INTO d2 VALUES(2);").await.unwrap();
8394 conn.execute("INSERT INTO d2 VALUES(NULL);").await.unwrap();
8395 let row = conn
8396 .query_row("SELECT COUNT(DISTINCT x) FROM d2;")
8397 .await
8398 .unwrap();
8399 assert_eq!(row_values(&row)[0], SqliteValue::Integer(2));
8401 });
8402 }
8403
8404 #[test]
8405 fn parity_sum_distinct() {
8406 asupersync::test_utils::run_test(|| async {
8407 let conn = Connection::open(":memory:").await.unwrap();
8408 conn.execute("CREATE TABLE d3(x INTEGER);").await.unwrap();
8409 conn.execute("INSERT INTO d3 VALUES(10);").await.unwrap();
8410 conn.execute("INSERT INTO d3 VALUES(20);").await.unwrap();
8411 conn.execute("INSERT INTO d3 VALUES(10);").await.unwrap();
8412 conn.execute("INSERT INTO d3 VALUES(30);").await.unwrap();
8413 conn.execute("INSERT INTO d3 VALUES(20);").await.unwrap();
8414 let row = conn
8415 .query_row("SELECT SUM(DISTINCT x) FROM d3;")
8416 .await
8417 .unwrap();
8418 assert_eq!(row_values(&row)[0], SqliteValue::Integer(60));
8420 });
8421 }
8422
8423 #[test]
8424 fn parity_count_vs_count_distinct() {
8425 asupersync::test_utils::run_test(|| async {
8426 let conn = Connection::open(":memory:").await.unwrap();
8427 conn.execute("CREATE TABLE d4(x INTEGER);").await.unwrap();
8428 conn.execute("INSERT INTO d4 VALUES(1);").await.unwrap();
8429 conn.execute("INSERT INTO d4 VALUES(1);").await.unwrap();
8430 conn.execute("INSERT INTO d4 VALUES(2);").await.unwrap();
8431 conn.execute("INSERT INTO d4 VALUES(2);").await.unwrap();
8432 conn.execute("INSERT INTO d4 VALUES(2);").await.unwrap();
8433 let r1 = conn.query_row("SELECT COUNT(x) FROM d4;").await.unwrap();
8434 assert_eq!(row_values(&r1)[0], SqliteValue::Integer(5));
8435 let r2 = conn
8436 .query_row("SELECT COUNT(DISTINCT x) FROM d4;")
8437 .await
8438 .unwrap();
8439 assert_eq!(row_values(&r2)[0], SqliteValue::Integer(2));
8440 });
8441 }
8442
8443 #[test]
8444 fn parity_count_distinct_group_by() {
8445 asupersync::test_utils::run_test(|| async {
8446 let conn = Connection::open(":memory:").await.unwrap();
8447 conn.execute("CREATE TABLE d5(grp TEXT, val INTEGER);")
8448 .await
8449 .unwrap();
8450 conn.execute("INSERT INTO d5 VALUES('a', 1);")
8451 .await
8452 .unwrap();
8453 conn.execute("INSERT INTO d5 VALUES('a', 2);")
8454 .await
8455 .unwrap();
8456 conn.execute("INSERT INTO d5 VALUES('a', 1);")
8457 .await
8458 .unwrap();
8459 conn.execute("INSERT INTO d5 VALUES('b', 10);")
8460 .await
8461 .unwrap();
8462 conn.execute("INSERT INTO d5 VALUES('b', 10);")
8463 .await
8464 .unwrap();
8465 conn.execute("INSERT INTO d5 VALUES('b', 20);")
8466 .await
8467 .unwrap();
8468 let rows = conn
8469 .query("SELECT grp, COUNT(DISTINCT val) FROM d5 GROUP BY grp ORDER BY grp;")
8470 .await
8471 .unwrap();
8472 assert_eq!(rows.len(), 2);
8473 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("a".into()));
8475 assert_eq!(row_values(&rows[0])[1], SqliteValue::Integer(2));
8476 assert_eq!(row_values(&rows[1])[0], SqliteValue::Text("b".into()));
8478 assert_eq!(row_values(&rows[1])[1], SqliteValue::Integer(2));
8479 });
8480 }
8481
8482 #[test]
8483 fn parity_count_distinct_all_same() {
8484 asupersync::test_utils::run_test(|| async {
8485 let conn = Connection::open(":memory:").await.unwrap();
8486 conn.execute("CREATE TABLE d6(x INTEGER);").await.unwrap();
8487 conn.execute("INSERT INTO d6 VALUES(42);").await.unwrap();
8488 conn.execute("INSERT INTO d6 VALUES(42);").await.unwrap();
8489 conn.execute("INSERT INTO d6 VALUES(42);").await.unwrap();
8490 let row = conn
8491 .query_row("SELECT COUNT(DISTINCT x) FROM d6;")
8492 .await
8493 .unwrap();
8494 assert_eq!(row_values(&row)[0], SqliteValue::Integer(1));
8495 });
8496 }
8497
8498 #[test]
8499 fn parity_count_distinct_empty_table() {
8500 asupersync::test_utils::run_test(|| async {
8501 let conn = Connection::open(":memory:").await.unwrap();
8502 conn.execute("CREATE TABLE d7(x INTEGER);").await.unwrap();
8503 let row = conn
8504 .query_row("SELECT COUNT(DISTINCT x) FROM d7;")
8505 .await
8506 .unwrap();
8507 assert_eq!(row_values(&row)[0], SqliteValue::Integer(0));
8508 });
8509 }
8510
8511 #[test]
8514 fn parity_scalar_subquery_aggregate() {
8515 asupersync::test_utils::run_test(|| async {
8516 let conn = Connection::open(":memory:").await.unwrap();
8517 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
8518 conn.execute("INSERT INTO t VALUES(10);").await.unwrap();
8519 conn.execute("INSERT INTO t VALUES(20);").await.unwrap();
8520 conn.execute("INSERT INTO t VALUES(30);").await.unwrap();
8521 let row = conn
8522 .query_row("SELECT (SELECT COUNT(*) FROM t);")
8523 .await
8524 .unwrap();
8525 assert_eq!(row_values(&row)[0], SqliteValue::Integer(3));
8526 });
8527 }
8528
8529 #[test]
8530 fn parity_scalar_subquery_max() {
8531 asupersync::test_utils::run_test(|| async {
8532 let conn = Connection::open(":memory:").await.unwrap();
8533 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
8534 conn.execute("INSERT INTO t VALUES(5);").await.unwrap();
8535 conn.execute("INSERT INTO t VALUES(15);").await.unwrap();
8536 conn.execute("INSERT INTO t VALUES(10);").await.unwrap();
8537 let row = conn
8538 .query_row("SELECT (SELECT MAX(x) FROM t);")
8539 .await
8540 .unwrap();
8541 assert_eq!(row_values(&row)[0], SqliteValue::Integer(15));
8542 });
8543 }
8544
8545 #[test]
8546 fn parity_scalar_subquery_no_from() {
8547 asupersync::test_utils::run_test(|| async {
8548 let conn = Connection::open(":memory:").await.unwrap();
8549 let row = conn.query_row("SELECT (SELECT 42);").await.unwrap();
8550 assert_eq!(row_values(&row)[0], SqliteValue::Integer(42));
8551 });
8552 }
8553
8554 #[test]
8555 fn parity_scalar_subquery_first_row() {
8556 asupersync::test_utils::run_test(|| async {
8557 let conn = Connection::open(":memory:").await.unwrap();
8558 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
8559 conn.execute("INSERT INTO t VALUES(100);").await.unwrap();
8560 conn.execute("INSERT INTO t VALUES(200);").await.unwrap();
8561 let row = conn.query_row("SELECT (SELECT x FROM t);").await.unwrap();
8562 assert_eq!(row_values(&row)[0], SqliteValue::Integer(100));
8564 });
8565 }
8566
8567 #[test]
8568 fn parity_scalar_subquery_empty_table_is_null() {
8569 asupersync::test_utils::run_test(|| async {
8570 let conn = Connection::open(":memory:").await.unwrap();
8571 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
8572 let row = conn.query_row("SELECT (SELECT x FROM t);").await.unwrap();
8573 assert_eq!(row_values(&row)[0], SqliteValue::Null);
8574 });
8575 }
8576
8577 #[test]
8580 fn parity_exists_true() {
8581 asupersync::test_utils::run_test(|| async {
8582 let conn = Connection::open(":memory:").await.unwrap();
8583 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
8584 conn.execute("INSERT INTO t VALUES(1);").await.unwrap();
8585 let row = conn
8586 .query_row("SELECT EXISTS (SELECT 1 FROM t);")
8587 .await
8588 .unwrap();
8589 assert_eq!(row_values(&row)[0], SqliteValue::Integer(1));
8590 });
8591 }
8592
8593 #[test]
8594 fn parity_exists_false_empty() {
8595 asupersync::test_utils::run_test(|| async {
8596 let conn = Connection::open(":memory:").await.unwrap();
8597 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
8598 let row = conn
8599 .query_row("SELECT EXISTS (SELECT 1 FROM t);")
8600 .await
8601 .unwrap();
8602 assert_eq!(row_values(&row)[0], SqliteValue::Integer(0));
8603 });
8604 }
8605
8606 #[test]
8607 fn parity_not_exists_true_empty() {
8608 asupersync::test_utils::run_test(|| async {
8609 let conn = Connection::open(":memory:").await.unwrap();
8610 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
8611 let row = conn
8612 .query_row("SELECT NOT EXISTS (SELECT 1 FROM t);")
8613 .await
8614 .unwrap();
8615 assert_eq!(row_values(&row)[0], SqliteValue::Integer(1));
8616 });
8617 }
8618
8619 #[test]
8620 fn parity_exists_no_from() {
8621 asupersync::test_utils::run_test(|| async {
8622 let conn = Connection::open(":memory:").await.unwrap();
8624 let row = conn.query_row("SELECT EXISTS (SELECT 1);").await.unwrap();
8625 assert_eq!(row_values(&row)[0], SqliteValue::Integer(1));
8626 });
8627 }
8628
8629 #[test]
8630 fn parity_exists_with_where() {
8631 asupersync::test_utils::run_test(|| async {
8632 let conn = Connection::open(":memory:").await.unwrap();
8633 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
8634 conn.execute("INSERT INTO t VALUES(10);").await.unwrap();
8635 conn.execute("INSERT INTO t VALUES(20);").await.unwrap();
8636
8637 let row = conn
8639 .query_row("SELECT EXISTS (SELECT 1 FROM t WHERE x = 10);")
8640 .await
8641 .unwrap();
8642 assert_eq!(row_values(&row)[0], SqliteValue::Integer(1));
8643
8644 let row = conn
8646 .query_row("SELECT EXISTS (SELECT 1 FROM t WHERE x = 99);")
8647 .await
8648 .unwrap();
8649 assert_eq!(row_values(&row)[0], SqliteValue::Integer(0));
8650 });
8651 }
8652
8653 #[test]
8656 fn parity_count_filter() {
8657 asupersync::test_utils::run_test(|| async {
8658 let conn = Connection::open(":memory:").await.unwrap();
8659 conn.execute("CREATE TABLE f1(x INTEGER);").await.unwrap();
8660 conn.execute("INSERT INTO f1 VALUES(1);").await.unwrap();
8661 conn.execute("INSERT INTO f1 VALUES(2);").await.unwrap();
8662 conn.execute("INSERT INTO f1 VALUES(3);").await.unwrap();
8663 conn.execute("INSERT INTO f1 VALUES(4);").await.unwrap();
8664 conn.execute("INSERT INTO f1 VALUES(5);").await.unwrap();
8665 let row = conn
8667 .query_row("SELECT COUNT(*) FILTER (WHERE x > 3) FROM f1;")
8668 .await
8669 .unwrap();
8670 assert_eq!(row_values(&row)[0], SqliteValue::Integer(2));
8671 });
8672 }
8673
8674 #[test]
8675 fn parity_sum_filter() {
8676 asupersync::test_utils::run_test(|| async {
8677 let conn = Connection::open(":memory:").await.unwrap();
8678 conn.execute("CREATE TABLE f2(x INTEGER);").await.unwrap();
8679 conn.execute("INSERT INTO f2 VALUES(10);").await.unwrap();
8680 conn.execute("INSERT INTO f2 VALUES(20);").await.unwrap();
8681 conn.execute("INSERT INTO f2 VALUES(30);").await.unwrap();
8682 let row = conn
8684 .query_row("SELECT SUM(x) FILTER (WHERE x >= 20) FROM f2;")
8685 .await
8686 .unwrap();
8687 assert_eq!(row_values(&row)[0], SqliteValue::Integer(50));
8688 });
8689 }
8690
8691 #[test]
8692 fn parity_count_filter_none_match() {
8693 asupersync::test_utils::run_test(|| async {
8694 let conn = Connection::open(":memory:").await.unwrap();
8695 conn.execute("CREATE TABLE f3(x INTEGER);").await.unwrap();
8696 conn.execute("INSERT INTO f3 VALUES(1);").await.unwrap();
8697 conn.execute("INSERT INTO f3 VALUES(2);").await.unwrap();
8698 let row = conn
8700 .query_row("SELECT COUNT(*) FILTER (WHERE x > 100) FROM f3;")
8701 .await
8702 .unwrap();
8703 assert_eq!(row_values(&row)[0], SqliteValue::Integer(0));
8704 });
8705 }
8706
8707 #[test]
8708 fn parity_filter_no_group_by_same_table() {
8709 asupersync::test_utils::run_test(|| async {
8710 let conn = Connection::open(":memory:").await.unwrap();
8712 conn.execute("CREATE TABLE f4b(city TEXT, age INTEGER);")
8713 .await
8714 .unwrap();
8715 conn.execute("INSERT INTO f4b VALUES('A', 10);")
8716 .await
8717 .unwrap();
8718 conn.execute("INSERT INTO f4b VALUES('A', 30);")
8719 .await
8720 .unwrap();
8721 conn.execute("INSERT INTO f4b VALUES('B', 20);")
8722 .await
8723 .unwrap();
8724 conn.execute("INSERT INTO f4b VALUES('B', 40);")
8725 .await
8726 .unwrap();
8727 let row = conn
8729 .query_row("SELECT COUNT(*) FILTER (WHERE age > 25) FROM f4b;")
8730 .await
8731 .unwrap();
8732 assert_eq!(row_values(&row)[0], SqliteValue::Integer(2));
8733 });
8734 }
8735
8736 #[test]
8737 fn parity_filter_group_by_always_false() {
8738 asupersync::test_utils::run_test(|| async {
8739 let conn = Connection::open(":memory:").await.unwrap();
8741 conn.execute("CREATE TABLE f4z(city TEXT, val INTEGER);")
8742 .await
8743 .unwrap();
8744 conn.execute("INSERT INTO f4z VALUES('A', 1);")
8745 .await
8746 .unwrap();
8747 conn.execute("INSERT INTO f4z VALUES('A', 2);")
8748 .await
8749 .unwrap();
8750 conn.execute("INSERT INTO f4z VALUES('B', 3);")
8751 .await
8752 .unwrap();
8753 let rows = conn
8754 .query("SELECT city, COUNT(*) FILTER (WHERE 0) FROM f4z GROUP BY city;")
8755 .await
8756 .unwrap();
8757 let mut results: Vec<(String, i64)> = rows
8758 .iter()
8759 .map(|r| {
8760 let vals = row_values(r);
8761 let city = match &vals[0] {
8762 SqliteValue::Text(s) => s.to_string(),
8763 _ => panic!("expected text"),
8764 };
8765 let cnt = match vals[1] {
8766 SqliteValue::Integer(n) => n,
8767 _ => panic!("expected integer, got {:?}", vals[1]),
8768 };
8769 (city, cnt)
8770 })
8771 .collect();
8772 results.sort_by(|a, b| a.0.cmp(&b.0));
8773 assert_eq!(results, vec![("A".into(), 0), ("B".into(), 0)]);
8774 });
8775 }
8776
8777 #[test]
8778 fn parity_filter_with_group_by() {
8779 asupersync::test_utils::run_test(|| async {
8780 let conn = Connection::open(":memory:").await.unwrap();
8781 conn.execute("CREATE TABLE f4(city TEXT, age INTEGER);")
8782 .await
8783 .unwrap();
8784 conn.execute("INSERT INTO f4 VALUES('A', 10);")
8785 .await
8786 .unwrap();
8787 conn.execute("INSERT INTO f4 VALUES('A', 30);")
8788 .await
8789 .unwrap();
8790 conn.execute("INSERT INTO f4 VALUES('B', 20);")
8791 .await
8792 .unwrap();
8793 conn.execute("INSERT INTO f4 VALUES('B', 40);")
8794 .await
8795 .unwrap();
8796 let rows = conn
8799 .query("SELECT city, COUNT(*) FILTER (WHERE age > 25) FROM f4 GROUP BY city;")
8800 .await
8801 .unwrap();
8802 let mut results: Vec<(String, i64)> = rows
8803 .iter()
8804 .map(|r| {
8805 let vals = row_values(r);
8806 let city = match &vals[0] {
8807 SqliteValue::Text(s) => s.to_string(),
8808 _ => panic!("expected text"),
8809 };
8810 let cnt = match vals[1] {
8811 SqliteValue::Integer(n) => n,
8812 _ => panic!("expected integer"),
8813 };
8814 (city, cnt)
8815 })
8816 .collect();
8817 results.sort_by(|a, b| a.0.cmp(&b.0));
8818 assert_eq!(results, vec![("A".into(), 1), ("B".into(), 1)]);
8819 });
8820 }
8821
8822 #[test]
8823 fn parity_filter_multiple_aggregates() {
8824 asupersync::test_utils::run_test(|| async {
8825 let conn = Connection::open(":memory:").await.unwrap();
8826 conn.execute("CREATE TABLE f5(x INTEGER);").await.unwrap();
8827 conn.execute("INSERT INTO f5 VALUES(1);").await.unwrap();
8828 conn.execute("INSERT INTO f5 VALUES(2);").await.unwrap();
8829 conn.execute("INSERT INTO f5 VALUES(3);").await.unwrap();
8830 conn.execute("INSERT INTO f5 VALUES(4);").await.unwrap();
8831 let row = conn
8833 .query_row(
8834 "SELECT COUNT(*) FILTER (WHERE x <= 2), COUNT(*) FILTER (WHERE x >= 3) FROM f5;",
8835 )
8836 .await
8837 .unwrap();
8838 let vals = row_values(&row);
8839 assert_eq!(vals[0], SqliteValue::Integer(2)); assert_eq!(vals[1], SqliteValue::Integer(2)); });
8842 }
8843
8844 #[test]
8845 fn parity_count_filter_vs_no_filter() {
8846 asupersync::test_utils::run_test(|| async {
8847 let conn = Connection::open(":memory:").await.unwrap();
8848 conn.execute("CREATE TABLE f6(x INTEGER);").await.unwrap();
8849 conn.execute("INSERT INTO f6 VALUES(1);").await.unwrap();
8850 conn.execute("INSERT INTO f6 VALUES(2);").await.unwrap();
8851 conn.execute("INSERT INTO f6 VALUES(3);").await.unwrap();
8852 let row = conn
8854 .query_row("SELECT COUNT(*), COUNT(*) FILTER (WHERE x > 1) FROM f6;")
8855 .await
8856 .unwrap();
8857 let vals = row_values(&row);
8858 assert_eq!(vals[0], SqliteValue::Integer(3)); assert_eq!(vals[1], SqliteValue::Integer(2)); });
8861 }
8862
8863 #[test]
8866 fn parity_case_simple() {
8867 asupersync::test_utils::run_test(|| async {
8868 let conn = Connection::open(":memory:").await.unwrap();
8869 let row = conn
8870 .query_row("SELECT CASE 2 WHEN 1 THEN 'one' WHEN 2 THEN 'two' ELSE 'other' END;")
8871 .await
8872 .unwrap();
8873 assert_eq!(row_values(&row)[0], SqliteValue::Text("two".into()));
8874 });
8875 }
8876
8877 #[test]
8878 fn parity_case_searched() {
8879 asupersync::test_utils::run_test(|| async {
8880 let conn = Connection::open(":memory:").await.unwrap();
8881 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
8882 conn.execute("INSERT INTO t VALUES(15);").await.unwrap();
8883 let row = conn
8884 .query_row(
8885 "SELECT CASE WHEN x < 10 THEN 'low' WHEN x < 20 THEN 'mid' ELSE 'high' END FROM t;",
8886 )
8887 .await
8888 .unwrap();
8889 assert_eq!(row_values(&row)[0], SqliteValue::Text("mid".into()));
8890 });
8891 }
8892
8893 #[test]
8894 fn parity_case_no_else_returns_null() {
8895 asupersync::test_utils::run_test(|| async {
8896 let conn = Connection::open(":memory:").await.unwrap();
8897 let row = conn
8898 .query_row("SELECT CASE 5 WHEN 1 THEN 'one' WHEN 2 THEN 'two' END;")
8899 .await
8900 .unwrap();
8901 assert_eq!(row_values(&row)[0], SqliteValue::Null);
8902 });
8903 }
8904
8905 #[test]
8908 fn parity_coalesce_basic() {
8909 asupersync::test_utils::run_test(|| async {
8910 let conn = Connection::open(":memory:").await.unwrap();
8911 let row = conn
8912 .query_row("SELECT COALESCE(NULL, NULL, 42, 10);")
8913 .await
8914 .unwrap();
8915 assert_eq!(row_values(&row)[0], SqliteValue::Integer(42));
8916 });
8917 }
8918
8919 #[test]
8920 fn parity_coalesce_all_null() {
8921 asupersync::test_utils::run_test(|| async {
8922 let conn = Connection::open(":memory:").await.unwrap();
8923 let row = conn
8924 .query_row("SELECT COALESCE(NULL, NULL);")
8925 .await
8926 .unwrap();
8927 assert_eq!(row_values(&row)[0], SqliteValue::Null);
8928 });
8929 }
8930
8931 #[test]
8932 fn parity_nullif_equal() {
8933 asupersync::test_utils::run_test(|| async {
8934 let conn = Connection::open(":memory:").await.unwrap();
8935 let row = conn.query_row("SELECT NULLIF(5, 5);").await.unwrap();
8936 assert_eq!(row_values(&row)[0], SqliteValue::Null);
8937 });
8938 }
8939
8940 #[test]
8941 fn parity_nullif_not_equal() {
8942 asupersync::test_utils::run_test(|| async {
8943 let conn = Connection::open(":memory:").await.unwrap();
8944 let row = conn.query_row("SELECT NULLIF(5, 3);").await.unwrap();
8945 assert_eq!(row_values(&row)[0], SqliteValue::Integer(5));
8946 });
8947 }
8948
8949 #[test]
8950 fn parity_iif_true() {
8951 asupersync::test_utils::run_test(|| async {
8952 let conn = Connection::open(":memory:").await.unwrap();
8953 let row = conn
8954 .query_row("SELECT IIF(1=1, 'yes', 'no');")
8955 .await
8956 .unwrap();
8957 assert_eq!(row_values(&row)[0], SqliteValue::Text("yes".into()));
8958 });
8959 }
8960
8961 #[test]
8962 fn parity_iif_false() {
8963 asupersync::test_utils::run_test(|| async {
8964 let conn = Connection::open(":memory:").await.unwrap();
8965 let row = conn
8966 .query_row("SELECT IIF(1=0, 'yes', 'no');")
8967 .await
8968 .unwrap();
8969 assert_eq!(row_values(&row)[0], SqliteValue::Text("no".into()));
8970 });
8971 }
8972
8973 #[test]
8976 fn parity_between_basic() {
8977 asupersync::test_utils::run_test(|| async {
8978 let conn = Connection::open(":memory:").await.unwrap();
8979 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
8980 conn.execute("INSERT INTO t VALUES(1);").await.unwrap();
8981 conn.execute("INSERT INTO t VALUES(5);").await.unwrap();
8982 conn.execute("INSERT INTO t VALUES(10);").await.unwrap();
8983 conn.execute("INSERT INTO t VALUES(15);").await.unwrap();
8984 let rows = conn
8985 .query("SELECT x FROM t WHERE x BETWEEN 5 AND 10 ORDER BY x;")
8986 .await
8987 .unwrap();
8988 assert_eq!(rows.len(), 2);
8989 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(5));
8990 assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(10));
8991 });
8992 }
8993
8994 #[test]
8995 fn parity_not_between() {
8996 asupersync::test_utils::run_test(|| async {
8997 let conn = Connection::open(":memory:").await.unwrap();
8998 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
8999 conn.execute("INSERT INTO t VALUES(1);").await.unwrap();
9000 conn.execute("INSERT INTO t VALUES(5);").await.unwrap();
9001 conn.execute("INSERT INTO t VALUES(10);").await.unwrap();
9002 let rows = conn
9003 .query("SELECT x FROM t WHERE x NOT BETWEEN 3 AND 7 ORDER BY x;")
9004 .await
9005 .unwrap();
9006 assert_eq!(rows.len(), 2);
9007 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
9008 assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(10));
9009 });
9010 }
9011
9012 #[test]
9015 fn parity_like_percent() {
9016 asupersync::test_utils::run_test(|| async {
9017 let conn = Connection::open(":memory:").await.unwrap();
9018 conn.execute("CREATE TABLE t(name TEXT);").await.unwrap();
9019 conn.execute("INSERT INTO t VALUES('apple');")
9020 .await
9021 .unwrap();
9022 conn.execute("INSERT INTO t VALUES('banana');")
9023 .await
9024 .unwrap();
9025 conn.execute("INSERT INTO t VALUES('apricot');")
9026 .await
9027 .unwrap();
9028 let rows = conn
9029 .query("SELECT name FROM t WHERE name LIKE 'ap%' ORDER BY name;")
9030 .await
9031 .unwrap();
9032 assert_eq!(rows.len(), 2);
9033 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("apple".into()));
9034 assert_eq!(row_values(&rows[1])[0], SqliteValue::Text("apricot".into()));
9035 });
9036 }
9037
9038 #[test]
9039 fn parity_like_underscore() {
9040 asupersync::test_utils::run_test(|| async {
9041 let conn = Connection::open(":memory:").await.unwrap();
9042 conn.execute("CREATE TABLE t(code TEXT);").await.unwrap();
9043 conn.execute("INSERT INTO t VALUES('a1');").await.unwrap();
9044 conn.execute("INSERT INTO t VALUES('b2');").await.unwrap();
9045 conn.execute("INSERT INTO t VALUES('abc');").await.unwrap();
9046 let rows = conn
9047 .query("SELECT code FROM t WHERE code LIKE '_2';")
9048 .await
9049 .unwrap();
9050 assert_eq!(rows.len(), 1);
9051 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("b2".into()));
9052 });
9053 }
9054
9055 #[test]
9056 fn parity_not_like() {
9057 asupersync::test_utils::run_test(|| async {
9058 let conn = Connection::open(":memory:").await.unwrap();
9059 conn.execute("CREATE TABLE t(name TEXT);").await.unwrap();
9060 conn.execute("INSERT INTO t VALUES('cat');").await.unwrap();
9061 conn.execute("INSERT INTO t VALUES('dog');").await.unwrap();
9062 conn.execute("INSERT INTO t VALUES('car');").await.unwrap();
9063 let rows = conn
9064 .query("SELECT name FROM t WHERE name NOT LIKE 'ca%' ORDER BY name;")
9065 .await
9066 .unwrap();
9067 assert_eq!(rows.len(), 1);
9068 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("dog".into()));
9069 });
9070 }
9071
9072 #[test]
9075 fn parity_inner_join() {
9076 asupersync::test_utils::run_test(|| async {
9077 let conn = Connection::open(":memory:").await.unwrap();
9078 conn.execute("CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT);")
9079 .await
9080 .unwrap();
9081 conn.execute(
9082 "CREATE TABLE orders(id INTEGER PRIMARY KEY, user_id INTEGER, item TEXT);",
9083 )
9084 .await
9085 .unwrap();
9086 conn.execute("INSERT INTO users VALUES(1, 'Alice');")
9087 .await
9088 .unwrap();
9089 conn.execute("INSERT INTO users VALUES(2, 'Bob');")
9090 .await
9091 .unwrap();
9092 conn.execute("INSERT INTO orders VALUES(1, 1, 'Book');")
9093 .await
9094 .unwrap();
9095 conn.execute("INSERT INTO orders VALUES(2, 1, 'Pen');")
9096 .await
9097 .unwrap();
9098 conn.execute("INSERT INTO orders VALUES(3, 2, 'Notebook');")
9099 .await
9100 .unwrap();
9101 let rows = conn
9102 .query(
9103 "SELECT users.name, orders.item FROM users JOIN orders ON users.id = orders.user_id;",
9104 )
9105 .await
9106 .unwrap();
9107 assert_eq!(rows.len(), 3);
9108 let mut pairs: Vec<(String, String)> = rows
9110 .iter()
9111 .map(|r| {
9112 let v = row_values(r);
9113 let name = match &v[0] {
9114 SqliteValue::Text(s) => s.to_string(),
9115 other => panic!("expected Text, got {other:?}"),
9116 };
9117 let item = match &v[1] {
9118 SqliteValue::Text(s) => s.to_string(),
9119 other => panic!("expected Text, got {other:?}"),
9120 };
9121 (name, item)
9122 })
9123 .collect();
9124 pairs.sort();
9125 assert_eq!(
9126 pairs,
9127 vec![
9128 ("Alice".into(), "Book".into()),
9129 ("Alice".into(), "Pen".into()),
9130 ("Bob".into(), "Notebook".into()),
9131 ]
9132 );
9133 });
9134 }
9135
9136 #[test]
9137 fn parity_left_join_with_nulls() {
9138 asupersync::test_utils::run_test(|| async {
9139 let conn = Connection::open(":memory:").await.unwrap();
9140 conn.execute("CREATE TABLE a(id INTEGER PRIMARY KEY, val TEXT);")
9141 .await
9142 .unwrap();
9143 conn.execute("CREATE TABLE b(id INTEGER PRIMARY KEY, a_id INTEGER, info TEXT);")
9144 .await
9145 .unwrap();
9146 conn.execute("INSERT INTO a VALUES(1, 'x');").await.unwrap();
9147 conn.execute("INSERT INTO a VALUES(2, 'y');").await.unwrap();
9148 conn.execute("INSERT INTO b VALUES(1, 1, 'linked');")
9149 .await
9150 .unwrap();
9151 let rows = conn
9152 .query("SELECT a.val, b.info FROM a LEFT JOIN b ON a.id = b.a_id;")
9153 .await
9154 .unwrap();
9155 assert_eq!(rows.len(), 2);
9156 let mut results: Vec<(String, Option<String>)> = rows
9158 .iter()
9159 .map(|r| {
9160 let v = row_values(r);
9161 let val = match &v[0] {
9162 SqliteValue::Text(s) => s.to_string(),
9163 other => panic!("expected Text, got {other:?}"),
9164 };
9165 let info = match &v[1] {
9166 SqliteValue::Text(s) => Some(s.to_string()),
9167 SqliteValue::Null => None,
9168 other => panic!("expected Text or Null, got {other:?}"),
9169 };
9170 (val, info)
9171 })
9172 .collect();
9173 results.sort();
9174 assert_eq!(
9175 results,
9176 vec![("x".into(), Some("linked".into())), ("y".into(), None),]
9177 );
9178 });
9179 }
9180
9181 #[test]
9182 fn parity_cross_join() {
9183 asupersync::test_utils::run_test(|| async {
9184 let conn = Connection::open(":memory:").await.unwrap();
9185 conn.execute("CREATE TABLE a(x INTEGER);").await.unwrap();
9186 conn.execute("CREATE TABLE b(y INTEGER);").await.unwrap();
9187 conn.execute("INSERT INTO a VALUES(1);").await.unwrap();
9188 conn.execute("INSERT INTO a VALUES(2);").await.unwrap();
9189 conn.execute("INSERT INTO b VALUES(10);").await.unwrap();
9190 conn.execute("INSERT INTO b VALUES(20);").await.unwrap();
9191 let rows = conn
9192 .query("SELECT x, y FROM a, b ORDER BY x, y;")
9193 .await
9194 .unwrap();
9195 assert_eq!(rows.len(), 4);
9197 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
9198 assert_eq!(row_values(&rows[0])[1], SqliteValue::Integer(10));
9199 assert_eq!(row_values(&rows[3])[0], SqliteValue::Integer(2));
9200 assert_eq!(row_values(&rows[3])[1], SqliteValue::Integer(20));
9201 });
9202 }
9203
9204 #[test]
9207 fn parity_union_removes_duplicates() {
9208 asupersync::test_utils::run_test(|| async {
9209 let conn = Connection::open(":memory:").await.unwrap();
9210 conn.execute("CREATE TABLE a(x INTEGER);").await.unwrap();
9211 conn.execute("CREATE TABLE b(x INTEGER);").await.unwrap();
9212 conn.execute("INSERT INTO a VALUES(1);").await.unwrap();
9213 conn.execute("INSERT INTO a VALUES(2);").await.unwrap();
9214 conn.execute("INSERT INTO b VALUES(2);").await.unwrap();
9215 conn.execute("INSERT INTO b VALUES(3);").await.unwrap();
9216 let rows = conn
9217 .query("SELECT x FROM a UNION SELECT x FROM b ORDER BY x;")
9218 .await
9219 .unwrap();
9220 assert_eq!(rows.len(), 3);
9221 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
9222 assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(2));
9223 assert_eq!(row_values(&rows[2])[0], SqliteValue::Integer(3));
9224 });
9225 }
9226
9227 #[test]
9228 fn parity_union_all_keeps_duplicates() {
9229 asupersync::test_utils::run_test(|| async {
9230 let conn = Connection::open(":memory:").await.unwrap();
9231 conn.execute("CREATE TABLE a(x INTEGER);").await.unwrap();
9232 conn.execute("CREATE TABLE b(x INTEGER);").await.unwrap();
9233 conn.execute("INSERT INTO a VALUES(1);").await.unwrap();
9234 conn.execute("INSERT INTO a VALUES(2);").await.unwrap();
9235 conn.execute("INSERT INTO b VALUES(2);").await.unwrap();
9236 conn.execute("INSERT INTO b VALUES(3);").await.unwrap();
9237 let rows = conn
9238 .query("SELECT x FROM a UNION ALL SELECT x FROM b ORDER BY x;")
9239 .await
9240 .unwrap();
9241 assert_eq!(rows.len(), 4);
9242 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
9243 assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(2));
9244 assert_eq!(row_values(&rows[2])[0], SqliteValue::Integer(2));
9245 assert_eq!(row_values(&rows[3])[0], SqliteValue::Integer(3));
9246 });
9247 }
9248
9249 #[test]
9252 fn parity_update_multiple_columns() {
9253 asupersync::test_utils::run_test(|| async {
9254 let conn = Connection::open(":memory:").await.unwrap();
9255 conn.execute("CREATE TABLE t(a INTEGER, b TEXT, c REAL);")
9256 .await
9257 .unwrap();
9258 conn.execute("INSERT INTO t VALUES(1, 'old', 1.0);")
9259 .await
9260 .unwrap();
9261 conn.execute("UPDATE t SET b = 'new', c = 2.5 WHERE a = 1;")
9262 .await
9263 .unwrap();
9264 let row = conn.query_row("SELECT a, b, c FROM t;").await.unwrap();
9265 let vals = row_values(&row);
9266 assert_eq!(vals[0], SqliteValue::Integer(1));
9267 assert_eq!(vals[1], SqliteValue::Text("new".into()));
9268 assert_eq!(vals[2], SqliteValue::Float(2.5));
9269 });
9270 }
9271
9272 #[test]
9273 fn parity_delete_with_where() {
9274 asupersync::test_utils::run_test(|| async {
9275 let conn = Connection::open(":memory:").await.unwrap();
9276 conn.execute("CREATE TABLE t(id INTEGER PRIMARY KEY, val TEXT);")
9277 .await
9278 .unwrap();
9279 conn.execute("INSERT INTO t VALUES(1, 'a');").await.unwrap();
9280 conn.execute("INSERT INTO t VALUES(2, 'b');").await.unwrap();
9281 conn.execute("INSERT INTO t VALUES(3, 'c');").await.unwrap();
9282 conn.execute("DELETE FROM t WHERE id > 1;").await.unwrap();
9283 let rows = conn.query("SELECT val FROM t;").await.unwrap();
9284 assert_eq!(rows.len(), 1);
9285 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("a".into()));
9286 });
9287 }
9288
9289 #[test]
9292 fn parity_having_basic() {
9293 asupersync::test_utils::run_test(|| async {
9294 let conn = Connection::open(":memory:").await.unwrap();
9295 conn.execute("CREATE TABLE sales(product TEXT, amount INTEGER);")
9296 .await
9297 .unwrap();
9298 conn.execute("INSERT INTO sales VALUES('A', 10);")
9299 .await
9300 .unwrap();
9301 conn.execute("INSERT INTO sales VALUES('A', 20);")
9302 .await
9303 .unwrap();
9304 conn.execute("INSERT INTO sales VALUES('B', 5);")
9305 .await
9306 .unwrap();
9307 conn.execute("INSERT INTO sales VALUES('C', 30);")
9308 .await
9309 .unwrap();
9310 conn.execute("INSERT INTO sales VALUES('C', 40);")
9311 .await
9312 .unwrap();
9313 let rows = conn
9314 .query(
9315 "SELECT product, SUM(amount) FROM sales GROUP BY product HAVING SUM(amount) > 15 ORDER BY product;",
9316 )
9317 .await
9318 .unwrap();
9319 assert_eq!(rows.len(), 2);
9320 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("A".into()));
9321 assert_eq!(row_values(&rows[0])[1], SqliteValue::Integer(30));
9322 assert_eq!(row_values(&rows[1])[0], SqliteValue::Text("C".into()));
9323 assert_eq!(row_values(&rows[1])[1], SqliteValue::Integer(70));
9324 });
9325 }
9326
9327 #[test]
9330 fn parity_in_values_list() {
9331 asupersync::test_utils::run_test(|| async {
9332 let conn = Connection::open(":memory:").await.unwrap();
9333 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
9334 conn.execute("INSERT INTO t VALUES(1);").await.unwrap();
9335 conn.execute("INSERT INTO t VALUES(2);").await.unwrap();
9336 conn.execute("INSERT INTO t VALUES(3);").await.unwrap();
9337 conn.execute("INSERT INTO t VALUES(4);").await.unwrap();
9338 let rows = conn
9339 .query("SELECT x FROM t WHERE x IN (1, 3) ORDER BY x;")
9340 .await
9341 .unwrap();
9342 assert_eq!(rows.len(), 2);
9343 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
9344 assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(3));
9345 });
9346 }
9347
9348 #[test]
9349 fn parity_not_in_values_list() {
9350 asupersync::test_utils::run_test(|| async {
9351 let conn = Connection::open(":memory:").await.unwrap();
9352 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
9353 conn.execute("INSERT INTO t VALUES(1);").await.unwrap();
9354 conn.execute("INSERT INTO t VALUES(2);").await.unwrap();
9355 conn.execute("INSERT INTO t VALUES(3);").await.unwrap();
9356 let rows = conn
9357 .query("SELECT x FROM t WHERE x NOT IN (1, 3) ORDER BY x;")
9358 .await
9359 .unwrap();
9360 assert_eq!(rows.len(), 1);
9361 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(2));
9362 });
9363 }
9364
9365 #[test]
9368 fn parity_unary_minus() {
9369 asupersync::test_utils::run_test(|| async {
9370 let conn = Connection::open(":memory:").await.unwrap();
9371 let row = conn.query_row("SELECT -42;").await.unwrap();
9372 assert_eq!(row_values(&row)[0], SqliteValue::Integer(-42));
9373 });
9374 }
9375
9376 #[test]
9377 fn parity_string_concat_operator() {
9378 asupersync::test_utils::run_test(|| async {
9379 let conn = Connection::open(":memory:").await.unwrap();
9380 let row = conn
9381 .query_row("SELECT 'hello' || ' ' || 'world';")
9382 .await
9383 .unwrap();
9384 assert_eq!(row_values(&row)[0], SqliteValue::Text("hello world".into()));
9385 });
9386 }
9387
9388 #[test]
9389 fn parity_typeof_function() {
9390 asupersync::test_utils::run_test(|| async {
9391 let conn = Connection::open(":memory:").await.unwrap();
9392 let row = conn.query_row("SELECT typeof(42);").await.unwrap();
9393 assert_eq!(row_values(&row)[0], SqliteValue::Text("integer".into()));
9394 let row = conn.query_row("SELECT typeof(3.14);").await.unwrap();
9395 assert_eq!(row_values(&row)[0], SqliteValue::Text("real".into()));
9396 let row = conn.query_row("SELECT typeof('hi');").await.unwrap();
9397 assert_eq!(row_values(&row)[0], SqliteValue::Text("text".into()));
9398 let row = conn.query_row("SELECT typeof(NULL);").await.unwrap();
9399 assert_eq!(row_values(&row)[0], SqliteValue::Text("null".into()));
9400 });
9401 }
9402
9403 #[test]
9404 fn parity_abs_function() {
9405 asupersync::test_utils::run_test(|| async {
9406 let conn = Connection::open(":memory:").await.unwrap();
9407 let row = conn.query_row("SELECT ABS(-10);").await.unwrap();
9408 assert_eq!(row_values(&row)[0], SqliteValue::Integer(10));
9409 let row = conn.query_row("SELECT ABS(10);").await.unwrap();
9410 assert_eq!(row_values(&row)[0], SqliteValue::Integer(10));
9411 });
9412 }
9413
9414 #[test]
9415 fn parity_upper_lower_functions() {
9416 asupersync::test_utils::run_test(|| async {
9417 let conn = Connection::open(":memory:").await.unwrap();
9418 let row = conn.query_row("SELECT UPPER('hello');").await.unwrap();
9419 assert_eq!(row_values(&row)[0], SqliteValue::Text("HELLO".into()));
9420 let row = conn.query_row("SELECT LOWER('WORLD');").await.unwrap();
9421 assert_eq!(row_values(&row)[0], SqliteValue::Text("world".into()));
9422 });
9423 }
9424
9425 #[test]
9426 fn parity_length_function() {
9427 asupersync::test_utils::run_test(|| async {
9428 let conn = Connection::open(":memory:").await.unwrap();
9429 let row = conn.query_row("SELECT LENGTH('hello');").await.unwrap();
9430 assert_eq!(row_values(&row)[0], SqliteValue::Integer(5));
9431 let row = conn.query_row("SELECT LENGTH('');").await.unwrap();
9432 assert_eq!(row_values(&row)[0], SqliteValue::Integer(0));
9433 });
9434 }
9435
9436 #[test]
9437 fn parity_min_max_aggregate() {
9438 asupersync::test_utils::run_test(|| async {
9439 let conn = Connection::open(":memory:").await.unwrap();
9440 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
9441 conn.execute("INSERT INTO t VALUES(3);").await.unwrap();
9442 conn.execute("INSERT INTO t VALUES(1);").await.unwrap();
9443 conn.execute("INSERT INTO t VALUES(4);").await.unwrap();
9444 conn.execute("INSERT INTO t VALUES(1);").await.unwrap();
9445 conn.execute("INSERT INTO t VALUES(5);").await.unwrap();
9446 let row = conn
9447 .query_row("SELECT MIN(x), MAX(x) FROM t;")
9448 .await
9449 .unwrap();
9450 let vals = row_values(&row);
9451 assert_eq!(vals[0], SqliteValue::Integer(1));
9452 assert_eq!(vals[1], SqliteValue::Integer(5));
9453 });
9454 }
9455
9456 #[test]
9457 fn parity_avg_aggregate() {
9458 asupersync::test_utils::run_test(|| async {
9459 let conn = Connection::open(":memory:").await.unwrap();
9460 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
9461 conn.execute("INSERT INTO t VALUES(10);").await.unwrap();
9462 conn.execute("INSERT INTO t VALUES(20);").await.unwrap();
9463 conn.execute("INSERT INTO t VALUES(30);").await.unwrap();
9464 let row = conn.query_row("SELECT AVG(x) FROM t;").await.unwrap();
9465 assert_eq!(row_values(&row)[0], SqliteValue::Float(20.0));
9466 });
9467 }
9468
9469 #[test]
9472 fn parity_update_where_in_subquery() {
9473 asupersync::test_utils::run_test(|| async {
9474 let conn = Connection::open(":memory:").await.unwrap();
9475 conn.execute("CREATE TABLE items(id INTEGER PRIMARY KEY, name TEXT, price INTEGER);")
9476 .await
9477 .unwrap();
9478 conn.execute("INSERT INTO items VALUES(1, 'apple', 10);")
9479 .await
9480 .unwrap();
9481 conn.execute("INSERT INTO items VALUES(2, 'banana', 20);")
9482 .await
9483 .unwrap();
9484 conn.execute("INSERT INTO items VALUES(3, 'cherry', 30);")
9485 .await
9486 .unwrap();
9487 conn.execute("CREATE TABLE expensive(id INTEGER);")
9488 .await
9489 .unwrap();
9490 conn.execute("INSERT INTO expensive VALUES(2);")
9491 .await
9492 .unwrap();
9493 conn.execute("INSERT INTO expensive VALUES(3);")
9494 .await
9495 .unwrap();
9496 conn.execute(
9498 "UPDATE items SET price = price * 2 WHERE id IN (SELECT id FROM expensive);",
9499 )
9500 .await
9501 .unwrap();
9502 let rows = conn
9503 .query("SELECT id, price FROM items ORDER BY id;")
9504 .await
9505 .unwrap();
9506 let results: Vec<(i64, i64)> = rows
9507 .iter()
9508 .map(|r| {
9509 let vals = row_values(r);
9510 (vals[0].to_integer(), vals[1].to_integer())
9511 })
9512 .collect();
9513 assert_eq!(results, vec![(1, 10), (2, 40), (3, 60)]);
9514 });
9515 }
9516
9517 #[test]
9520 fn parity_delete_where_in_subquery() {
9521 asupersync::test_utils::run_test(|| async {
9522 let conn = Connection::open(":memory:").await.unwrap();
9523 conn.execute("CREATE TABLE data(id INTEGER, val TEXT);")
9524 .await
9525 .unwrap();
9526 conn.execute("INSERT INTO data VALUES(1, 'a');")
9527 .await
9528 .unwrap();
9529 conn.execute("INSERT INTO data VALUES(2, 'b');")
9530 .await
9531 .unwrap();
9532 conn.execute("INSERT INTO data VALUES(3, 'c');")
9533 .await
9534 .unwrap();
9535 conn.execute("CREATE TABLE to_remove(id INTEGER);")
9536 .await
9537 .unwrap();
9538 conn.execute("INSERT INTO to_remove VALUES(1);")
9539 .await
9540 .unwrap();
9541 conn.execute("INSERT INTO to_remove VALUES(3);")
9542 .await
9543 .unwrap();
9544 conn.execute("DELETE FROM data WHERE id IN (SELECT id FROM to_remove);")
9546 .await
9547 .unwrap();
9548 let rows = conn
9549 .query("SELECT id, val FROM data ORDER BY id;")
9550 .await
9551 .unwrap();
9552 let results: Vec<(i64, String)> = rows
9553 .iter()
9554 .map(|r| {
9555 let vals = row_values(r);
9556 (
9557 vals[0].to_integer(),
9558 match &vals[1] {
9559 SqliteValue::Text(s) => s.to_string(),
9560 _ => panic!("expected text"),
9561 },
9562 )
9563 })
9564 .collect();
9565 assert_eq!(results, vec![(2, "b".into())]);
9566 });
9567 }
9568
9569 #[test]
9572 fn parity_datetime_date_function() {
9573 asupersync::test_utils::run_test(|| async {
9574 let conn = Connection::open(":memory:").await.unwrap();
9575 let row = conn
9576 .query_row("SELECT date('2023-06-15 14:30:00');")
9577 .await
9578 .unwrap();
9579 assert_eq!(row_values(&row)[0], SqliteValue::Text("2023-06-15".into()));
9580 });
9581 }
9582
9583 #[test]
9584 fn parity_datetime_time_function() {
9585 asupersync::test_utils::run_test(|| async {
9586 let conn = Connection::open(":memory:").await.unwrap();
9587 let row = conn
9588 .query_row("SELECT time('2023-06-15 14:30:45');")
9589 .await
9590 .unwrap();
9591 assert_eq!(row_values(&row)[0], SqliteValue::Text("14:30:45".into()));
9592 });
9593 }
9594
9595 #[test]
9596 fn parity_datetime_strftime() {
9597 asupersync::test_utils::run_test(|| async {
9598 let conn = Connection::open(":memory:").await.unwrap();
9599 let row = conn
9600 .query_row("SELECT strftime('%Y', '2023-06-15');")
9601 .await
9602 .unwrap();
9603 assert_eq!(row_values(&row)[0], SqliteValue::Text("2023".into()));
9604 });
9605 }
9606
9607 #[test]
9610 fn parity_total_aggregate() {
9611 asupersync::test_utils::run_test(|| async {
9612 let conn = Connection::open(":memory:").await.unwrap();
9614 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
9615 let row = conn.query_row("SELECT TOTAL(x) FROM t;").await.unwrap();
9616 assert_eq!(row_values(&row)[0], SqliteValue::Float(0.0));
9617 });
9618 }
9619
9620 #[test]
9621 fn parity_total_aggregate_with_values() {
9622 asupersync::test_utils::run_test(|| async {
9623 let conn = Connection::open(":memory:").await.unwrap();
9624 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
9625 conn.execute("INSERT INTO t VALUES(10);").await.unwrap();
9626 conn.execute("INSERT INTO t VALUES(20);").await.unwrap();
9627 let row = conn.query_row("SELECT TOTAL(x) FROM t;").await.unwrap();
9628 assert_eq!(row_values(&row)[0], SqliteValue::Float(30.0));
9629 });
9630 }
9631
9632 #[test]
9635 fn parity_glob_operator() {
9636 asupersync::test_utils::run_test(|| async {
9637 let conn = Connection::open(":memory:").await.unwrap();
9638 conn.execute("CREATE TABLE files(name TEXT);")
9639 .await
9640 .unwrap();
9641 conn.execute("INSERT INTO files VALUES('readme.txt');")
9642 .await
9643 .unwrap();
9644 conn.execute("INSERT INTO files VALUES('main.rs');")
9645 .await
9646 .unwrap();
9647 conn.execute("INSERT INTO files VALUES('test.txt');")
9648 .await
9649 .unwrap();
9650 let rows = conn
9651 .query("SELECT name FROM files WHERE name GLOB '*.txt' ORDER BY name;")
9652 .await
9653 .unwrap();
9654 let results: Vec<String> = rows
9655 .iter()
9656 .map(|r| match &row_values(r)[0] {
9657 SqliteValue::Text(s) => s.to_string(),
9658 _ => panic!("expected text"),
9659 })
9660 .collect();
9661 assert_eq!(results, vec!["readme.txt", "test.txt"]);
9662 });
9663 }
9664
9665 #[test]
9668 fn parity_replace_function() {
9669 asupersync::test_utils::run_test(|| async {
9670 let conn = Connection::open(":memory:").await.unwrap();
9671 let row = conn
9672 .query_row("SELECT replace('hello world', 'world', 'rust');")
9673 .await
9674 .unwrap();
9675 assert_eq!(row_values(&row)[0], SqliteValue::Text("hello rust".into()));
9676 });
9677 }
9678
9679 #[test]
9682 fn parity_zeroblob_function() {
9683 asupersync::test_utils::run_test(|| async {
9684 let conn = Connection::open(":memory:").await.unwrap();
9685 let row = conn
9686 .query_row("SELECT typeof(zeroblob(4)), length(zeroblob(4));")
9687 .await
9688 .unwrap();
9689 let vals = row_values(&row);
9690 assert_eq!(vals[0], SqliteValue::Text("blob".into()));
9691 assert_eq!(vals[1], SqliteValue::Integer(4));
9692 });
9693 }
9694
9695 #[test]
9698 fn parity_unicode_function() {
9699 asupersync::test_utils::run_test(|| async {
9700 let conn = Connection::open(":memory:").await.unwrap();
9701 let row = conn.query_row("SELECT unicode('A');").await.unwrap();
9702 assert_eq!(row_values(&row)[0], SqliteValue::Integer(65));
9703 });
9704 }
9705
9706 #[test]
9707 fn parity_char_function() {
9708 asupersync::test_utils::run_test(|| async {
9709 let conn = Connection::open(":memory:").await.unwrap();
9710 let row = conn.query_row("SELECT char(65, 66, 67);").await.unwrap();
9711 assert_eq!(row_values(&row)[0], SqliteValue::Text("ABC".into()));
9712 });
9713 }
9714
9715 #[test]
9718 fn parity_instr_multi_occurrence() {
9719 asupersync::test_utils::run_test(|| async {
9720 let conn = Connection::open(":memory:").await.unwrap();
9721 let row = conn
9723 .query_row("SELECT instr('abcabc', 'bc');")
9724 .await
9725 .unwrap();
9726 assert_eq!(row_values(&row)[0], SqliteValue::Integer(2));
9727 });
9728 }
9729
9730 #[test]
9733 fn parity_printf_function() {
9734 asupersync::test_utils::run_test(|| async {
9735 let conn = Connection::open(":memory:").await.unwrap();
9736 let row = conn
9737 .query_row("SELECT printf('%d + %d = %d', 1, 2, 3);")
9738 .await
9739 .unwrap();
9740 assert_eq!(row_values(&row)[0], SqliteValue::Text("1 + 2 = 3".into()));
9741 });
9742 }
9743
9744 #[test]
9747 fn parity_row_number_window() {
9748 asupersync::test_utils::run_test(|| async {
9749 let conn = Connection::open(":memory:").await.unwrap();
9750 conn.execute("CREATE TABLE w(name TEXT, val INTEGER);")
9751 .await
9752 .unwrap();
9753 conn.execute("INSERT INTO w VALUES('a', 10);")
9754 .await
9755 .unwrap();
9756 conn.execute("INSERT INTO w VALUES('b', 20);")
9757 .await
9758 .unwrap();
9759 conn.execute("INSERT INTO w VALUES('c', 30);")
9760 .await
9761 .unwrap();
9762 let rows = conn
9763 .query("SELECT name, ROW_NUMBER() OVER (ORDER BY val) FROM w;")
9764 .await
9765 .unwrap();
9766 let results: Vec<(String, i64)> = rows
9767 .iter()
9768 .map(|r| {
9769 let vals = row_values(r);
9770 let name = match &vals[0] {
9771 SqliteValue::Text(s) => s.to_string(),
9772 _ => panic!("expected text"),
9773 };
9774 (name, vals[1].to_integer())
9775 })
9776 .collect();
9777 assert_eq!(
9778 results,
9779 vec![("a".into(), 1), ("b".into(), 2), ("c".into(), 3),]
9780 );
9781 });
9782 }
9783
9784 #[test]
9785 fn window_row_number_partition_by() {
9786 asupersync::test_utils::run_test(|| async {
9787 let conn = Connection::open(":memory:").await.unwrap();
9788 conn.execute("CREATE TABLE wp(dept TEXT, name TEXT, val INTEGER);")
9789 .await
9790 .unwrap();
9791 conn.execute("INSERT INTO wp VALUES('eng','a',10);")
9792 .await
9793 .unwrap();
9794 conn.execute("INSERT INTO wp VALUES('eng','b',20);")
9795 .await
9796 .unwrap();
9797 conn.execute("INSERT INTO wp VALUES('sales','c',5);")
9798 .await
9799 .unwrap();
9800 conn.execute("INSERT INTO wp VALUES('sales','d',15);")
9801 .await
9802 .unwrap();
9803 let rows = conn
9804 .query("SELECT dept, name, ROW_NUMBER() OVER (PARTITION BY dept ORDER BY val) FROM wp;")
9805 .await
9806 .unwrap();
9807 let results: Vec<(String, String, i64)> = rows
9808 .iter()
9809 .map(|r| {
9810 let vals = row_values(r);
9811 let dept = match &vals[0] {
9812 SqliteValue::Text(s) => s.to_string(),
9813 _ => panic!("expected text"),
9814 };
9815 let name = match &vals[1] {
9816 SqliteValue::Text(s) => s.to_string(),
9817 _ => panic!("expected text"),
9818 };
9819 (dept, name, vals[2].to_integer())
9820 })
9821 .collect();
9822 assert_eq!(
9823 results,
9824 vec![
9825 ("eng".into(), "a".into(), 1),
9826 ("eng".into(), "b".into(), 2),
9827 ("sales".into(), "c".into(), 1),
9828 ("sales".into(), "d".into(), 2),
9829 ]
9830 );
9831 });
9832 }
9833
9834 #[test]
9835 fn window_rank_and_dense_rank() {
9836 asupersync::test_utils::run_test(|| async {
9837 let conn = Connection::open(":memory:").await.unwrap();
9838 conn.execute("CREATE TABLE wr(name TEXT, score INTEGER);")
9839 .await
9840 .unwrap();
9841 conn.execute("INSERT INTO wr VALUES('a', 100);")
9842 .await
9843 .unwrap();
9844 conn.execute("INSERT INTO wr VALUES('b', 100);")
9845 .await
9846 .unwrap();
9847 conn.execute("INSERT INTO wr VALUES('c', 90);")
9848 .await
9849 .unwrap();
9850 conn.execute("INSERT INTO wr VALUES('d', 80);")
9851 .await
9852 .unwrap();
9853 let rows = conn
9854 .query(
9855 "SELECT name, RANK() OVER (ORDER BY score DESC), \
9856 DENSE_RANK() OVER (ORDER BY score DESC) FROM wr;",
9857 )
9858 .await
9859 .unwrap();
9860 let results: Vec<(String, i64, i64)> = rows
9861 .iter()
9862 .map(|r| {
9863 let vals = row_values(r);
9864 let name = match &vals[0] {
9865 SqliteValue::Text(s) => s.to_string(),
9866 _ => panic!("expected text"),
9867 };
9868 (name, vals[1].to_integer(), vals[2].to_integer())
9869 })
9870 .collect();
9871 assert_eq!(
9874 results,
9875 vec![
9876 ("a".into(), 1, 1),
9877 ("b".into(), 1, 1),
9878 ("c".into(), 3, 2),
9879 ("d".into(), 4, 3),
9880 ]
9881 );
9882 });
9883 }
9884
9885 #[test]
9886 fn window_row_number_desc_order() {
9887 asupersync::test_utils::run_test(|| async {
9888 let conn = Connection::open(":memory:").await.unwrap();
9889 conn.execute("CREATE TABLE wd(x INTEGER);").await.unwrap();
9890 conn.execute("INSERT INTO wd VALUES(1);").await.unwrap();
9891 conn.execute("INSERT INTO wd VALUES(2);").await.unwrap();
9892 conn.execute("INSERT INTO wd VALUES(3);").await.unwrap();
9893 let rows = conn
9894 .query("SELECT x, ROW_NUMBER() OVER (ORDER BY x DESC) FROM wd;")
9895 .await
9896 .unwrap();
9897 let results: Vec<(i64, i64)> = rows
9898 .iter()
9899 .map(|r| {
9900 let vals = row_values(r);
9901 (vals[0].to_integer(), vals[1].to_integer())
9902 })
9903 .collect();
9904 assert_eq!(results, vec![(3, 1), (2, 2), (1, 3)]);
9906 });
9907 }
9908
9909 #[test]
9910 fn window_multiple_window_functions() {
9911 asupersync::test_utils::run_test(|| async {
9912 let conn = Connection::open(":memory:").await.unwrap();
9913 conn.execute("CREATE TABLE wm(name TEXT, val INTEGER);")
9914 .await
9915 .unwrap();
9916 conn.execute("INSERT INTO wm VALUES('a', 10);")
9917 .await
9918 .unwrap();
9919 conn.execute("INSERT INTO wm VALUES('b', 20);")
9920 .await
9921 .unwrap();
9922 conn.execute("INSERT INTO wm VALUES('c', 30);")
9923 .await
9924 .unwrap();
9925 let rows = conn
9926 .query(
9927 "SELECT name, ROW_NUMBER() OVER (ORDER BY val), \
9928 DENSE_RANK() OVER (ORDER BY val) FROM wm;",
9929 )
9930 .await
9931 .unwrap();
9932 let results: Vec<(String, i64, i64)> = rows
9933 .iter()
9934 .map(|r| {
9935 let vals = row_values(r);
9936 let name = match &vals[0] {
9937 SqliteValue::Text(s) => s.to_string(),
9938 _ => panic!("expected text"),
9939 };
9940 (name, vals[1].to_integer(), vals[2].to_integer())
9941 })
9942 .collect();
9943 assert_eq!(
9945 results,
9946 vec![("a".into(), 1, 1), ("b".into(), 2, 2), ("c".into(), 3, 3),]
9947 );
9948 });
9949 }
9950
9951 #[test]
9952 fn window_lag_negative_offset_reads_following_row() {
9953 asupersync::test_utils::run_test(|| async {
9954 let conn = Connection::open(":memory:").await.unwrap();
9955 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val INTEGER);")
9956 .await
9957 .unwrap();
9958 conn.execute("INSERT INTO t1 VALUES(1,10),(2,20),(3,30);")
9959 .await
9960 .unwrap();
9961 let rows = conn
9962 .query("SELECT id, lag(val,-1,'D') OVER (ORDER BY id) FROM t1 ORDER BY id;")
9963 .await
9964 .unwrap();
9965 let results: Vec<Vec<SqliteValue>> = rows.iter().map(row_values).collect();
9966 assert_eq!(
9967 results,
9968 vec![
9969 vec![SqliteValue::Integer(1), SqliteValue::Integer(20)],
9970 vec![SqliteValue::Integer(2), SqliteValue::Integer(30)],
9971 vec![SqliteValue::Integer(3), SqliteValue::Text("D".into())],
9972 ]
9973 );
9974 });
9975 }
9976
9977 #[test]
9978 fn window_lag_lead_fractional_offset_uses_default() {
9979 asupersync::test_utils::run_test(|| async {
9980 let conn = Connection::open(":memory:").await.unwrap();
9981 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val TEXT, off REAL);")
9982 .await
9983 .unwrap();
9984 conn.execute("INSERT INTO t1 VALUES(1,'a',1.5),(2,'b',1.5),(3,'c',1.5);")
9985 .await
9986 .unwrap();
9987 let rows = conn
9988 .query(
9989 "SELECT id, \
9990 lag(val,off,'D') OVER (ORDER BY id), \
9991 lead(val,off,'D') OVER (ORDER BY id) \
9992 FROM t1 ORDER BY id;",
9993 )
9994 .await
9995 .unwrap();
9996 let results: Vec<Vec<SqliteValue>> = rows.iter().map(row_values).collect();
9997 assert_eq!(
9998 results,
9999 vec![
10000 vec![
10001 SqliteValue::Integer(1),
10002 SqliteValue::Text("D".into()),
10003 SqliteValue::Text("D".into()),
10004 ],
10005 vec![
10006 SqliteValue::Integer(2),
10007 SqliteValue::Text("D".into()),
10008 SqliteValue::Text("D".into()),
10009 ],
10010 vec![
10011 SqliteValue::Integer(3),
10012 SqliteValue::Text("D".into()),
10013 SqliteValue::Text("D".into()),
10014 ],
10015 ]
10016 );
10017 });
10018 }
10019
10020 #[test]
10021 fn window_grouped_ntile_advances_two_pass_position() {
10022 asupersync::test_utils::run_test(|| async {
10023 let conn = Connection::open(":memory:").await.unwrap();
10024 conn.execute("CREATE TABLE t(id INTEGER, val INTEGER);")
10025 .await
10026 .unwrap();
10027 conn.execute("INSERT INTO t VALUES(1,10),(2,20),(3,30);")
10028 .await
10029 .unwrap();
10030 let rows = conn
10031 .query("SELECT id, ntile(2) OVER (ORDER BY id) FROM t GROUP BY id ORDER BY id;")
10032 .await
10033 .unwrap();
10034 let results: Vec<Vec<SqliteValue>> = rows.iter().map(row_values).collect();
10035 assert_eq!(
10036 results,
10037 vec![
10038 vec![SqliteValue::Integer(1), SqliteValue::Integer(1)],
10039 vec![SqliteValue::Integer(2), SqliteValue::Integer(1)],
10040 vec![SqliteValue::Integer(3), SqliteValue::Integer(2)],
10041 ]
10042 );
10043 });
10044 }
10045
10046 #[test]
10047 fn window_nth_value_uses_current_row_n_for_full_frame() {
10048 asupersync::test_utils::run_test(|| async {
10049 let conn = Connection::open(":memory:").await.unwrap();
10050 conn.execute("CREATE TABLE t(x TEXT, n INTEGER);")
10051 .await
10052 .unwrap();
10053 conn.execute("INSERT INTO t VALUES('a',1),('b',2),('c',3);")
10054 .await
10055 .unwrap();
10056 let rows = conn
10057 .query(
10058 "SELECT x, n, nth_value(x,n) OVER (\
10059 ORDER BY x ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING\
10060 ) FROM t ORDER BY x;",
10061 )
10062 .await
10063 .unwrap();
10064 let results: Vec<Vec<SqliteValue>> = rows.iter().map(row_values).collect();
10065 assert_eq!(
10066 results,
10067 vec![
10068 vec![
10069 SqliteValue::Text("a".into()),
10070 SqliteValue::Integer(1),
10071 SqliteValue::Text("a".into()),
10072 ],
10073 vec![
10074 SqliteValue::Text("b".into()),
10075 SqliteValue::Integer(2),
10076 SqliteValue::Text("b".into()),
10077 ],
10078 vec![
10079 SqliteValue::Text("c".into()),
10080 SqliteValue::Integer(3),
10081 SqliteValue::Text("c".into()),
10082 ],
10083 ]
10084 );
10085 });
10086 }
10087
10088 #[test]
10089 fn window_nth_value_uses_current_row_n_for_sliding_frame() {
10090 asupersync::test_utils::run_test(|| async {
10091 let conn = Connection::open(":memory:").await.unwrap();
10092 conn.execute("CREATE TABLE t(x TEXT, n INTEGER);")
10093 .await
10094 .unwrap();
10095 conn.execute("INSERT INTO t VALUES('a',1),('b',2),('c',1);")
10096 .await
10097 .unwrap();
10098 let rows = conn
10099 .query(
10100 "SELECT x, n, nth_value(x,n) OVER (\
10101 ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW\
10102 ) FROM t ORDER BY x;",
10103 )
10104 .await
10105 .unwrap();
10106 let results: Vec<Vec<SqliteValue>> = rows.iter().map(row_values).collect();
10107 assert_eq!(
10108 results,
10109 vec![
10110 vec![
10111 SqliteValue::Text("a".into()),
10112 SqliteValue::Integer(1),
10113 SqliteValue::Text("a".into()),
10114 ],
10115 vec![
10116 SqliteValue::Text("b".into()),
10117 SqliteValue::Integer(2),
10118 SqliteValue::Text("b".into()),
10119 ],
10120 vec![
10121 SqliteValue::Text("c".into()),
10122 SqliteValue::Integer(1),
10123 SqliteValue::Text("b".into()),
10124 ],
10125 ]
10126 );
10127 });
10128 }
10129
10130 #[test]
10131 fn window_nth_value_rejects_fractional_n() {
10132 asupersync::test_utils::run_test(|| async {
10133 let conn = Connection::open(":memory:").await.unwrap();
10134 let err = conn
10135 .query(
10136 "WITH t(x,n) AS (VALUES ('a',1.5),('b',1.5)) \
10137 SELECT nth_value(x,n) OVER (\
10138 ORDER BY x ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING\
10139 ) FROM t;",
10140 )
10141 .await
10142 .expect_err("fractional nth_value offset should fail");
10143 assert!(
10144 matches!(&err, FrankenError::FunctionError(message) if message == "second argument to nth_value must be a positive integer"),
10145 "unexpected error: {err:?}"
10146 );
10147 });
10148 }
10149
10150 #[test]
10151 fn window_nth_value_rejects_text_numeric_prefix_n() {
10152 asupersync::test_utils::run_test(|| async {
10153 let conn = Connection::open(":memory:").await.unwrap();
10154 let err = conn
10155 .query(
10156 "WITH t(x,n) AS (VALUES ('a','2x'),('b','2x')) \
10157 SELECT nth_value(x,n) OVER (\
10158 ORDER BY x ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING\
10159 ) FROM t;",
10160 )
10161 .await
10162 .expect_err("numeric-prefix nth_value offset should fail");
10163 assert!(
10164 matches!(&err, FrankenError::FunctionError(message) if message == "second argument to nth_value must be a positive integer"),
10165 "unexpected error: {err:?}"
10166 );
10167 });
10168 }
10169
10170 #[test]
10171 fn window_nth_value_rejects_blob_integer_n() {
10172 asupersync::test_utils::run_test(|| async {
10173 let conn = Connection::open(":memory:").await.unwrap();
10174 let err = conn
10175 .query(
10176 "WITH t(x,n) AS (VALUES ('a',x'32'),('b',x'32')) \
10177 SELECT nth_value(x,n) OVER (\
10178 ORDER BY x ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING\
10179 ) FROM t;",
10180 )
10181 .await
10182 .expect_err("blob nth_value offset should fail");
10183 assert!(
10184 matches!(&err, FrankenError::FunctionError(message) if message == "second argument to nth_value must be a positive integer"),
10185 "unexpected error: {err:?}"
10186 );
10187 });
10188 }
10189
10190 #[test]
10193 fn parity_cte_basic() {
10194 asupersync::test_utils::run_test(|| async {
10195 let conn = Connection::open(":memory:").await.unwrap();
10196 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
10197 conn.execute("INSERT INTO t VALUES(1);").await.unwrap();
10198 conn.execute("INSERT INTO t VALUES(2);").await.unwrap();
10199 conn.execute("INSERT INTO t VALUES(3);").await.unwrap();
10200 let rows = conn
10201 .query(
10202 "WITH doubled AS (SELECT x * 2 AS d FROM t) SELECT d FROM doubled ORDER BY d;",
10203 )
10204 .await
10205 .unwrap();
10206 let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
10207 assert_eq!(results, vec![2, 4, 6]);
10208 });
10209 }
10210
10211 #[test]
10214 fn parity_recursive_cte() {
10215 asupersync::test_utils::run_test(|| async {
10216 let conn = Connection::open(":memory:").await.unwrap();
10217 let rows = conn
10218 .query(
10219 "WITH RECURSIVE cnt(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM cnt WHERE x<5) \
10220 SELECT x FROM cnt;",
10221 )
10222 .await
10223 .unwrap();
10224 let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
10225 assert_eq!(results, vec![1, 2, 3, 4, 5]);
10226 });
10227 }
10228
10229 #[test]
10232 fn parity_having_count_and_sum() {
10233 asupersync::test_utils::run_test(|| async {
10234 let conn = Connection::open(":memory:").await.unwrap();
10235 conn.execute("CREATE TABLE sales(region TEXT, amount INTEGER);")
10236 .await
10237 .unwrap();
10238 conn.execute("INSERT INTO sales VALUES('East', 100);")
10239 .await
10240 .unwrap();
10241 conn.execute("INSERT INTO sales VALUES('East', 200);")
10242 .await
10243 .unwrap();
10244 conn.execute("INSERT INTO sales VALUES('West', 50);")
10245 .await
10246 .unwrap();
10247 conn.execute("INSERT INTO sales VALUES('West', 60);")
10248 .await
10249 .unwrap();
10250 conn.execute("INSERT INTO sales VALUES('West', 70);")
10251 .await
10252 .unwrap();
10253 let rows = conn
10255 .query("SELECT region, SUM(amount) FROM sales GROUP BY region HAVING COUNT(*) > 2;")
10256 .await
10257 .unwrap();
10258 let results: Vec<(String, i64)> = rows
10259 .iter()
10260 .map(|r| {
10261 let vals = row_values(r);
10262 (
10263 match &vals[0] {
10264 SqliteValue::Text(s) => s.to_string(),
10265 _ => panic!("expected text"),
10266 },
10267 vals[1].to_integer(),
10268 )
10269 })
10270 .collect();
10271 assert_eq!(results, vec![("West".into(), 180)]);
10272 });
10273 }
10274
10275 #[test]
10278 fn parity_update_with_exists_subquery() {
10279 asupersync::test_utils::run_test(|| async {
10280 let conn = Connection::open(":memory:").await.unwrap();
10281 conn.execute("CREATE TABLE products(pid INTEGER, name TEXT, active INTEGER);")
10282 .await
10283 .unwrap();
10284 conn.execute("INSERT INTO products VALUES(1, 'Widget', 1);")
10285 .await
10286 .unwrap();
10287 conn.execute("INSERT INTO products VALUES(2, 'Gadget', 1);")
10288 .await
10289 .unwrap();
10290 conn.execute("INSERT INTO products VALUES(3, 'Doohickey', 1);")
10291 .await
10292 .unwrap();
10293 conn.execute("CREATE TABLE discontinued(product_id INTEGER);")
10294 .await
10295 .unwrap();
10296 conn.execute("INSERT INTO discontinued VALUES(1);")
10297 .await
10298 .unwrap();
10299 conn.execute("INSERT INTO discontinued VALUES(3);")
10300 .await
10301 .unwrap();
10302 conn.execute(
10304 "UPDATE products SET active = 0 WHERE EXISTS \
10305 (SELECT 1 FROM discontinued WHERE discontinued.product_id = products.pid);",
10306 )
10307 .await
10308 .unwrap();
10309 let rows = conn
10310 .query("SELECT pid, active FROM products ORDER BY pid;")
10311 .await
10312 .unwrap();
10313 let results: Vec<(i64, i64)> = rows
10314 .iter()
10315 .map(|r| {
10316 let vals = row_values(r);
10317 (vals[0].to_integer(), vals[1].to_integer())
10318 })
10319 .collect();
10320 assert_eq!(results, vec![(1, 0), (2, 1), (3, 0)]);
10321 });
10322 }
10323
10324 #[test]
10325 fn probe_correlated_exists_select() {
10326 asupersync::test_utils::run_test(|| async {
10327 let conn = Connection::open(":memory:").await.unwrap();
10329 conn.execute("CREATE TABLE a(x INTEGER);").await.unwrap();
10330 conn.execute("INSERT INTO a VALUES(1);").await.unwrap();
10331 conn.execute("INSERT INTO a VALUES(2);").await.unwrap();
10332 conn.execute("INSERT INTO a VALUES(3);").await.unwrap();
10333 conn.execute("CREATE TABLE b(y INTEGER);").await.unwrap();
10334 conn.execute("INSERT INTO b VALUES(1);").await.unwrap();
10335 conn.execute("INSERT INTO b VALUES(3);").await.unwrap();
10336 let rows = conn
10337 .query("SELECT x FROM a WHERE EXISTS (SELECT 1 FROM b WHERE b.y = a.x) ORDER BY x;")
10338 .await
10339 .unwrap();
10340 let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
10341 assert_eq!(results, vec![1, 3]);
10342 });
10343 }
10344
10345 #[test]
10348 fn probe_order_by_expression() {
10349 asupersync::test_utils::run_test(|| async {
10350 let conn = Connection::open(":memory:").await.unwrap();
10351 conn.execute("CREATE TABLE t(x INTEGER, y TEXT);")
10352 .await
10353 .unwrap();
10354 conn.execute("INSERT INTO t VALUES(3, 'c');").await.unwrap();
10355 conn.execute("INSERT INTO t VALUES(1, 'a');").await.unwrap();
10356 conn.execute("INSERT INTO t VALUES(2, 'b');").await.unwrap();
10357 let rows = conn
10358 .query("SELECT y FROM t ORDER BY x * -1;")
10359 .await
10360 .unwrap();
10361 let results: Vec<String> = rows
10362 .iter()
10363 .map(|r| match &row_values(r)[0] {
10364 SqliteValue::Text(s) => s.to_string(),
10365 _ => panic!("expected text"),
10366 })
10367 .collect();
10368 assert_eq!(results, vec!["c", "b", "a"]);
10369 });
10370 }
10371
10372 #[test]
10373 fn probe_group_by_expression() {
10374 asupersync::test_utils::run_test(|| async {
10375 let conn = Connection::open(":memory:").await.unwrap();
10376 conn.execute("CREATE TABLE t(x INTEGER, v INTEGER);")
10377 .await
10378 .unwrap();
10379 conn.execute("INSERT INTO t VALUES(1, 10);").await.unwrap();
10380 conn.execute("INSERT INTO t VALUES(2, 20);").await.unwrap();
10381 conn.execute("INSERT INTO t VALUES(3, 30);").await.unwrap();
10382 conn.execute("INSERT INTO t VALUES(4, 40);").await.unwrap();
10383 let rows = conn
10384 .query("SELECT x % 2 AS grp, SUM(v) FROM t GROUP BY x % 2 ORDER BY grp;")
10385 .await
10386 .unwrap();
10387 let results: Vec<(i64, i64)> = rows
10388 .iter()
10389 .map(|r| {
10390 let vals = row_values(r);
10391 (vals[0].to_integer(), vals[1].to_integer())
10392 })
10393 .collect();
10394 assert_eq!(results, vec![(0, 60), (1, 40)]);
10395 });
10396 }
10397
10398 #[test]
10399 fn probe_having_with_expression() {
10400 asupersync::test_utils::run_test(|| async {
10401 let conn = Connection::open(":memory:").await.unwrap();
10402 conn.execute("CREATE TABLE t(cat TEXT, val INTEGER);")
10403 .await
10404 .unwrap();
10405 conn.execute("INSERT INTO t VALUES('a', 10);")
10406 .await
10407 .unwrap();
10408 conn.execute("INSERT INTO t VALUES('a', 20);")
10409 .await
10410 .unwrap();
10411 conn.execute("INSERT INTO t VALUES('b', 5);").await.unwrap();
10412 let rows = conn
10413 .query(
10414 "SELECT cat, SUM(val) AS s FROM t GROUP BY cat HAVING SUM(val) > 10 ORDER BY cat;",
10415 )
10416 .await
10417 .unwrap();
10418 let results: Vec<(String, i64)> = rows
10419 .iter()
10420 .map(|r| {
10421 let vals = row_values(r);
10422 let cat = match &vals[0] {
10423 SqliteValue::Text(s) => s.to_string(),
10424 _ => panic!("expected text"),
10425 };
10426 (cat, vals[1].to_integer())
10427 })
10428 .collect();
10429 assert_eq!(results, vec![("a".to_string(), 30)]);
10430 });
10431 }
10432
10433 #[test]
10434 fn probe_scalar_subquery_in_select() {
10435 asupersync::test_utils::run_test(|| async {
10436 let conn = Connection::open(":memory:").await.unwrap();
10437 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
10438 conn.execute("INSERT INTO t VALUES(1);").await.unwrap();
10439 conn.execute("INSERT INTO t VALUES(2);").await.unwrap();
10440 conn.execute("INSERT INTO t VALUES(3);").await.unwrap();
10441 let rows = conn
10442 .query("SELECT x, (SELECT MAX(x) FROM t) AS mx FROM t ORDER BY x;")
10443 .await
10444 .unwrap();
10445 let results: Vec<(i64, i64)> = rows
10446 .iter()
10447 .map(|r| {
10448 let vals = row_values(r);
10449 (vals[0].to_integer(), vals[1].to_integer())
10450 })
10451 .collect();
10452 assert_eq!(results, vec![(1, 3), (2, 3), (3, 3)]);
10453 });
10454 }
10455
10456 #[test]
10457 fn probe_nested_case_when() {
10458 asupersync::test_utils::run_test(|| async {
10459 let conn = Connection::open(":memory:").await.unwrap();
10460 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
10461 conn.execute("INSERT INTO t VALUES(1);").await.unwrap();
10462 conn.execute("INSERT INTO t VALUES(2);").await.unwrap();
10463 conn.execute("INSERT INTO t VALUES(3);").await.unwrap();
10464 let rows = conn
10465 .query(
10466 "SELECT CASE WHEN x < 2 THEN 'low' \
10467 WHEN x < 3 THEN 'mid' ELSE 'high' END AS label FROM t ORDER BY x;",
10468 )
10469 .await
10470 .unwrap();
10471 let results: Vec<String> = rows
10472 .iter()
10473 .map(|r| match &row_values(r)[0] {
10474 SqliteValue::Text(s) => s.to_string(),
10475 _ => panic!("expected text"),
10476 })
10477 .collect();
10478 assert_eq!(results, vec!["low", "mid", "high"]);
10479 });
10480 }
10481
10482 #[test]
10483 fn probe_coalesce_multi_arg() {
10484 asupersync::test_utils::run_test(|| async {
10485 let conn = Connection::open(":memory:").await.unwrap();
10486 let rows = conn
10487 .query("SELECT COALESCE(NULL, NULL, NULL, 42);")
10488 .await
10489 .unwrap();
10490 assert_eq!(row_values(&rows[0])[0].to_integer(), 42);
10491 });
10492 }
10493
10494 #[test]
10495 fn probe_nullif_function() {
10496 asupersync::test_utils::run_test(|| async {
10497 let conn = Connection::open(":memory:").await.unwrap();
10498 let rows = conn
10499 .query("SELECT NULLIF(5, 5), NULLIF(5, 3);")
10500 .await
10501 .unwrap();
10502 let vals = row_values(&rows[0]);
10503 assert_eq!(vals[0], SqliteValue::Null);
10504 assert_eq!(vals[1].to_integer(), 5);
10505 });
10506 }
10507
10508 #[test]
10509 fn probe_iif_function() {
10510 asupersync::test_utils::run_test(|| async {
10511 let conn = Connection::open(":memory:").await.unwrap();
10512 let rows = conn
10513 .query("SELECT IIF(1 > 0, 'yes', 'no'), IIF(1 < 0, 'yes', 'no');")
10514 .await
10515 .unwrap();
10516 let vals = row_values(&rows[0]);
10517 let a = match &vals[0] {
10518 SqliteValue::Text(s) => s.to_string(),
10519 _ => panic!("expected text"),
10520 };
10521 let b = match &vals[1] {
10522 SqliteValue::Text(s) => s.to_string(),
10523 _ => panic!("expected text"),
10524 };
10525 assert_eq!(a, "yes");
10526 assert_eq!(b, "no");
10527 });
10528 }
10529
10530 #[test]
10531 fn probe_like_escape() {
10532 asupersync::test_utils::run_test(|| async {
10533 let conn = Connection::open(":memory:").await.unwrap();
10534 conn.execute("CREATE TABLE t(s TEXT);").await.unwrap();
10535 conn.execute("INSERT INTO t VALUES('100% done');")
10536 .await
10537 .unwrap();
10538 conn.execute("INSERT INTO t VALUES('50 percent');")
10539 .await
10540 .unwrap();
10541 let rows = conn
10542 .query("SELECT s FROM t WHERE s LIKE '%!%%' ESCAPE '!';")
10543 .await
10544 .unwrap();
10545 assert_eq!(rows.len(), 1);
10546 let val = match &row_values(&rows[0])[0] {
10547 SqliteValue::Text(s) => s.to_string(),
10548 _ => panic!("expected text"),
10549 };
10550 assert_eq!(val, "100% done");
10551 });
10552 }
10553
10554 #[test]
10555 fn probe_between_with_expressions() {
10556 asupersync::test_utils::run_test(|| async {
10557 let conn = Connection::open(":memory:").await.unwrap();
10558 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
10559 conn.execute("INSERT INTO t VALUES(1);").await.unwrap();
10560 conn.execute("INSERT INTO t VALUES(5);").await.unwrap();
10561 conn.execute("INSERT INTO t VALUES(10);").await.unwrap();
10562 let rows = conn
10563 .query("SELECT x FROM t WHERE x BETWEEN 2 + 1 AND 4 * 2 ORDER BY x;")
10564 .await
10565 .unwrap();
10566 let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
10567 assert_eq!(results, vec![5]);
10568 });
10569 }
10570
10571 #[test]
10572 fn probe_distinct_with_expression() {
10573 asupersync::test_utils::run_test(|| async {
10574 let conn = Connection::open(":memory:").await.unwrap();
10575 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
10576 conn.execute("INSERT INTO t VALUES(1);").await.unwrap();
10577 conn.execute("INSERT INTO t VALUES(2);").await.unwrap();
10578 conn.execute("INSERT INTO t VALUES(3);").await.unwrap();
10579 conn.execute("INSERT INTO t VALUES(4);").await.unwrap();
10580 let rows = conn
10581 .query("SELECT DISTINCT x % 2 AS mod2 FROM t ORDER BY mod2;")
10582 .await
10583 .unwrap();
10584 let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
10585 assert_eq!(results, vec![0, 1]);
10586 });
10587 }
10588
10589 #[test]
10590 fn probe_insert_or_ignore_keeps_existing() {
10591 asupersync::test_utils::run_test(|| async {
10592 let conn = Connection::open(":memory:").await.unwrap();
10593 conn.execute("CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT);")
10594 .await
10595 .unwrap();
10596 conn.execute("INSERT INTO t VALUES(1, 'first');")
10597 .await
10598 .unwrap();
10599 conn.execute("INSERT OR IGNORE INTO t VALUES(1, 'second');")
10600 .await
10601 .unwrap();
10602 let rows = conn.query("SELECT v FROM t WHERE id = 1;").await.unwrap();
10603 let val = match &row_values(&rows[0])[0] {
10604 SqliteValue::Text(s) => s.to_string(),
10605 _ => panic!("expected text"),
10606 };
10607 assert_eq!(val, "first");
10608 });
10609 }
10610
10611 #[test]
10612 fn probe_insert_or_replace_overwrites() {
10613 asupersync::test_utils::run_test(|| async {
10614 let conn = Connection::open(":memory:").await.unwrap();
10615 conn.execute("CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT);")
10616 .await
10617 .unwrap();
10618 conn.execute("INSERT INTO t VALUES(1, 'first');")
10619 .await
10620 .unwrap();
10621 conn.execute("INSERT OR REPLACE INTO t VALUES(1, 'second');")
10622 .await
10623 .unwrap();
10624 let rows = conn.query("SELECT v FROM t WHERE id = 1;").await.unwrap();
10625 let val = match &row_values(&rows[0])[0] {
10626 SqliteValue::Text(s) => s.to_string(),
10627 _ => panic!("expected text"),
10628 };
10629 assert_eq!(val, "second");
10630 });
10631 }
10632
10633 #[test]
10634 fn probe_delete_with_correlated_exists() {
10635 asupersync::test_utils::run_test(|| async {
10636 let conn = Connection::open(":memory:").await.unwrap();
10637 conn.execute("CREATE TABLE items(id INTEGER, active INTEGER);")
10638 .await
10639 .unwrap();
10640 conn.execute("INSERT INTO items VALUES(1, 1);")
10641 .await
10642 .unwrap();
10643 conn.execute("INSERT INTO items VALUES(2, 1);")
10644 .await
10645 .unwrap();
10646 conn.execute("INSERT INTO items VALUES(3, 1);")
10647 .await
10648 .unwrap();
10649 conn.execute("CREATE TABLE retired(item_id INTEGER);")
10650 .await
10651 .unwrap();
10652 conn.execute("INSERT INTO retired VALUES(2);")
10653 .await
10654 .unwrap();
10655 conn.execute(
10656 "DELETE FROM items WHERE EXISTS \
10657 (SELECT 1 FROM retired WHERE retired.item_id = items.id);",
10658 )
10659 .await
10660 .unwrap();
10661 let rows = conn
10662 .query("SELECT id FROM items ORDER BY id;")
10663 .await
10664 .unwrap();
10665 let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
10666 assert_eq!(results, vec![1, 3]);
10667 });
10668 }
10669
10670 #[test]
10671 fn probe_aggregate_in_order_by() {
10672 asupersync::test_utils::run_test(|| async {
10673 let conn = Connection::open(":memory:").await.unwrap();
10674 conn.execute("CREATE TABLE t(cat TEXT, val INTEGER);")
10675 .await
10676 .unwrap();
10677 conn.execute("INSERT INTO t VALUES('b', 10);")
10678 .await
10679 .unwrap();
10680 conn.execute("INSERT INTO t VALUES('a', 30);")
10681 .await
10682 .unwrap();
10683 conn.execute("INSERT INTO t VALUES('c', 20);")
10684 .await
10685 .unwrap();
10686 let rows = conn
10687 .query("SELECT cat, SUM(val) AS s FROM t GROUP BY cat ORDER BY SUM(val) DESC;")
10688 .await
10689 .unwrap();
10690 let results: Vec<(String, i64)> = rows
10691 .iter()
10692 .map(|r| {
10693 let vals = row_values(r);
10694 let cat = match &vals[0] {
10695 SqliteValue::Text(s) => s.to_string(),
10696 _ => panic!("expected text"),
10697 };
10698 (cat, vals[1].to_integer())
10699 })
10700 .collect();
10701 assert_eq!(
10702 results,
10703 vec![
10704 ("a".to_string(), 30),
10705 ("c".to_string(), 20),
10706 ("b".to_string(), 10)
10707 ]
10708 );
10709 });
10710 }
10711
10712 #[test]
10713 fn probe_subquery_in_from() {
10714 asupersync::test_utils::run_test(|| async {
10715 let conn = Connection::open(":memory:").await.unwrap();
10716 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
10717 conn.execute("INSERT INTO t VALUES(1);").await.unwrap();
10718 conn.execute("INSERT INTO t VALUES(2);").await.unwrap();
10719 conn.execute("INSERT INTO t VALUES(3);").await.unwrap();
10720 let rows = conn
10721 .query("SELECT sub.doubled FROM (SELECT x * 2 AS doubled FROM t) AS sub ORDER BY sub.doubled;")
10722 .await
10723 .unwrap();
10724 let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
10725 assert_eq!(results, vec![2, 4, 6]);
10726 });
10727 }
10728
10729 #[test]
10730 fn probe_multi_column_order_by_mixed() {
10731 asupersync::test_utils::run_test(|| async {
10732 let conn = Connection::open(":memory:").await.unwrap();
10733 conn.execute("CREATE TABLE t(a INTEGER, b INTEGER);")
10734 .await
10735 .unwrap();
10736 conn.execute("INSERT INTO t VALUES(1, 3);").await.unwrap();
10737 conn.execute("INSERT INTO t VALUES(1, 1);").await.unwrap();
10738 conn.execute("INSERT INTO t VALUES(2, 2);").await.unwrap();
10739 conn.execute("INSERT INTO t VALUES(2, 4);").await.unwrap();
10740 let rows = conn
10741 .query("SELECT a, b FROM t ORDER BY a ASC, b DESC;")
10742 .await
10743 .unwrap();
10744 let results: Vec<(i64, i64)> = rows
10745 .iter()
10746 .map(|r| {
10747 let vals = row_values(r);
10748 (vals[0].to_integer(), vals[1].to_integer())
10749 })
10750 .collect();
10751 assert_eq!(results, vec![(1, 3), (1, 1), (2, 4), (2, 2)]);
10752 });
10753 }
10754
10755 #[test]
10756 fn probe_null_handling_order_by() {
10757 asupersync::test_utils::run_test(|| async {
10758 let conn = Connection::open(":memory:").await.unwrap();
10759 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
10760 conn.execute("INSERT INTO t VALUES(3);").await.unwrap();
10761 conn.execute("INSERT INTO t VALUES(NULL);").await.unwrap();
10762 conn.execute("INSERT INTO t VALUES(1);").await.unwrap();
10763 let rows = conn.query("SELECT x FROM t ORDER BY x ASC;").await.unwrap();
10765 let results: Vec<SqliteValue> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
10766 assert_eq!(results[0], SqliteValue::Null);
10767 assert_eq!(results[1].to_integer(), 1);
10768 assert_eq!(results[2].to_integer(), 3);
10769 });
10770 }
10771
10772 #[test]
10773 fn probe_count_distinct() {
10774 asupersync::test_utils::run_test(|| async {
10775 let conn = Connection::open(":memory:").await.unwrap();
10776 conn.execute("CREATE TABLE t(x INTEGER);").await.unwrap();
10777 conn.execute("INSERT INTO t VALUES(1);").await.unwrap();
10778 conn.execute("INSERT INTO t VALUES(2);").await.unwrap();
10779 conn.execute("INSERT INTO t VALUES(1);").await.unwrap();
10780 conn.execute("INSERT INTO t VALUES(NULL);").await.unwrap();
10781 let rows = conn
10782 .query("SELECT COUNT(DISTINCT x) FROM t;")
10783 .await
10784 .unwrap();
10785 assert_eq!(row_values(&rows[0])[0].to_integer(), 2);
10787 });
10788 }
10789
10790 #[test]
10791 fn probe_cast_in_where() {
10792 asupersync::test_utils::run_test(|| async {
10793 let conn = Connection::open(":memory:").await.unwrap();
10794 conn.execute("CREATE TABLE t(x TEXT);").await.unwrap();
10795 conn.execute("INSERT INTO t VALUES('123');").await.unwrap();
10796 conn.execute("INSERT INTO t VALUES('456');").await.unwrap();
10797 conn.execute("INSERT INTO t VALUES('abc');").await.unwrap();
10798 let rows = conn
10799 .query("SELECT x FROM t WHERE CAST(x AS INTEGER) > 200;")
10800 .await
10801 .unwrap();
10802 assert_eq!(rows.len(), 1);
10803 let val = match &row_values(&rows[0])[0] {
10804 SqliteValue::Text(s) => s.to_string(),
10805 _ => panic!("expected text"),
10806 };
10807 assert_eq!(val, "456");
10808 });
10809 }
10810
10811 #[test]
10812 fn probe_union_all_three_way() {
10813 asupersync::test_utils::run_test(|| async {
10814 let conn = Connection::open(":memory:").await.unwrap();
10815 let rows = conn
10816 .query("SELECT 1 AS v UNION ALL SELECT 2 UNION ALL SELECT 1;")
10817 .await
10818 .unwrap();
10819 let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
10820 assert_eq!(results, vec![1, 2, 1]);
10821 });
10822 }
10823
10824 #[test]
10825 fn probe_union_dedup_three_way() {
10826 asupersync::test_utils::run_test(|| async {
10827 let conn = Connection::open(":memory:").await.unwrap();
10828 let rows = conn
10829 .query("SELECT 1 AS v UNION SELECT 2 UNION SELECT 1 ORDER BY v;")
10830 .await
10831 .unwrap();
10832 let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
10833 assert_eq!(results, vec![1, 2]);
10834 });
10835 }
10836
10837 #[test]
10838 fn probe_except_compound() {
10839 asupersync::test_utils::run_test(|| async {
10840 let conn = Connection::open(":memory:").await.unwrap();
10841 let rows = conn
10842 .query("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 EXCEPT SELECT 2;")
10843 .await
10844 .unwrap();
10845 let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
10846 assert!(results.contains(&1));
10848 assert!(results.contains(&3));
10849 assert!(!results.contains(&2));
10850 });
10851 }
10852
10853 #[test]
10854 fn probe_intersect_compound() {
10855 asupersync::test_utils::run_test(|| async {
10856 let conn = Connection::open(":memory:").await.unwrap();
10857 let rows = conn
10858 .query(
10859 "SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 \
10860 INTERSECT SELECT 2 UNION ALL SELECT 3;",
10861 )
10862 .await
10863 .unwrap();
10864 let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
10865 assert!(results.contains(&2) || results.contains(&3));
10866 });
10867 }
10868
10869 #[test]
10874 fn conformance_024_substr() {
10875 asupersync::test_utils::run_test(|| async {
10876 let conn = Connection::open(":memory:").await.unwrap();
10877 let r = conn.query("SELECT substr('hello world', 7)").await.unwrap();
10878 assert_eq!(row_values(&r[0])[0].to_text(), "world");
10879 let r = conn
10880 .query("SELECT substr('hello world', 1, 5)")
10881 .await
10882 .unwrap();
10883 assert_eq!(row_values(&r[0])[0].to_text(), "hello");
10884 let r = conn.query("SELECT substr('hello', -3)").await.unwrap();
10885 assert_eq!(row_values(&r[0])[0].to_text(), "llo");
10886 });
10887 }
10888
10889 #[test]
10890 fn conformance_024_replace() {
10891 asupersync::test_utils::run_test(|| async {
10892 let conn = Connection::open(":memory:").await.unwrap();
10893 let r = conn
10894 .query("SELECT replace('hello world', 'world', 'rust')")
10895 .await
10896 .unwrap();
10897 assert_eq!(row_values(&r[0])[0].to_text(), "hello rust");
10898 let r = conn
10899 .query("SELECT replace('aaa', 'a', 'bb')")
10900 .await
10901 .unwrap();
10902 assert_eq!(row_values(&r[0])[0].to_text(), "bbbbbb");
10903 });
10904 }
10905
10906 #[test]
10907 fn conformance_024_trim() {
10908 asupersync::test_utils::run_test(|| async {
10909 let conn = Connection::open(":memory:").await.unwrap();
10910 let r = conn.query("SELECT trim(' hello ')").await.unwrap();
10911 assert_eq!(row_values(&r[0])[0].to_text(), "hello");
10912 let r = conn.query("SELECT ltrim(' hello ')").await.unwrap();
10913 assert_eq!(row_values(&r[0])[0].to_text(), "hello ");
10914 let r = conn.query("SELECT rtrim(' hello ')").await.unwrap();
10915 assert_eq!(row_values(&r[0])[0].to_text(), " hello");
10916 });
10917 }
10918
10919 #[test]
10920 fn conformance_024_instr() {
10921 asupersync::test_utils::run_test(|| async {
10922 let conn = Connection::open(":memory:").await.unwrap();
10923 let r = conn
10924 .query("SELECT instr('hello world', 'world')")
10925 .await
10926 .unwrap();
10927 assert_eq!(row_values(&r[0])[0].to_text(), "7");
10928 let r = conn.query("SELECT instr('hello', 'xyz')").await.unwrap();
10929 assert_eq!(row_values(&r[0])[0].to_text(), "0");
10930 let r = conn.query("SELECT instr('hello', '')").await.unwrap();
10931 assert_eq!(row_values(&r[0])[0].to_text(), "1");
10932 });
10933 }
10934
10935 #[test]
10936 fn conformance_024_hex_zeroblob() {
10937 asupersync::test_utils::run_test(|| async {
10938 let conn = Connection::open(":memory:").await.unwrap();
10939 let r = conn.query("SELECT hex(zeroblob(4))").await.unwrap();
10940 assert_eq!(row_values(&r[0])[0].to_text(), "00000000");
10941 let r = conn.query("SELECT typeof(zeroblob(4))").await.unwrap();
10942 assert_eq!(row_values(&r[0])[0].to_text(), "blob");
10943 });
10944 }
10945
10946 #[test]
10947 fn conformance_024_char_unicode() {
10948 asupersync::test_utils::run_test(|| async {
10949 let conn = Connection::open(":memory:").await.unwrap();
10950 let r = conn.query("SELECT char(65, 66, 67)").await.unwrap();
10951 assert_eq!(row_values(&r[0])[0].to_text(), "ABC");
10952 let r = conn.query("SELECT unicode('A')").await.unwrap();
10953 assert_eq!(row_values(&r[0])[0].to_text(), "65");
10954 });
10955 }
10956
10957 #[test]
10962 fn conformance_025_between() {
10963 asupersync::test_utils::run_test(|| async {
10964 let conn = Connection::open(":memory:").await.unwrap();
10965 let r = conn.query("SELECT 5 BETWEEN 1 AND 10").await.unwrap();
10966 assert_eq!(row_values(&r[0])[0].to_text(), "1");
10967 let r = conn.query("SELECT 15 BETWEEN 1 AND 10").await.unwrap();
10968 assert_eq!(row_values(&r[0])[0].to_text(), "0");
10969 let r = conn.query("SELECT 5 NOT BETWEEN 1 AND 10").await.unwrap();
10970 assert_eq!(row_values(&r[0])[0].to_text(), "0");
10971 });
10972 }
10973
10974 #[test]
10975 fn conformance_025_in_operator() {
10976 asupersync::test_utils::run_test(|| async {
10977 let conn = Connection::open(":memory:").await.unwrap();
10978 let r = conn.query("SELECT 3 IN (1, 2, 3, 4)").await.unwrap();
10979 assert_eq!(row_values(&r[0])[0].to_text(), "1");
10980 let r = conn.query("SELECT 5 IN (1, 2, 3, 4)").await.unwrap();
10981 assert_eq!(row_values(&r[0])[0].to_text(), "0");
10982 let r = conn.query("SELECT 3 NOT IN (1, 2, 3, 4)").await.unwrap();
10983 assert_eq!(row_values(&r[0])[0].to_text(), "0");
10984 });
10985 }
10986
10987 #[test]
10988 fn conformance_025_like_glob() {
10989 asupersync::test_utils::run_test(|| async {
10990 let conn = Connection::open(":memory:").await.unwrap();
10991 let r = conn.query("SELECT 'hello' LIKE 'hel%'").await.unwrap();
10992 assert_eq!(row_values(&r[0])[0].to_text(), "1");
10993 let r = conn.query("SELECT 'hello' LIKE 'HEL%'").await.unwrap();
10994 assert_eq!(row_values(&r[0])[0].to_text(), "1");
10995 let r = conn.query("SELECT 'hello' GLOB 'hel*'").await.unwrap();
10996 assert_eq!(row_values(&r[0])[0].to_text(), "1");
10997 let r = conn.query("SELECT 'hello' GLOB 'HEL*'").await.unwrap();
10998 assert_eq!(row_values(&r[0])[0].to_text(), "0");
10999 });
11000 }
11001
11002 #[test]
11003 fn conformance_025_coalesce_nullif_iif() {
11004 asupersync::test_utils::run_test(|| async {
11005 let conn = Connection::open(":memory:").await.unwrap();
11006 let r = conn
11007 .query("SELECT coalesce(NULL, NULL, 'found')")
11008 .await
11009 .unwrap();
11010 assert_eq!(row_values(&r[0])[0].to_text(), "found");
11011 let r = conn.query("SELECT nullif(5, 5)").await.unwrap();
11012 assert!(row_values(&r[0])[0].is_null());
11013 let r = conn.query("SELECT nullif(5, 3)").await.unwrap();
11014 assert_eq!(row_values(&r[0])[0].to_text(), "5");
11015 let r = conn.query("SELECT iif(1, 'yes', 'no')").await.unwrap();
11016 assert_eq!(row_values(&r[0])[0].to_text(), "yes");
11017 let r = conn.query("SELECT iif(0, 'yes', 'no')").await.unwrap();
11018 assert_eq!(row_values(&r[0])[0].to_text(), "no");
11019 });
11020 }
11021
11022 #[test]
11023 fn conformance_025_bitwise() {
11024 asupersync::test_utils::run_test(|| async {
11025 let conn = Connection::open(":memory:").await.unwrap();
11026 let r = conn.query("SELECT 6 & 3").await.unwrap();
11027 assert_eq!(row_values(&r[0])[0].to_text(), "2");
11028 let r = conn.query("SELECT 6 | 3").await.unwrap();
11029 assert_eq!(row_values(&r[0])[0].to_text(), "7");
11030 let r = conn.query("SELECT ~0").await.unwrap();
11031 assert_eq!(row_values(&r[0])[0].to_text(), "-1");
11032 let r = conn.query("SELECT 1 << 4").await.unwrap();
11033 assert_eq!(row_values(&r[0])[0].to_text(), "16");
11034 let r = conn.query("SELECT 16 >> 2").await.unwrap();
11035 assert_eq!(row_values(&r[0])[0].to_text(), "4");
11036 });
11037 }
11038
11039 #[test]
11040 fn conformance_025_cast() {
11041 asupersync::test_utils::run_test(|| async {
11042 let conn = Connection::open(":memory:").await.unwrap();
11043 let r = conn.query("SELECT CAST('123' AS INTEGER)").await.unwrap();
11044 assert_eq!(row_values(&r[0])[0].to_text(), "123");
11045 let r = conn
11046 .query("SELECT typeof(CAST(123 AS TEXT))")
11047 .await
11048 .unwrap();
11049 assert_eq!(row_values(&r[0])[0].to_text(), "text");
11050 let r = conn.query("SELECT CAST(3.14 AS INTEGER)").await.unwrap();
11051 assert_eq!(row_values(&r[0])[0].to_text(), "3");
11052 });
11053 }
11054
11055 #[test]
11056 fn conformance_025_unary_operators() {
11057 asupersync::test_utils::run_test(|| async {
11058 let conn = Connection::open(":memory:").await.unwrap();
11059 let r = conn.query("SELECT -(-5)").await.unwrap();
11060 assert_eq!(row_values(&r[0])[0].to_text(), "5");
11061 let r = conn.query("SELECT +42").await.unwrap();
11062 assert_eq!(row_values(&r[0])[0].to_text(), "42");
11063 let r = conn.query("SELECT NOT 0").await.unwrap();
11064 assert_eq!(row_values(&r[0])[0].to_text(), "1");
11065 let r = conn.query("SELECT NOT 1").await.unwrap();
11066 assert_eq!(row_values(&r[0])[0].to_text(), "0");
11067 });
11068 }
11069
11070 #[test]
11071 fn conformance_025_is_null() {
11072 asupersync::test_utils::run_test(|| async {
11073 let conn = Connection::open(":memory:").await.unwrap();
11074 let r = conn.query("SELECT NULL IS NULL").await.unwrap();
11075 assert_eq!(row_values(&r[0])[0].to_text(), "1");
11076 let r = conn.query("SELECT 5 IS NOT NULL").await.unwrap();
11077 assert_eq!(row_values(&r[0])[0].to_text(), "1");
11078 let r = conn.query("SELECT NULL IS NOT NULL").await.unwrap();
11079 assert_eq!(row_values(&r[0])[0].to_text(), "0");
11080 });
11081 }
11082
11083 #[test]
11088 fn conformance_026_scalar_subquery() {
11089 asupersync::test_utils::run_test(|| async {
11090 let conn = Connection::open(":memory:").await.unwrap();
11091 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val INTEGER)")
11092 .await
11093 .unwrap();
11094 conn.execute("INSERT INTO t1 VALUES (1, 10), (2, 20), (3, 30)")
11095 .await
11096 .unwrap();
11097 let r = conn
11098 .query("SELECT (SELECT MAX(val) FROM t1)")
11099 .await
11100 .unwrap();
11101 assert_eq!(row_values(&r[0])[0].to_text(), "30");
11102 let r = conn
11103 .query("SELECT (SELECT COUNT(*) FROM t1)")
11104 .await
11105 .unwrap();
11106 assert_eq!(row_values(&r[0])[0].to_text(), "3");
11107 });
11108 }
11109
11110 #[test]
11111 fn conformance_026_exists_subquery() {
11112 asupersync::test_utils::run_test(|| async {
11113 let conn = Connection::open(":memory:").await.unwrap();
11114 conn.execute("CREATE TABLE items(id INTEGER PRIMARY KEY, name TEXT)")
11115 .await
11116 .unwrap();
11117 conn.execute("INSERT INTO items VALUES (1, 'apple'), (2, 'banana')")
11118 .await
11119 .unwrap();
11120 let r = conn
11121 .query("SELECT EXISTS(SELECT 1 FROM items WHERE name = 'apple')")
11122 .await
11123 .unwrap();
11124 assert_eq!(row_values(&r[0])[0].to_text(), "1");
11125 let r = conn
11126 .query("SELECT EXISTS(SELECT 1 FROM items WHERE name = 'cherry')")
11127 .await
11128 .unwrap();
11129 assert_eq!(row_values(&r[0])[0].to_text(), "0");
11130 });
11131 }
11132
11133 #[test]
11134 fn conformance_026_cte_basic() {
11135 asupersync::test_utils::run_test(|| async {
11136 let conn = Connection::open(":memory:").await.unwrap();
11137 conn.execute(
11138 "CREATE TABLE employees(id INTEGER PRIMARY KEY, name TEXT, dept TEXT, salary INTEGER)",
11139 )
11140 .await
11141 .unwrap();
11142 conn.execute(
11143 "INSERT INTO employees VALUES \
11144 (1, 'Alice', 'eng', 100000), \
11145 (2, 'Bob', 'eng', 95000), \
11146 (3, 'Charlie', 'sales', 80000)",
11147 )
11148 .await
11149 .unwrap();
11150 let r = conn
11151 .query(
11152 "WITH eng AS (SELECT * FROM employees WHERE dept = 'eng') \
11153 SELECT name FROM eng ORDER BY name",
11154 )
11155 .await
11156 .unwrap();
11157 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
11158 assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
11159 assert_eq!(r.len(), 2);
11160 });
11161 }
11162
11163 #[test]
11164 fn conformance_026_cte_explicit_columns() {
11165 asupersync::test_utils::run_test(|| async {
11166 let conn = Connection::open(":memory:").await.unwrap();
11167 conn.execute("CREATE TABLE nums(n INTEGER)").await.unwrap();
11168 conn.execute("INSERT INTO nums VALUES (10), (20), (30)")
11169 .await
11170 .unwrap();
11171 let r = conn
11172 .query(
11173 "WITH doubled(val) AS (SELECT n * 2 FROM nums) \
11174 SELECT val FROM doubled ORDER BY val",
11175 )
11176 .await
11177 .unwrap();
11178 assert_eq!(row_values(&r[0])[0].to_text(), "20");
11179 assert_eq!(row_values(&r[1])[0].to_text(), "40");
11180 assert_eq!(row_values(&r[2])[0].to_text(), "60");
11181 });
11182 }
11183
11184 #[test]
11185 fn conformance_026_recursive_cte() {
11186 asupersync::test_utils::run_test(|| async {
11187 let conn = Connection::open(":memory:").await.unwrap();
11188 let r = conn
11189 .query(
11190 "WITH RECURSIVE cnt(x) AS (\
11191 SELECT 1 UNION ALL SELECT x + 1 FROM cnt WHERE x < 5\
11192 ) SELECT x FROM cnt",
11193 )
11194 .await
11195 .unwrap();
11196 let vals: Vec<String> = r.iter().map(|row| row_values(row)[0].to_text()).collect();
11197 assert_eq!(vals, ["1", "2", "3", "4", "5"]);
11198 });
11199 }
11200
11201 #[test]
11202 fn conformance_026_derived_table() {
11203 asupersync::test_utils::run_test(|| async {
11204 let conn = Connection::open(":memory:").await.unwrap();
11205 conn.execute("CREATE TABLE scores(student TEXT, score INTEGER)")
11206 .await
11207 .unwrap();
11208 conn.execute(
11209 "INSERT INTO scores VALUES \
11210 ('Alice', 90), ('Alice', 85), ('Bob', 70), ('Bob', 80)",
11211 )
11212 .await
11213 .unwrap();
11214 let r = conn
11215 .query(
11216 "SELECT student, avg_score FROM \
11217 (SELECT student, AVG(score) as avg_score \
11218 FROM scores GROUP BY student) ORDER BY student",
11219 )
11220 .await
11221 .unwrap();
11222 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
11223 assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
11224 });
11225 }
11226
11227 #[test]
11232
11233 fn conformance_027_row_number() {
11234 asupersync::test_utils::run_test(|| async {
11235 let conn = Connection::open(":memory:").await.unwrap();
11236 conn.execute("CREATE TABLE sales(id INTEGER PRIMARY KEY, region TEXT, amount REAL)")
11237 .await
11238 .unwrap();
11239 conn.execute(
11240 "INSERT INTO sales VALUES \
11241 (1, 'North', 100.0), (2, 'South', 200.0), \
11242 (3, 'North', 150.0), (4, 'South', 175.0)",
11243 )
11244 .await
11245 .unwrap();
11246 let r = conn
11247 .query(
11248 "SELECT region, amount, \
11249 ROW_NUMBER() OVER (ORDER BY amount DESC) as rn \
11250 FROM sales",
11251 )
11252 .await
11253 .unwrap();
11254 assert_eq!(row_values(&r[0])[2].to_text(), "1");
11255 });
11256 }
11257
11258 #[test]
11259
11260 fn conformance_027_rank_dense_rank() {
11261 asupersync::test_utils::run_test(|| async {
11262 let conn = Connection::open(":memory:").await.unwrap();
11263 conn.execute("CREATE TABLE scores(id INTEGER PRIMARY KEY, name TEXT, score INTEGER)")
11264 .await
11265 .unwrap();
11266 conn.execute(
11267 "INSERT INTO scores VALUES \
11268 (1, 'A', 100), (2, 'B', 100), (3, 'C', 90), (4, 'D', 80)",
11269 )
11270 .await
11271 .unwrap();
11272 let r = conn
11273 .query(
11274 "SELECT name, RANK() OVER (ORDER BY score DESC) as rnk \
11275 FROM scores",
11276 )
11277 .await
11278 .unwrap();
11279 assert_eq!(row_values(&r[0])[1].to_text(), "1");
11280 assert_eq!(row_values(&r[1])[1].to_text(), "1");
11281 assert_eq!(row_values(&r[2])[1].to_text(), "3");
11282 });
11283 }
11284
11285 #[test]
11286
11287 fn conformance_027_sum_over() {
11288 asupersync::test_utils::run_test(|| async {
11289 let conn = Connection::open(":memory:").await.unwrap();
11290 conn.execute("CREATE TABLE txns(id INTEGER PRIMARY KEY, amount REAL)")
11291 .await
11292 .unwrap();
11293 conn.execute("INSERT INTO txns VALUES (1, 10.0), (2, 20.0), (3, 30.0)")
11294 .await
11295 .unwrap();
11296 let r = conn
11297 .query(
11298 "SELECT id, SUM(amount) OVER (ORDER BY id) as running \
11299 FROM txns",
11300 )
11301 .await
11302 .unwrap();
11303 assert_eq!(row_values(&r[0])[1].to_text(), "10.0");
11304 assert_eq!(row_values(&r[1])[1].to_text(), "30.0");
11305 assert_eq!(row_values(&r[2])[1].to_text(), "60.0");
11306 });
11307 }
11308
11309 #[test]
11310
11311 fn conformance_027_lag_lead() {
11312 asupersync::test_utils::run_test(|| async {
11313 let conn = Connection::open(":memory:").await.unwrap();
11314 conn.execute("CREATE TABLE seq(id INTEGER PRIMARY KEY, val TEXT)")
11315 .await
11316 .unwrap();
11317 conn.execute("INSERT INTO seq VALUES (1, 'a'), (2, 'b'), (3, 'c')")
11318 .await
11319 .unwrap();
11320 let r = conn
11321 .query(
11322 "SELECT val, LAG(val) OVER (ORDER BY id) as prev \
11323 FROM seq",
11324 )
11325 .await
11326 .unwrap();
11327 assert!(row_values(&r[0])[1].is_null());
11328 assert_eq!(row_values(&r[1])[1].to_text(), "a");
11329 });
11330 }
11331
11332 #[test]
11337 fn conformance_028_create_select_drop_view() {
11338 asupersync::test_utils::run_test(|| async {
11339 let conn = Connection::open(":memory:").await.unwrap();
11340 conn.execute(
11341 "CREATE TABLE products(\
11342 id INTEGER PRIMARY KEY, name TEXT, price REAL, category TEXT)",
11343 )
11344 .await
11345 .unwrap();
11346 conn.execute(
11347 "INSERT INTO products VALUES \
11348 (1, 'Widget', 9.99, 'gadgets'), \
11349 (2, 'Gizmo', 24.99, 'gadgets'), \
11350 (3, 'Doohickey', 4.99, 'tools')",
11351 )
11352 .await
11353 .unwrap();
11354 conn.execute("CREATE VIEW expensive AS SELECT * FROM products WHERE price > 10.0")
11355 .await
11356 .unwrap();
11357 let r = conn
11358 .query("SELECT name FROM expensive ORDER BY name")
11359 .await
11360 .unwrap();
11361 assert_eq!(row_values(&r[0])[0].to_text(), "Gizmo");
11362 assert_eq!(r.len(), 1);
11363 conn.execute("DROP VIEW expensive").await.unwrap();
11364 });
11365 }
11366
11367 #[test]
11368 fn conformance_028_view_with_aggregate() {
11369 asupersync::test_utils::run_test(|| async {
11370 let conn = Connection::open(":memory:").await.unwrap();
11371 conn.execute(
11372 "CREATE TABLE products(\
11373 id INTEGER PRIMARY KEY, name TEXT, price REAL, category TEXT)",
11374 )
11375 .await
11376 .unwrap();
11377 conn.execute(
11378 "INSERT INTO products VALUES \
11379 (1, 'Widget', 9.99, 'gadgets'), \
11380 (2, 'Gizmo', 24.99, 'gadgets'), \
11381 (3, 'Doohickey', 4.99, 'tools'), \
11382 (4, 'Thingamajig', 14.99, 'tools'), \
11383 (5, 'Whatchamacallit', 49.99, 'gadgets')",
11384 )
11385 .await
11386 .unwrap();
11387 conn.execute(
11388 "CREATE VIEW category_stats AS \
11389 SELECT category, COUNT(*) as cnt \
11390 FROM products GROUP BY category",
11391 )
11392 .await
11393 .unwrap();
11394 let r = conn
11395 .query("SELECT category, cnt FROM category_stats ORDER BY category")
11396 .await
11397 .unwrap();
11398 assert_eq!(row_values(&r[0])[0].to_text(), "gadgets");
11399 assert_eq!(row_values(&r[0])[1].to_text(), "3");
11400 assert_eq!(row_values(&r[1])[0].to_text(), "tools");
11401 assert_eq!(row_values(&r[1])[1].to_text(), "2");
11402 });
11403 }
11404
11405 #[test]
11406 fn conformance_028_view_with_join() {
11407 asupersync::test_utils::run_test(|| async {
11408 let conn = Connection::open(":memory:").await.unwrap();
11409 conn.execute(
11410 "CREATE TABLE products(\
11411 id INTEGER PRIMARY KEY, name TEXT, price REAL)",
11412 )
11413 .await
11414 .unwrap();
11415 conn.execute(
11416 "INSERT INTO products VALUES \
11417 (1, 'Widget', 9.99), (2, 'Gizmo', 24.99)",
11418 )
11419 .await
11420 .unwrap();
11421 conn.execute(
11422 "CREATE TABLE orders(\
11423 id INTEGER PRIMARY KEY, product_id INTEGER, qty INTEGER)",
11424 )
11425 .await
11426 .unwrap();
11427 conn.execute("INSERT INTO orders VALUES (1, 1, 10), (2, 2, 5)")
11428 .await
11429 .unwrap();
11430 conn.execute(
11431 "CREATE VIEW order_details AS \
11432 SELECT o.id as order_id, p.name, o.qty, \
11433 p.price * o.qty as total \
11434 FROM orders o JOIN products p ON o.product_id = p.id",
11435 )
11436 .await
11437 .unwrap();
11438 let r = conn
11439 .query(
11440 "SELECT order_id, name, total \
11441 FROM order_details ORDER BY order_id",
11442 )
11443 .await
11444 .unwrap();
11445 assert_eq!(row_values(&r[0])[1].to_text(), "Widget");
11446 assert_eq!(row_values(&r[0])[2].to_text(), "99.9");
11447 assert_eq!(row_values(&r[1])[1].to_text(), "Gizmo");
11448 });
11449 }
11450
11451 #[test]
11452 fn conformance_028_create_view_if_not_exists() {
11453 asupersync::test_utils::run_test(|| async {
11454 let conn = Connection::open(":memory:").await.unwrap();
11455 conn.execute("CREATE TABLE t1(a INTEGER)").await.unwrap();
11456 conn.execute("INSERT INTO t1 VALUES (1), (2), (3)")
11457 .await
11458 .unwrap();
11459 conn.execute("CREATE VIEW v1 AS SELECT a FROM t1")
11460 .await
11461 .unwrap();
11462 conn.execute("CREATE VIEW IF NOT EXISTS v1 AS SELECT 999")
11463 .await
11464 .unwrap();
11465 let r = conn.query("SELECT COUNT(*) FROM v1").await.unwrap();
11466 assert_eq!(row_values(&r[0])[0].to_text(), "3");
11467 });
11468 }
11469
11470 #[test]
11475 fn conformance_029_group_by_basic() {
11476 asupersync::test_utils::run_test(|| async {
11477 let conn = Connection::open(":memory:").await.unwrap();
11478 conn.execute(
11479 "CREATE TABLE orders(\
11480 id INTEGER PRIMARY KEY, customer TEXT, amount REAL)",
11481 )
11482 .await
11483 .unwrap();
11484 conn.execute(
11485 "INSERT INTO orders VALUES \
11486 (1,'Alice',50.0),(2,'Bob',30.0),(3,'Alice',70.0),\
11487 (4,'Bob',20.0),(5,'Charlie',100.0)",
11488 )
11489 .await
11490 .unwrap();
11491 let r = conn
11492 .query(
11493 "SELECT customer, SUM(amount) as total \
11494 FROM orders GROUP BY customer ORDER BY customer",
11495 )
11496 .await
11497 .unwrap();
11498 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
11499 assert_eq!(row_values(&r[0])[1].to_text(), "120.0");
11500 assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
11501 assert_eq!(row_values(&r[1])[1].to_text(), "50.0");
11502 });
11503 }
11504
11505 #[test]
11506 fn conformance_029_having() {
11507 asupersync::test_utils::run_test(|| async {
11508 let conn = Connection::open(":memory:").await.unwrap();
11509 conn.execute(
11510 "CREATE TABLE orders(\
11511 id INTEGER PRIMARY KEY, customer TEXT, amount REAL)",
11512 )
11513 .await
11514 .unwrap();
11515 conn.execute(
11516 "INSERT INTO orders VALUES \
11517 (1,'Alice',50.0),(2,'Bob',30.0),(3,'Alice',70.0),\
11518 (4,'Bob',20.0),(5,'Charlie',100.0)",
11519 )
11520 .await
11521 .unwrap();
11522 let r = conn
11523 .query(
11524 "SELECT customer, SUM(amount) as total \
11525 FROM orders GROUP BY customer \
11526 HAVING total > 60 ORDER BY customer",
11527 )
11528 .await
11529 .unwrap();
11530 assert_eq!(r.len(), 2);
11531 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
11532 assert_eq!(row_values(&r[1])[0].to_text(), "Charlie");
11533 });
11534 }
11535
11536 #[test]
11537 fn conformance_029_group_by_count_min_max_avg() {
11538 asupersync::test_utils::run_test(|| async {
11539 let conn = Connection::open(":memory:").await.unwrap();
11540 conn.execute(
11541 "CREATE TABLE scores(\
11542 student TEXT, subject TEXT, score INTEGER)",
11543 )
11544 .await
11545 .unwrap();
11546 conn.execute(
11547 "INSERT INTO scores VALUES \
11548 ('Alice','Math',90),('Alice','Sci',85),\
11549 ('Bob','Math',70),('Bob','Sci',80)",
11550 )
11551 .await
11552 .unwrap();
11553 let r = conn
11554 .query(
11555 "SELECT student, COUNT(*) as cnt, MIN(score), MAX(score) \
11556 FROM scores GROUP BY student ORDER BY student",
11557 )
11558 .await
11559 .unwrap();
11560 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
11561 assert_eq!(row_values(&r[0])[1].to_text(), "2");
11562 assert_eq!(row_values(&r[0])[2].to_text(), "85");
11563 assert_eq!(row_values(&r[0])[3].to_text(), "90");
11564 });
11565 }
11566
11567 #[test]
11568 fn conformance_029_group_by_multi_column() {
11569 asupersync::test_utils::run_test(|| async {
11570 let conn = Connection::open(":memory:").await.unwrap();
11571 conn.execute(
11572 "CREATE TABLE log(\
11573 dept TEXT, year INTEGER, revenue REAL)",
11574 )
11575 .await
11576 .unwrap();
11577 conn.execute(
11578 "INSERT INTO log VALUES \
11579 ('eng',2024,100.0),('eng',2024,200.0),\
11580 ('eng',2025,150.0),('sales',2024,80.0)",
11581 )
11582 .await
11583 .unwrap();
11584 let r = conn
11585 .query(
11586 "SELECT dept, year, SUM(revenue) as total \
11587 FROM log GROUP BY dept, year ORDER BY dept, year",
11588 )
11589 .await
11590 .unwrap();
11591 assert_eq!(r.len(), 3);
11592 assert_eq!(row_values(&r[0])[0].to_text(), "eng");
11593 assert_eq!(row_values(&r[0])[1].to_text(), "2024");
11594 assert_eq!(row_values(&r[0])[2].to_text(), "300.0");
11595 });
11596 }
11597
11598 #[test]
11603 fn conformance_030_case_searched() {
11604 asupersync::test_utils::run_test(|| async {
11605 let conn = Connection::open(":memory:").await.unwrap();
11606 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, score INTEGER)")
11607 .await
11608 .unwrap();
11609 conn.execute("INSERT INTO t1 VALUES (1, 95), (2, 72), (3, 45)")
11610 .await
11611 .unwrap();
11612 let r = conn
11613 .query(
11614 "SELECT id, \
11615 CASE WHEN score >= 90 THEN 'A' \
11616 WHEN score >= 70 THEN 'B' \
11617 ELSE 'F' END as grade \
11618 FROM t1 ORDER BY id",
11619 )
11620 .await
11621 .unwrap();
11622 assert_eq!(row_values(&r[0])[1].to_text(), "A");
11623 assert_eq!(row_values(&r[1])[1].to_text(), "B");
11624 assert_eq!(row_values(&r[2])[1].to_text(), "F");
11625 });
11626 }
11627
11628 #[test]
11629 fn conformance_030_case_simple() {
11630 asupersync::test_utils::run_test(|| async {
11631 let conn = Connection::open(":memory:").await.unwrap();
11632 let r = conn
11633 .query(
11634 "SELECT CASE 2 \
11635 WHEN 1 THEN 'one' \
11636 WHEN 2 THEN 'two' \
11637 WHEN 3 THEN 'three' \
11638 ELSE 'other' END",
11639 )
11640 .await
11641 .unwrap();
11642 assert_eq!(row_values(&r[0])[0].to_text(), "two");
11643 });
11644 }
11645
11646 #[test]
11647 fn conformance_030_case_null() {
11648 asupersync::test_utils::run_test(|| async {
11649 let conn = Connection::open(":memory:").await.unwrap();
11650 let r = conn
11651 .query(
11652 "SELECT CASE NULL \
11653 WHEN NULL THEN 'match' \
11654 ELSE 'no match' END",
11655 )
11656 .await
11657 .unwrap();
11658 assert_eq!(row_values(&r[0])[0].to_text(), "no match");
11659 let r = conn
11660 .query(
11661 "SELECT CASE WHEN NULL THEN 'truthy' \
11662 ELSE 'falsy' END",
11663 )
11664 .await
11665 .unwrap();
11666 assert_eq!(row_values(&r[0])[0].to_text(), "falsy");
11667 });
11668 }
11669
11670 #[test]
11671 fn conformance_030_case_in_aggregate() {
11672 asupersync::test_utils::run_test(|| async {
11673 let conn = Connection::open(":memory:").await.unwrap();
11674 conn.execute("CREATE TABLE items(id INTEGER PRIMARY KEY, status TEXT)")
11675 .await
11676 .unwrap();
11677 conn.execute(
11678 "INSERT INTO items VALUES \
11679 (1,'active'),(2,'inactive'),(3,'active'),\
11680 (4,'active'),(5,'inactive')",
11681 )
11682 .await
11683 .unwrap();
11684 let r = conn
11685 .query(
11686 "SELECT SUM(CASE WHEN status = 'active' \
11687 THEN 1 ELSE 0 END) as active_count FROM items",
11688 )
11689 .await
11690 .unwrap();
11691 assert_eq!(row_values(&r[0])[0].to_text(), "3");
11692 });
11693 }
11694
11695 #[test]
11700 fn conformance_031_insert_or_replace() {
11701 asupersync::test_utils::run_test(|| async {
11702 let conn = Connection::open(":memory:").await.unwrap();
11703 conn.execute("CREATE TABLE kv(key TEXT PRIMARY KEY, value TEXT)")
11704 .await
11705 .unwrap();
11706 conn.execute("INSERT INTO kv VALUES ('a', 'first')")
11707 .await
11708 .unwrap();
11709 conn.execute("INSERT OR REPLACE INTO kv VALUES ('a', 'replaced')")
11710 .await
11711 .unwrap();
11712 let r = conn
11713 .query("SELECT value FROM kv WHERE key = 'a'")
11714 .await
11715 .unwrap();
11716 assert_eq!(row_values(&r[0])[0].to_text(), "replaced");
11717 });
11718 }
11719
11720 #[test]
11721 fn conformance_031_insert_or_ignore() {
11722 asupersync::test_utils::run_test(|| async {
11723 let conn = Connection::open(":memory:").await.unwrap();
11724 conn.execute("CREATE TABLE kv(key TEXT PRIMARY KEY, value TEXT)")
11725 .await
11726 .unwrap();
11727 conn.execute("INSERT INTO kv VALUES ('a', 'first')")
11728 .await
11729 .unwrap();
11730 conn.execute("INSERT OR IGNORE INTO kv VALUES ('a', 'ignored')")
11731 .await
11732 .unwrap();
11733 let r = conn
11734 .query("SELECT value FROM kv WHERE key = 'a'")
11735 .await
11736 .unwrap();
11737 assert_eq!(row_values(&r[0])[0].to_text(), "first");
11738 conn.execute("INSERT OR IGNORE INTO kv VALUES ('b', 'new')")
11739 .await
11740 .unwrap();
11741 let r = conn.query("SELECT key FROM kv ORDER BY key").await.unwrap();
11743 assert_eq!(r.len(), 2);
11744 assert_eq!(row_values(&r[0])[0].to_text(), "a");
11745 assert_eq!(row_values(&r[1])[0].to_text(), "b");
11746 });
11747 }
11748
11749 #[test]
11750 fn conformance_031_replace_into() {
11751 asupersync::test_utils::run_test(|| async {
11752 let conn = Connection::open(":memory:").await.unwrap();
11753 conn.execute(
11754 "CREATE TABLE t1(\
11755 id INTEGER PRIMARY KEY, name TEXT, \
11756 score INTEGER DEFAULT 0)",
11757 )
11758 .await
11759 .unwrap();
11760 conn.execute("INSERT INTO t1 VALUES (1, 'Alice', 100)")
11761 .await
11762 .unwrap();
11763 conn.execute("REPLACE INTO t1 VALUES (1, 'Alice Updated', 200)")
11764 .await
11765 .unwrap();
11766 let r = conn
11767 .query("SELECT name, score FROM t1 WHERE id = 1")
11768 .await
11769 .unwrap();
11770 assert_eq!(row_values(&r[0])[0].to_text(), "Alice Updated");
11771 assert_eq!(row_values(&r[0])[1].to_text(), "200");
11772 });
11773 }
11774
11775 #[test]
11780 fn conformance_032_rename_table() {
11781 asupersync::test_utils::run_test(|| async {
11782 let conn = Connection::open(":memory:").await.unwrap();
11783 conn.execute(
11784 "CREATE TABLE original(\
11785 id INTEGER PRIMARY KEY, name TEXT)",
11786 )
11787 .await
11788 .unwrap();
11789 conn.execute("INSERT INTO original VALUES (1, 'Alice'), (2, 'Bob')")
11790 .await
11791 .unwrap();
11792 conn.execute("ALTER TABLE original RENAME TO renamed")
11793 .await
11794 .unwrap();
11795 let r = conn
11796 .query("SELECT name FROM renamed ORDER BY id")
11797 .await
11798 .unwrap();
11799 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
11800 assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
11801 });
11802 }
11803
11804 #[test]
11805 fn conformance_032_add_column() {
11806 asupersync::test_utils::run_test(|| async {
11807 let conn = Connection::open(":memory:").await.unwrap();
11808 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT)")
11809 .await
11810 .unwrap();
11811 conn.execute("INSERT INTO t1 VALUES (1, 'Alice'), (2, 'Bob')")
11812 .await
11813 .unwrap();
11814 conn.execute("ALTER TABLE t1 ADD COLUMN score INTEGER DEFAULT 0")
11815 .await
11816 .unwrap();
11817 let r = conn
11818 .query("SELECT name, score FROM t1 ORDER BY id")
11819 .await
11820 .unwrap();
11821 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
11822 assert_eq!(row_values(&r[0])[1].to_text(), "0");
11823 conn.execute("INSERT INTO t1 VALUES (3, 'Charlie', 95)")
11824 .await
11825 .unwrap();
11826 let r = conn
11827 .query("SELECT name, score FROM t1 WHERE id = 3")
11828 .await
11829 .unwrap();
11830 assert_eq!(row_values(&r[0])[0].to_text(), "Charlie");
11831 assert_eq!(row_values(&r[0])[1].to_text(), "95");
11832 });
11833 }
11834
11835 #[test]
11836 fn conformance_032_add_multiple_columns() {
11837 asupersync::test_utils::run_test(|| async {
11838 let conn = Connection::open(":memory:").await.unwrap();
11839 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT)")
11840 .await
11841 .unwrap();
11842 conn.execute("INSERT INTO t1 VALUES (1, 'Alice'), (2, 'Bob')")
11843 .await
11844 .unwrap();
11845 conn.execute("ALTER TABLE t1 ADD COLUMN score INTEGER DEFAULT 0")
11846 .await
11847 .unwrap();
11848 conn.execute("ALTER TABLE t1 ADD COLUMN active INTEGER DEFAULT 1")
11849 .await
11850 .unwrap();
11851 let r = conn
11852 .query("SELECT name, active FROM t1 ORDER BY id")
11853 .await
11854 .unwrap();
11855 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
11856 assert_eq!(row_values(&r[0])[1].to_text(), "1");
11857 assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
11858 assert_eq!(row_values(&r[1])[1].to_text(), "1");
11859 });
11860 }
11861
11862 #[test]
11863 fn alter_table_preserves_without_rowid_in_schema_sql() {
11864 asupersync::test_utils::run_test(|| async {
11865 let conn = Connection::open(":memory:").await.unwrap();
11866 conn.execute("CREATE TABLE wr(id INTEGER PRIMARY KEY, body TEXT) WITHOUT ROWID;")
11867 .await
11868 .unwrap();
11869 conn.execute("ALTER TABLE wr ADD COLUMN extra INTEGER DEFAULT 0;")
11870 .await
11871 .unwrap();
11872
11873 let rows = conn
11874 .query("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'wr';")
11875 .await
11876 .unwrap();
11877 let sql = row_values(&rows[0])[0].to_text();
11878 assert!(
11879 sql.to_ascii_uppercase().contains("WITHOUT ROWID"),
11880 "ALTER TABLE must preserve WITHOUT ROWID in sqlite_master SQL: {sql}"
11881 );
11882 });
11883 }
11884
11885 #[test]
11886 fn alter_table_preserves_typeless_columns_in_schema_sql() {
11887 asupersync::test_utils::run_test(|| async {
11888 let conn = Connection::open(":memory:").await.unwrap();
11889 conn.execute("CREATE TABLE typeless(payload);")
11890 .await
11891 .unwrap();
11892 conn.execute("ALTER TABLE typeless ADD COLUMN note;")
11893 .await
11894 .unwrap();
11895
11896 let rows = conn
11897 .query("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'typeless';")
11898 .await
11899 .unwrap();
11900 let sql = row_values(&rows[0])[0].to_text();
11901 let Some((Statement::CreateTable(create), _)) =
11902 parse_first_statement_with_tail(&sql).expect("sqlite_master sql should parse")
11903 else {
11904 panic!("expected CREATE TABLE sql, got: {sql}");
11905 };
11906 let CreateTableBody::Columns { columns, .. } = create.body else {
11907 panic!("expected CREATE TABLE column definition body, got: {sql}");
11908 };
11909 assert_eq!(
11910 columns.len(),
11911 2,
11912 "ALTER TABLE should preserve both columns in sqlite_master sql: {sql}"
11913 );
11914 assert_eq!(columns[0].name, "payload", "{sql}");
11915 assert!(
11916 columns[0].type_name.is_none(),
11917 "ALTER TABLE must not synthesize a declared type for existing typeless columns: {sql}"
11918 );
11919 assert_eq!(columns[1].name, "note", "{sql}");
11920 assert!(
11921 columns[1].type_name.is_none(),
11922 "ALTER TABLE must not synthesize a declared type for added typeless columns: {sql}"
11923 );
11924 });
11925 }
11926
11927 #[test]
11928 fn alter_table_preserves_embedded_quote_identifiers_in_schema_sql() {
11929 asupersync::test_utils::run_test(|| async {
11930 let conn = Connection::open(":memory:").await.unwrap();
11931 conn.execute(r#"CREATE TABLE "te""st"("co""l" TEXT PRIMARY KEY);"#)
11932 .await
11933 .unwrap();
11934 conn.execute(r#"ALTER TABLE "te""st" ADD COLUMN "no""te" TEXT;"#)
11935 .await
11936 .unwrap();
11937
11938 let rows = conn
11939 .query(r#"SELECT sql FROM sqlite_master WHERE type='table' AND name='te"st';"#)
11940 .await
11941 .unwrap();
11942 let sql = row_values(&rows[0])[0].to_text();
11943 let Some((Statement::CreateTable(create), _)) =
11944 parse_first_statement_with_tail(&sql).expect("sqlite_master sql should parse")
11945 else {
11946 panic!("expected CREATE TABLE sql, got: {sql}");
11947 };
11948 let CreateTableBody::Columns { columns, .. } = create.body else {
11949 panic!("expected CREATE TABLE column definition body, got: {sql}");
11950 };
11951 assert_eq!(create.name.name, "te\"st", "{sql}");
11952 assert_eq!(columns[0].name, "co\"l", "{sql}");
11953 assert_eq!(columns[1].name, "no\"te", "{sql}");
11954 });
11955 }
11956
11957 #[test]
11958 fn alter_table_rename_preserves_embedded_quote_identifiers_in_index_sql() {
11959 asupersync::test_utils::run_test(|| async {
11960 let conn = Connection::open(":memory:").await.unwrap();
11961 conn.execute(r#"CREATE TABLE "te""st"("co""l" TEXT);"#)
11962 .await
11963 .unwrap();
11964 conn.execute(r#"CREATE INDEX "ix""q" ON "te""st"("co""l");"#)
11965 .await
11966 .unwrap();
11967 conn.execute(r#"ALTER TABLE "te""st" RENAME TO "ta""rget";"#)
11968 .await
11969 .unwrap();
11970
11971 let rows = conn
11972 .query(r#"SELECT sql FROM sqlite_master WHERE type='index' AND name='ix"q';"#)
11973 .await
11974 .unwrap();
11975 let sql = row_values(&rows[0])[0].to_text();
11976 let Some((Statement::CreateIndex(create), _)) =
11977 parse_first_statement_with_tail(&sql).expect("sqlite_master sql should parse")
11978 else {
11979 panic!("expected CREATE INDEX sql, got: {sql}");
11980 };
11981 assert_eq!(create.name.name, "ix\"q", "{sql}");
11982 assert_eq!(create.table, "ta\"rget", "{sql}");
11983 });
11984 }
11985
11986 #[test]
11987 fn foreign_key_cascade_supports_embedded_quote_identifiers() {
11988 asupersync::test_utils::run_test(|| async {
11989 let conn = Connection::open(":memory:").await.unwrap();
11990 conn.execute("PRAGMA foreign_keys = ON;").await.unwrap();
11991 conn.execute(r#"CREATE TABLE "par""ent"("id""x" INTEGER PRIMARY KEY);"#)
11992 .await
11993 .unwrap();
11994 conn.execute(
11995 r#"CREATE TABLE "chi""ld"("fk""x" INTEGER REFERENCES "par""ent"("id""x") ON DELETE CASCADE);"#,
11996 )
11997 .await
11998 .unwrap();
11999 conn.execute(r#"INSERT INTO "par""ent"("id""x") VALUES (1);"#)
12000 .await
12001 .unwrap();
12002 conn.execute(r#"INSERT INTO "chi""ld"("fk""x") VALUES (1);"#)
12003 .await
12004 .unwrap();
12005
12006 conn.execute(r#"DELETE FROM "par""ent" WHERE "id""x" = 1;"#)
12007 .await
12008 .unwrap();
12009
12010 let rows = conn
12011 .query(r#"SELECT COUNT(*) FROM "chi""ld";"#)
12012 .await
12013 .unwrap();
12014 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(0));
12015 });
12016 }
12017
12018 #[test]
12023 fn conformance_033_inner_join() {
12024 asupersync::test_utils::run_test(|| async {
12025 let conn = Connection::open(":memory:").await.unwrap();
12026 conn.execute("CREATE TABLE dept(id INTEGER PRIMARY KEY, name TEXT)")
12027 .await
12028 .unwrap();
12029 conn.execute("CREATE TABLE emp(id INTEGER PRIMARY KEY, name TEXT, dept_id INTEGER)")
12030 .await
12031 .unwrap();
12032 conn.execute("INSERT INTO dept VALUES (1,'Eng'),(2,'Sales'),(3,'HR')")
12033 .await
12034 .unwrap();
12035 conn.execute(
12036 "INSERT INTO emp VALUES (1,'Alice',1),(2,'Bob',2),(3,'Charlie',1),(4,'Diana',2)",
12037 )
12038 .await
12039 .unwrap();
12040 let r = conn
12041 .query(
12042 "SELECT emp.name, dept.name FROM emp \
12043 INNER JOIN dept ON emp.dept_id = dept.id \
12044 ORDER BY emp.name",
12045 )
12046 .await
12047 .unwrap();
12048 assert_eq!(r.len(), 4);
12049 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
12050 assert_eq!(row_values(&r[0])[1].to_text(), "Eng");
12051 assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
12052 assert_eq!(row_values(&r[1])[1].to_text(), "Sales");
12053 assert_eq!(row_values(&r[2])[0].to_text(), "Charlie");
12054 assert_eq!(row_values(&r[2])[1].to_text(), "Eng");
12055 assert_eq!(row_values(&r[3])[0].to_text(), "Diana");
12056 assert_eq!(row_values(&r[3])[1].to_text(), "Sales");
12057 });
12058 }
12059
12060 #[test]
12061 fn conformance_033_left_join() {
12062 asupersync::test_utils::run_test(|| async {
12063 let conn = Connection::open(":memory:").await.unwrap();
12064 conn.execute("CREATE TABLE dept(id INTEGER PRIMARY KEY, name TEXT)")
12065 .await
12066 .unwrap();
12067 conn.execute("CREATE TABLE emp(id INTEGER PRIMARY KEY, name TEXT, dept_id INTEGER)")
12068 .await
12069 .unwrap();
12070 conn.execute("INSERT INTO dept VALUES (1,'Eng'),(2,'Sales'),(3,'HR')")
12071 .await
12072 .unwrap();
12073 conn.execute("INSERT INTO emp VALUES (1,'Alice',1),(2,'Bob',2)")
12074 .await
12075 .unwrap();
12076 let r = conn
12077 .query(
12078 "SELECT dept.name, emp.name FROM dept \
12079 LEFT JOIN emp ON dept.id = emp.dept_id \
12080 ORDER BY dept.name",
12081 )
12082 .await
12083 .unwrap();
12084 assert_eq!(r.len(), 3);
12085 assert_eq!(row_values(&r[0])[0].to_text(), "Eng");
12087 assert_eq!(row_values(&r[0])[1].to_text(), "Alice");
12088 assert_eq!(row_values(&r[1])[0].to_text(), "HR");
12090 assert!(row_values(&r[1])[1].is_null());
12091 assert_eq!(row_values(&r[2])[0].to_text(), "Sales");
12093 assert_eq!(row_values(&r[2])[1].to_text(), "Bob");
12094 });
12095 }
12096
12097 #[test]
12098 fn conformance_033_cross_join() {
12099 asupersync::test_utils::run_test(|| async {
12100 let conn = Connection::open(":memory:").await.unwrap();
12101 conn.execute("CREATE TABLE colors(c TEXT)").await.unwrap();
12102 conn.execute("CREATE TABLE sizes(s TEXT)").await.unwrap();
12103 conn.execute("INSERT INTO colors VALUES ('red'),('blue')")
12104 .await
12105 .unwrap();
12106 conn.execute("INSERT INTO sizes VALUES ('S'),('M'),('L')")
12107 .await
12108 .unwrap();
12109 let r = conn
12110 .query("SELECT c, s FROM colors CROSS JOIN sizes ORDER BY c, s")
12111 .await
12112 .unwrap();
12113 assert_eq!(r.len(), 6);
12114 assert_eq!(row_values(&r[0])[0].to_text(), "blue");
12115 assert_eq!(row_values(&r[0])[1].to_text(), "L");
12116 assert_eq!(row_values(&r[5])[0].to_text(), "red");
12117 assert_eq!(row_values(&r[5])[1].to_text(), "S");
12118 });
12119 }
12120
12121 #[test]
12122 fn conformance_033_self_join() {
12123 asupersync::test_utils::run_test(|| async {
12124 let conn = Connection::open(":memory:").await.unwrap();
12125 conn.execute("CREATE TABLE emp(id INTEGER PRIMARY KEY, name TEXT, mgr_id INTEGER)")
12126 .await
12127 .unwrap();
12128 conn.execute(
12129 "INSERT INTO emp VALUES (1,'Boss',NULL),(2,'Alice',1),(3,'Bob',1),(4,'Charlie',2)",
12130 )
12131 .await
12132 .unwrap();
12133 let r = conn
12134 .query(
12135 "SELECT e.name, m.name FROM emp e \
12136 INNER JOIN emp m ON e.mgr_id = m.id \
12137 ORDER BY e.name",
12138 )
12139 .await
12140 .unwrap();
12141 assert_eq!(r.len(), 3);
12142 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
12143 assert_eq!(row_values(&r[0])[1].to_text(), "Boss");
12144 assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
12145 assert_eq!(row_values(&r[1])[1].to_text(), "Boss");
12146 assert_eq!(row_values(&r[2])[0].to_text(), "Charlie");
12147 assert_eq!(row_values(&r[2])[1].to_text(), "Alice");
12148 });
12149 }
12150
12151 #[test]
12152 fn regression_join_literal_text_numeric_comparison_uses_storage_class_order() {
12153 asupersync::test_utils::run_test(|| async {
12154 let conn = Connection::open(":memory:").await.unwrap();
12155 conn.execute("CREATE TABLE a(x INTEGER)").await.unwrap();
12156 conn.execute("CREATE TABLE b(y INTEGER)").await.unwrap();
12157 conn.execute("INSERT INTO a VALUES (1)").await.unwrap();
12158 conn.execute("INSERT INTO b VALUES (2)").await.unwrap();
12159
12160 let rows = conn
12161 .query("SELECT a.x FROM a JOIN b ON '123' = 123;")
12162 .await
12163 .unwrap();
12164 assert_eq!(rows.len(), 0);
12165
12166 let rows = conn
12167 .query("SELECT a.x FROM a JOIN b ON '123' < 124;")
12168 .await
12169 .unwrap();
12170 assert_eq!(rows.len(), 0);
12171
12172 let rows = conn
12173 .query("SELECT a.x FROM (SELECT 1 AS x) a JOIN (SELECT 2 AS y) b ON '123' = 123;")
12174 .await
12175 .unwrap();
12176 assert_eq!(rows.len(), 0);
12177
12178 let rows = conn
12179 .query("SELECT a.x FROM (SELECT 1 AS x) a JOIN (SELECT 2 AS y) b ON '123' < 124;")
12180 .await
12181 .unwrap();
12182 assert_eq!(rows.len(), 0);
12183
12184 let rows = conn
12185 .query("SELECT a.x FROM a JOIN b ON a.rowid = '1';")
12186 .await
12187 .unwrap();
12188 assert_eq!(rows.len(), 1);
12189
12190 let rows = conn
12191 .query("SELECT a.x FROM a JOIN b ON a.rowid > '0';")
12192 .await
12193 .unwrap();
12194 assert_eq!(rows.len(), 1);
12195
12196 conn.execute("CREATE TABLE txt(v TEXT)").await.unwrap();
12197 conn.execute("CREATE TABLE num(v NUMERIC)").await.unwrap();
12198 conn.execute("INSERT INTO txt VALUES ('9')").await.unwrap();
12199 conn.execute("INSERT INTO num VALUES (CAST('9' AS TEXT))")
12200 .await
12201 .unwrap();
12202
12203 let rows = conn
12204 .query("SELECT v FROM (SELECT v FROM txt) a JOIN (SELECT 10 AS n) b ON v < n;")
12205 .await
12206 .unwrap();
12207 assert_eq!(rows.len(), 0);
12208
12209 let rows = conn
12210 .query("SELECT v FROM (SELECT v FROM num) a JOIN (SELECT 10 AS n) b ON v < n;")
12211 .await
12212 .unwrap();
12213 assert_eq!(rows.len(), 1);
12214 });
12215 }
12216
12217 #[test]
12218 fn regression_join_ambiguous_column_surfaces_typed_error() {
12219 asupersync::test_utils::run_test(|| async {
12220 let conn = Connection::open(":memory:").await.unwrap();
12221 conn.execute("CREATE TABLE a(x INTEGER)").await.unwrap();
12222 conn.execute("CREATE TABLE b(x INTEGER)").await.unwrap();
12223
12224 let err = conn
12225 .query("SELECT a.x FROM a JOIN b ON x = 1;")
12226 .await
12227 .expect_err("unqualified duplicate JOIN column should fail");
12228 assert!(
12229 matches!(err, FrankenError::AmbiguousColumn { ref name } if name == "x"),
12230 "unexpected error: {err:?}"
12231 );
12232 });
12233 }
12234
12235 #[test]
12236 fn regression_table_alias_hides_base_table_qualifier() {
12237 asupersync::test_utils::run_test(|| async {
12238 let conn = Connection::open(":memory:").await.unwrap();
12239 conn.execute("CREATE TABLE t(x INTEGER)").await.unwrap();
12240
12241 let err = conn
12242 .query("SELECT t.x FROM t AS a;")
12243 .await
12244 .expect_err("base table qualifier should not resolve after aliasing");
12245 let message = err.to_string();
12246 assert!(
12247 message.contains("no such table: t") || message.contains("no such column"),
12248 "unexpected error: {err:?}"
12249 );
12250 });
12251 }
12252
12253 async fn assert_wrong_function_arity(conn: &Connection, sql: &str, name: &str) {
12254 let err = conn
12255 .query(sql)
12256 .await
12257 .expect_err("known function with wrong arity should fail");
12258 let expected = format!("wrong number of arguments to function {name}()");
12259 assert!(
12260 matches!(&err, FrankenError::FunctionError(message) if message == &expected),
12261 "unexpected error for {sql}: {err:?}"
12262 );
12263 }
12264
12265 #[test]
12266 fn regression_aggregate_wrong_arity_surfaces_function_error() {
12267 asupersync::test_utils::run_test(|| async {
12268 let conn = Connection::open(":memory:").await.unwrap();
12269 conn.execute("CREATE TABLE t(v INTEGER, s TEXT)")
12270 .await
12271 .unwrap();
12272 conn.execute("INSERT INTO t VALUES (1, 'a'), (2, 'b')")
12273 .await
12274 .unwrap();
12275
12276 assert_wrong_function_arity(&conn, "SELECT sum() FROM t;", "sum").await;
12277 assert_wrong_function_arity(&conn, "SELECT group_concat() FROM t;", "group_concat")
12278 .await;
12279 assert_wrong_function_arity(
12280 &conn,
12281 "SELECT group_concat(s, '-', '!') FROM t;",
12282 "group_concat",
12283 )
12284 .await;
12285 });
12286 }
12287
12288 #[test]
12289 fn regression_window_wrong_arity_surfaces_function_error() {
12290 asupersync::test_utils::run_test(|| async {
12291 let conn = Connection::open(":memory:").await.unwrap();
12292 conn.execute("CREATE TABLE t(v INTEGER)").await.unwrap();
12293 conn.execute("INSERT INTO t VALUES (1), (2)").await.unwrap();
12294
12295 assert_wrong_function_arity(&conn, "SELECT rank(1) OVER (ORDER BY v) FROM t;", "rank")
12296 .await;
12297 assert_wrong_function_arity(&conn, "SELECT lag() OVER (ORDER BY v) FROM t;", "lag")
12298 .await;
12299 assert_wrong_function_arity(
12300 &conn,
12301 "SELECT lag(v, 1, 0, 0) OVER (ORDER BY v) FROM t;",
12302 "lag",
12303 )
12304 .await;
12305 assert_wrong_function_arity(&conn, "SELECT count(1, 2) OVER () FROM t;", "count").await;
12306 });
12307 }
12308
12309 #[test]
12310 fn conformance_033_join_with_where() {
12311 asupersync::test_utils::run_test(|| async {
12312 let conn = Connection::open(":memory:").await.unwrap();
12313 conn.execute("CREATE TABLE dept(id INTEGER PRIMARY KEY, name TEXT)")
12314 .await
12315 .unwrap();
12316 conn.execute(
12317 "CREATE TABLE emp(id INTEGER PRIMARY KEY, name TEXT, dept_id INTEGER, salary INTEGER)",
12318 )
12319 .await
12320 .unwrap();
12321 conn.execute("INSERT INTO dept VALUES (1,'Eng'),(2,'Sales')")
12322 .await
12323 .unwrap();
12324 conn.execute(
12325 "INSERT INTO emp VALUES (1,'Alice',1,100),(2,'Bob',2,80),(3,'Charlie',1,120)",
12326 )
12327 .await
12328 .unwrap();
12329 let r = conn
12330 .query(
12331 "SELECT emp.name, dept.name FROM emp \
12332 JOIN dept ON emp.dept_id = dept.id \
12333 WHERE emp.salary > 90 ORDER BY emp.name",
12334 )
12335 .await
12336 .unwrap();
12337 assert_eq!(r.len(), 2);
12338 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
12339 assert_eq!(row_values(&r[1])[0].to_text(), "Charlie");
12340 });
12341 }
12342
12343 #[test]
12348 fn conformance_034_update_basic() {
12349 asupersync::test_utils::run_test(|| async {
12350 let conn = Connection::open(":memory:").await.unwrap();
12351 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT, score INTEGER)")
12352 .await
12353 .unwrap();
12354 conn.execute("INSERT INTO t1 VALUES (1,'Alice',80),(2,'Bob',90),(3,'Charlie',70)")
12355 .await
12356 .unwrap();
12357 conn.execute("UPDATE t1 SET score = 95 WHERE id = 2")
12358 .await
12359 .unwrap();
12360 let r = conn
12361 .query("SELECT score FROM t1 WHERE id = 2")
12362 .await
12363 .unwrap();
12364 assert_eq!(row_values(&r[0])[0].to_text(), "95");
12365 });
12366 }
12367
12368 #[test]
12369 fn conformance_034_update_multiple_columns() {
12370 asupersync::test_utils::run_test(|| async {
12371 let conn = Connection::open(":memory:").await.unwrap();
12372 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT, score INTEGER)")
12373 .await
12374 .unwrap();
12375 conn.execute("INSERT INTO t1 VALUES (1,'Alice',80)")
12376 .await
12377 .unwrap();
12378 conn.execute("UPDATE t1 SET name = 'Alicia', score = 99 WHERE id = 1")
12379 .await
12380 .unwrap();
12381 let r = conn
12382 .query("SELECT name, score FROM t1 WHERE id = 1")
12383 .await
12384 .unwrap();
12385 assert_eq!(row_values(&r[0])[0].to_text(), "Alicia");
12386 assert_eq!(row_values(&r[0])[1].to_text(), "99");
12387 });
12388 }
12389
12390 #[test]
12391 fn conformance_034_update_all_rows() {
12392 asupersync::test_utils::run_test(|| async {
12393 let conn = Connection::open(":memory:").await.unwrap();
12394 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, active INTEGER)")
12395 .await
12396 .unwrap();
12397 conn.execute("INSERT INTO t1 VALUES (1,1),(2,1),(3,0)")
12398 .await
12399 .unwrap();
12400 conn.execute("UPDATE t1 SET active = 0").await.unwrap();
12401 let r = conn
12402 .query("SELECT active FROM t1 ORDER BY id")
12403 .await
12404 .unwrap();
12405 assert_eq!(r.len(), 3);
12406 assert_eq!(row_values(&r[0])[0].to_text(), "0");
12407 assert_eq!(row_values(&r[1])[0].to_text(), "0");
12408 assert_eq!(row_values(&r[2])[0].to_text(), "0");
12409 });
12410 }
12411
12412 #[test]
12413 fn conformance_034_update_with_expression() {
12414 asupersync::test_utils::run_test(|| async {
12415 let conn = Connection::open(":memory:").await.unwrap();
12416 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val INTEGER)")
12417 .await
12418 .unwrap();
12419 conn.execute("INSERT INTO t1 VALUES (1,10),(2,20),(3,30)")
12420 .await
12421 .unwrap();
12422 conn.execute("UPDATE t1 SET val = val * 2 WHERE id <= 2")
12423 .await
12424 .unwrap();
12425 let r = conn.query("SELECT val FROM t1 ORDER BY id").await.unwrap();
12426 assert_eq!(row_values(&r[0])[0].to_text(), "20");
12427 assert_eq!(row_values(&r[1])[0].to_text(), "40");
12428 assert_eq!(row_values(&r[2])[0].to_text(), "30");
12429 });
12430 }
12431
12432 #[test]
12437 fn conformance_035_delete_with_where() {
12438 asupersync::test_utils::run_test(|| async {
12439 let conn = Connection::open(":memory:").await.unwrap();
12440 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT)")
12441 .await
12442 .unwrap();
12443 conn.execute("INSERT INTO t1 VALUES (1,'Alice'),(2,'Bob'),(3,'Charlie')")
12444 .await
12445 .unwrap();
12446 conn.execute("DELETE FROM t1 WHERE id = 2").await.unwrap();
12447 let r = conn.query("SELECT name FROM t1 ORDER BY id").await.unwrap();
12448 assert_eq!(r.len(), 2);
12449 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
12450 assert_eq!(row_values(&r[1])[0].to_text(), "Charlie");
12451 });
12452 }
12453
12454 #[test]
12455 fn conformance_035_delete_all() {
12456 asupersync::test_utils::run_test(|| async {
12457 let conn = Connection::open(":memory:").await.unwrap();
12458 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT)")
12459 .await
12460 .unwrap();
12461 conn.execute("INSERT INTO t1 VALUES (1,'Alice'),(2,'Bob')")
12462 .await
12463 .unwrap();
12464 conn.execute("DELETE FROM t1").await.unwrap();
12465 let r = conn.query("SELECT COUNT(*) FROM t1").await.unwrap();
12466 assert_eq!(row_values(&r[0])[0].to_text(), "0");
12467 });
12468 }
12469
12470 #[test]
12471 fn conformance_035_delete_with_in() {
12472 asupersync::test_utils::run_test(|| async {
12473 let conn = Connection::open(":memory:").await.unwrap();
12474 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT)")
12475 .await
12476 .unwrap();
12477 conn.execute("INSERT INTO t1 VALUES (1,'Alice'),(2,'Bob'),(3,'Charlie'),(4,'Diana')")
12478 .await
12479 .unwrap();
12480 conn.execute("DELETE FROM t1 WHERE id IN (2, 4)")
12481 .await
12482 .unwrap();
12483 let r = conn.query("SELECT name FROM t1 ORDER BY id").await.unwrap();
12484 assert_eq!(r.len(), 2);
12485 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
12486 assert_eq!(row_values(&r[1])[0].to_text(), "Charlie");
12487 });
12488 }
12489
12490 #[test]
12495 fn conformance_036_union_all() {
12496 asupersync::test_utils::run_test(|| async {
12497 let conn = Connection::open(":memory:").await.unwrap();
12498 conn.execute("CREATE TABLE t1(name TEXT)").await.unwrap();
12499 conn.execute("CREATE TABLE t2(name TEXT)").await.unwrap();
12500 conn.execute("INSERT INTO t1 VALUES ('Alice'),('Bob')")
12501 .await
12502 .unwrap();
12503 conn.execute("INSERT INTO t2 VALUES ('Bob'),('Charlie')")
12504 .await
12505 .unwrap();
12506 let r = conn
12507 .query("SELECT name FROM t1 UNION ALL SELECT name FROM t2 ORDER BY name")
12508 .await
12509 .unwrap();
12510 assert_eq!(r.len(), 4);
12511 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
12512 assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
12513 assert_eq!(row_values(&r[2])[0].to_text(), "Bob");
12514 assert_eq!(row_values(&r[3])[0].to_text(), "Charlie");
12515 });
12516 }
12517
12518 #[test]
12519 fn conformance_036_union_distinct() {
12520 asupersync::test_utils::run_test(|| async {
12521 let conn = Connection::open(":memory:").await.unwrap();
12522 conn.execute("CREATE TABLE t1(name TEXT)").await.unwrap();
12523 conn.execute("CREATE TABLE t2(name TEXT)").await.unwrap();
12524 conn.execute("INSERT INTO t1 VALUES ('Alice'),('Bob')")
12525 .await
12526 .unwrap();
12527 conn.execute("INSERT INTO t2 VALUES ('Bob'),('Charlie')")
12528 .await
12529 .unwrap();
12530 let r = conn
12531 .query("SELECT name FROM t1 UNION SELECT name FROM t2 ORDER BY name")
12532 .await
12533 .unwrap();
12534 assert_eq!(r.len(), 3);
12535 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
12536 assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
12537 assert_eq!(row_values(&r[2])[0].to_text(), "Charlie");
12538 });
12539 }
12540
12541 #[test]
12542 fn conformance_036_intersect() {
12543 asupersync::test_utils::run_test(|| async {
12544 let conn = Connection::open(":memory:").await.unwrap();
12545 conn.execute("CREATE TABLE t1(name TEXT)").await.unwrap();
12546 conn.execute("CREATE TABLE t2(name TEXT)").await.unwrap();
12547 conn.execute("INSERT INTO t1 VALUES ('Alice'),('Bob'),('Charlie')")
12548 .await
12549 .unwrap();
12550 conn.execute("INSERT INTO t2 VALUES ('Bob'),('Charlie'),('Diana')")
12551 .await
12552 .unwrap();
12553 let r = conn
12554 .query("SELECT name FROM t1 INTERSECT SELECT name FROM t2 ORDER BY name")
12555 .await
12556 .unwrap();
12557 assert_eq!(r.len(), 2);
12558 assert_eq!(row_values(&r[0])[0].to_text(), "Bob");
12559 assert_eq!(row_values(&r[1])[0].to_text(), "Charlie");
12560 });
12561 }
12562
12563 #[test]
12564 fn conformance_036_except() {
12565 asupersync::test_utils::run_test(|| async {
12566 let conn = Connection::open(":memory:").await.unwrap();
12567 conn.execute("CREATE TABLE t1(name TEXT)").await.unwrap();
12568 conn.execute("CREATE TABLE t2(name TEXT)").await.unwrap();
12569 conn.execute("INSERT INTO t1 VALUES ('Alice'),('Bob'),('Charlie')")
12570 .await
12571 .unwrap();
12572 conn.execute("INSERT INTO t2 VALUES ('Bob'),('Diana')")
12573 .await
12574 .unwrap();
12575 let r = conn
12576 .query("SELECT name FROM t1 EXCEPT SELECT name FROM t2 ORDER BY name")
12577 .await
12578 .unwrap();
12579 assert_eq!(r.len(), 2);
12580 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
12581 assert_eq!(row_values(&r[1])[0].to_text(), "Charlie");
12582 });
12583 }
12584
12585 #[test]
12590 fn conformance_037_order_by_desc() {
12591 asupersync::test_utils::run_test(|| async {
12592 let conn = Connection::open(":memory:").await.unwrap();
12593 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT)")
12594 .await
12595 .unwrap();
12596 conn.execute("INSERT INTO t1 VALUES (1,'Alice'),(2,'Bob'),(3,'Charlie')")
12597 .await
12598 .unwrap();
12599 let r = conn
12600 .query("SELECT name FROM t1 ORDER BY id DESC")
12601 .await
12602 .unwrap();
12603 assert_eq!(row_values(&r[0])[0].to_text(), "Charlie");
12604 assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
12605 assert_eq!(row_values(&r[2])[0].to_text(), "Alice");
12606 });
12607 }
12608
12609 #[test]
12610 fn conformance_037_order_by_multiple() {
12611 asupersync::test_utils::run_test(|| async {
12612 let conn = Connection::open(":memory:").await.unwrap();
12613 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, dept TEXT, name TEXT)")
12614 .await
12615 .unwrap();
12616 conn.execute(
12617 "INSERT INTO t1 VALUES (1,'A','Charlie'),(2,'B','Alice'),\
12618 (3,'A','Alice'),(4,'B','Bob')",
12619 )
12620 .await
12621 .unwrap();
12622 let r = conn
12623 .query("SELECT dept, name FROM t1 ORDER BY dept ASC, name ASC")
12624 .await
12625 .unwrap();
12626 assert_eq!(row_values(&r[0])[1].to_text(), "Alice");
12627 assert_eq!(row_values(&r[0])[0].to_text(), "A");
12628 assert_eq!(row_values(&r[1])[1].to_text(), "Charlie");
12629 assert_eq!(row_values(&r[2])[1].to_text(), "Alice");
12630 assert_eq!(row_values(&r[2])[0].to_text(), "B");
12631 assert_eq!(row_values(&r[3])[1].to_text(), "Bob");
12632 });
12633 }
12634
12635 #[test]
12636 fn conformance_037_limit() {
12637 asupersync::test_utils::run_test(|| async {
12638 let conn = Connection::open(":memory:").await.unwrap();
12639 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT)")
12640 .await
12641 .unwrap();
12642 conn.execute(
12643 "INSERT INTO t1 VALUES (1,'Alice'),(2,'Bob'),(3,'Charlie'),(4,'Diana'),(5,'Eve')",
12644 )
12645 .await
12646 .unwrap();
12647 let r = conn
12648 .query("SELECT name FROM t1 ORDER BY id LIMIT 3")
12649 .await
12650 .unwrap();
12651 assert_eq!(r.len(), 3);
12652 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
12653 assert_eq!(row_values(&r[2])[0].to_text(), "Charlie");
12654 });
12655 }
12656
12657 #[test]
12658 fn conformance_037_limit_offset() {
12659 asupersync::test_utils::run_test(|| async {
12660 let conn = Connection::open(":memory:").await.unwrap();
12661 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT)")
12662 .await
12663 .unwrap();
12664 conn.execute(
12665 "INSERT INTO t1 VALUES (1,'Alice'),(2,'Bob'),(3,'Charlie'),(4,'Diana'),(5,'Eve')",
12666 )
12667 .await
12668 .unwrap();
12669 let r = conn
12670 .query("SELECT name FROM t1 ORDER BY id LIMIT 2 OFFSET 2")
12671 .await
12672 .unwrap();
12673 assert_eq!(r.len(), 2);
12674 assert_eq!(row_values(&r[0])[0].to_text(), "Charlie");
12675 assert_eq!(row_values(&r[1])[0].to_text(), "Diana");
12676 });
12677 }
12678
12679 #[test]
12680 fn conformance_037_distinct() {
12681 asupersync::test_utils::run_test(|| async {
12682 let conn = Connection::open(":memory:").await.unwrap();
12683 conn.execute("CREATE TABLE t1(name TEXT)").await.unwrap();
12684 conn.execute("INSERT INTO t1 VALUES ('Alice'),('Bob'),('Alice'),('Charlie'),('Bob')")
12685 .await
12686 .unwrap();
12687 let r = conn
12688 .query("SELECT DISTINCT name FROM t1 ORDER BY name")
12689 .await
12690 .unwrap();
12691 assert_eq!(r.len(), 3);
12692 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
12693 assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
12694 assert_eq!(row_values(&r[2])[0].to_text(), "Charlie");
12695 });
12696 }
12697
12698 #[test]
12699 fn conformance_037_order_by_nulls() {
12700 asupersync::test_utils::run_test(|| async {
12701 let conn = Connection::open(":memory:").await.unwrap();
12702 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val TEXT)")
12703 .await
12704 .unwrap();
12705 conn.execute("INSERT INTO t1 VALUES (1,'B'),(2,NULL),(3,'A'),(4,NULL),(5,'C')")
12706 .await
12707 .unwrap();
12708 let r = conn
12710 .query("SELECT val FROM t1 ORDER BY val ASC")
12711 .await
12712 .unwrap();
12713 assert_eq!(r.len(), 5);
12714 assert!(row_values(&r[0])[0].is_null());
12715 assert!(row_values(&r[1])[0].is_null());
12716 assert_eq!(row_values(&r[2])[0].to_text(), "A");
12717 assert_eq!(row_values(&r[3])[0].to_text(), "B");
12718 assert_eq!(row_values(&r[4])[0].to_text(), "C");
12719 });
12720 }
12721
12722 #[test]
12727 fn conformance_038_insert_select_basic() {
12728 asupersync::test_utils::run_test(|| async {
12729 let conn = Connection::open(":memory:").await.unwrap();
12730 conn.execute("CREATE TABLE src(id INTEGER PRIMARY KEY, name TEXT)")
12731 .await
12732 .unwrap();
12733 conn.execute("CREATE TABLE dst(id INTEGER PRIMARY KEY, name TEXT)")
12734 .await
12735 .unwrap();
12736 conn.execute("INSERT INTO src VALUES (1,'Alice'),(2,'Bob'),(3,'Charlie')")
12737 .await
12738 .unwrap();
12739 conn.execute("INSERT INTO dst SELECT * FROM src WHERE id <= 2")
12740 .await
12741 .unwrap();
12742 let r = conn
12743 .query("SELECT name FROM dst ORDER BY id")
12744 .await
12745 .unwrap();
12746 assert_eq!(r.len(), 2);
12747 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
12748 assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
12749 });
12750 }
12751
12752 #[test]
12753 fn conformance_038_insert_select_with_transform() {
12754 asupersync::test_utils::run_test(|| async {
12755 let conn = Connection::open(":memory:").await.unwrap();
12756 conn.execute("CREATE TABLE src(id INTEGER PRIMARY KEY, name TEXT)")
12757 .await
12758 .unwrap();
12759 conn.execute("CREATE TABLE dst(name TEXT)").await.unwrap();
12760 conn.execute("INSERT INTO src VALUES (1,'Alice'),(2,'Bob')")
12761 .await
12762 .unwrap();
12763 conn.execute("INSERT INTO dst SELECT upper(name) FROM src")
12764 .await
12765 .unwrap();
12766 let r = conn
12767 .query("SELECT name FROM dst ORDER BY name")
12768 .await
12769 .unwrap();
12770 assert_eq!(r.len(), 2);
12771 assert_eq!(row_values(&r[0])[0].to_text(), "ALICE");
12772 assert_eq!(row_values(&r[1])[0].to_text(), "BOB");
12773 });
12774 }
12775
12776 #[test]
12781 fn conformance_039_null_comparison() {
12782 asupersync::test_utils::run_test(|| async {
12783 let conn = Connection::open(":memory:").await.unwrap();
12784 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val TEXT)")
12785 .await
12786 .unwrap();
12787 conn.execute("INSERT INTO t1 VALUES (1,'A'),(2,NULL),(3,'B')")
12788 .await
12789 .unwrap();
12790 let r = conn
12792 .query("SELECT COUNT(*) FROM t1 WHERE val = NULL")
12793 .await
12794 .unwrap();
12795 assert_eq!(row_values(&r[0])[0].to_text(), "0");
12796 let r = conn
12798 .query("SELECT COUNT(*) FROM t1 WHERE val IS NULL")
12799 .await
12800 .unwrap();
12801 assert_eq!(row_values(&r[0])[0].to_text(), "1");
12802 let r = conn
12804 .query("SELECT COUNT(*) FROM t1 WHERE val IS NOT NULL")
12805 .await
12806 .unwrap();
12807 assert_eq!(row_values(&r[0])[0].to_text(), "2");
12808 });
12809 }
12810
12811 #[test]
12812 fn conformance_039_coalesce() {
12813 asupersync::test_utils::run_test(|| async {
12814 let conn = Connection::open(":memory:").await.unwrap();
12815 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, a TEXT, b TEXT)")
12816 .await
12817 .unwrap();
12818 conn.execute("INSERT INTO t1 VALUES (1,NULL,'fallback'),(2,'value',NULL)")
12819 .await
12820 .unwrap();
12821 let r = conn
12822 .query("SELECT COALESCE(a, b) FROM t1 ORDER BY id")
12823 .await
12824 .unwrap();
12825 assert_eq!(row_values(&r[0])[0].to_text(), "fallback");
12826 assert_eq!(row_values(&r[1])[0].to_text(), "value");
12827 });
12828 }
12829
12830 #[test]
12831 fn conformance_039_ifnull() {
12832 asupersync::test_utils::run_test(|| async {
12833 let conn = Connection::open(":memory:").await.unwrap();
12834 let r = conn
12835 .query("SELECT IFNULL(NULL, 'default'), IFNULL('value', 'default')")
12836 .await
12837 .unwrap();
12838 assert_eq!(row_values(&r[0])[0].to_text(), "default");
12839 assert_eq!(row_values(&r[0])[1].to_text(), "value");
12840 });
12841 }
12842
12843 #[test]
12844 fn conformance_039_nullif() {
12845 asupersync::test_utils::run_test(|| async {
12846 let conn = Connection::open(":memory:").await.unwrap();
12847 let r = conn
12848 .query("SELECT NULLIF(1, 1), NULLIF(1, 2)")
12849 .await
12850 .unwrap();
12851 assert!(row_values(&r[0])[0].is_null());
12852 assert_eq!(row_values(&r[0])[1].to_text(), "1");
12853 });
12854 }
12855
12856 #[test]
12857 fn conformance_039_null_in_aggregate() {
12858 asupersync::test_utils::run_test(|| async {
12859 let conn = Connection::open(":memory:").await.unwrap();
12860 conn.execute("CREATE TABLE t1(val INTEGER)").await.unwrap();
12861 conn.execute("INSERT INTO t1 VALUES (1),(NULL),(3),(NULL),(5)")
12862 .await
12863 .unwrap();
12864 let r = conn
12865 .query("SELECT COUNT(*), COUNT(val), SUM(val), AVG(val) FROM t1")
12866 .await
12867 .unwrap();
12868 assert_eq!(row_values(&r[0])[0].to_text(), "5");
12870 assert_eq!(row_values(&r[0])[1].to_text(), "3");
12872 assert_eq!(row_values(&r[0])[2].to_text(), "9");
12874 assert_eq!(row_values(&r[0])[3].to_text(), "3.0");
12876 });
12877 }
12878
12879 #[test]
12884 fn conformance_040_create_index_basic() {
12885 asupersync::test_utils::run_test(|| async {
12886 let conn = Connection::open(":memory:").await.unwrap();
12887 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)")
12888 .await
12889 .unwrap();
12890 conn.execute("CREATE INDEX idx_name ON t1(name)")
12891 .await
12892 .unwrap();
12893 let r = conn
12895 .query("SELECT type, name, tbl_name FROM sqlite_master WHERE type = 'index' AND name = 'idx_name'")
12896 .await
12897 .unwrap();
12898 assert_eq!(r.len(), 1);
12899 assert_eq!(row_values(&r[0])[0].to_text(), "index");
12900 assert_eq!(row_values(&r[0])[1].to_text(), "idx_name");
12901 assert_eq!(row_values(&r[0])[2].to_text(), "t1");
12902 });
12903 }
12904
12905 #[test]
12906 fn conformance_040_create_index_if_not_exists() {
12907 asupersync::test_utils::run_test(|| async {
12908 let conn = Connection::open(":memory:").await.unwrap();
12909 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val TEXT)")
12910 .await
12911 .unwrap();
12912 conn.execute("CREATE INDEX idx1 ON t1(val)").await.unwrap();
12913 conn.execute("CREATE INDEX IF NOT EXISTS idx1 ON t1(val)")
12915 .await
12916 .unwrap();
12917 let r = conn
12918 .query("SELECT name FROM sqlite_master WHERE type = 'index' AND name = 'idx1'")
12919 .await
12920 .unwrap();
12921 assert_eq!(r.len(), 1);
12922 });
12923 }
12924
12925 #[test]
12926 fn conformance_040_drop_index() {
12927 asupersync::test_utils::run_test(|| async {
12928 let conn = Connection::open(":memory:").await.unwrap();
12929 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val TEXT)")
12930 .await
12931 .unwrap();
12932 conn.execute("CREATE INDEX idx1 ON t1(val)").await.unwrap();
12933 conn.execute("DROP INDEX idx1").await.unwrap();
12934 let r = conn
12935 .query("SELECT name FROM sqlite_master WHERE type = 'index' AND name = 'idx1'")
12936 .await
12937 .unwrap();
12938 assert_eq!(r.len(), 0);
12939 });
12940 }
12941
12942 #[test]
12943 fn conformance_040_unique_index() {
12944 asupersync::test_utils::run_test(|| async {
12945 let conn = Connection::open(":memory:").await.unwrap();
12946 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, code TEXT)")
12947 .await
12948 .unwrap();
12949 conn.execute("CREATE UNIQUE INDEX idx_code ON t1(code)")
12950 .await
12951 .unwrap();
12952 conn.execute("INSERT INTO t1 VALUES (1, 'abc')")
12953 .await
12954 .unwrap();
12955 let result = conn.execute("INSERT INTO t1 VALUES (2, 'abc')").await;
12957 assert!(result.is_err());
12958 });
12959 }
12960
12961 #[test]
12966 fn conformance_041_trigger_after_insert() {
12967 asupersync::test_utils::run_test(|| async {
12968 let conn = Connection::open(":memory:").await.unwrap();
12969 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val TEXT)")
12970 .await
12971 .unwrap();
12972 conn.execute("CREATE TABLE log(msg TEXT)").await.unwrap();
12973 conn.execute(
12974 "CREATE TRIGGER t1_after_insert AFTER INSERT ON t1 \
12975 BEGIN INSERT INTO log VALUES ('inserted ' || NEW.val); END",
12976 )
12977 .await
12978 .unwrap();
12979 conn.execute("INSERT INTO t1 VALUES (1, 'hello')")
12980 .await
12981 .unwrap();
12982 let r = conn.query("SELECT msg FROM log").await.unwrap();
12983 assert_eq!(r.len(), 1);
12984 assert_eq!(row_values(&r[0])[0].to_text(), "inserted hello");
12985 });
12986 }
12987
12988 #[test]
12989 fn conformance_041_trigger_before_insert() {
12990 asupersync::test_utils::run_test(|| async {
12991 let conn = Connection::open(":memory:").await.unwrap();
12992 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val INTEGER)")
12993 .await
12994 .unwrap();
12995 conn.execute("CREATE TABLE audit(action TEXT)")
12996 .await
12997 .unwrap();
12998 conn.execute(
12999 "CREATE TRIGGER t1_before BEFORE INSERT ON t1 \
13000 BEGIN INSERT INTO audit VALUES ('before_insert'); END",
13001 )
13002 .await
13003 .unwrap();
13004 conn.execute("INSERT INTO t1 VALUES (1, 42)").await.unwrap();
13005 let r = conn.query("SELECT action FROM audit").await.unwrap();
13006 assert_eq!(r.len(), 1);
13007 assert_eq!(row_values(&r[0])[0].to_text(), "before_insert");
13008 });
13009 }
13010
13011 #[test]
13012 fn conformance_041_trigger_drop() {
13013 asupersync::test_utils::run_test(|| async {
13014 let conn = Connection::open(":memory:").await.unwrap();
13015 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY)")
13016 .await
13017 .unwrap();
13018 conn.execute("CREATE TABLE log(x TEXT)").await.unwrap();
13019 conn.execute(
13020 "CREATE TRIGGER trg1 AFTER INSERT ON t1 \
13021 BEGIN INSERT INTO log VALUES ('fired'); END",
13022 )
13023 .await
13024 .unwrap();
13025 conn.execute("DROP TRIGGER trg1").await.unwrap();
13026 conn.execute("INSERT INTO t1 VALUES (1)").await.unwrap();
13027 let r = conn.query("SELECT x FROM log").await.unwrap();
13028 assert_eq!(r.len(), 0);
13030 });
13031 }
13032
13033 #[test]
13038 fn conformance_042_autoincrement_basic() {
13039 asupersync::test_utils::run_test(|| async {
13040 let conn = Connection::open(":memory:").await.unwrap();
13041 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT, val TEXT)")
13042 .await
13043 .unwrap();
13044 conn.execute("INSERT INTO t1(val) VALUES ('a')")
13045 .await
13046 .unwrap();
13047 conn.execute("INSERT INTO t1(val) VALUES ('b')")
13048 .await
13049 .unwrap();
13050 conn.execute("INSERT INTO t1(val) VALUES ('c')")
13051 .await
13052 .unwrap();
13053 let r = conn
13054 .query("SELECT id, val FROM t1 ORDER BY id")
13055 .await
13056 .unwrap();
13057 assert_eq!(r.len(), 3);
13058 assert_eq!(row_values(&r[0])[0].to_text(), "1");
13059 assert_eq!(row_values(&r[1])[0].to_text(), "2");
13060 assert_eq!(row_values(&r[2])[0].to_text(), "3");
13061 });
13062 }
13063
13064 #[test]
13065 fn conformance_042_autoincrement_after_delete() {
13066 asupersync::test_utils::run_test(|| async {
13067 let conn = Connection::open(":memory:").await.unwrap();
13068 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT, val TEXT)")
13069 .await
13070 .unwrap();
13071 conn.execute("INSERT INTO t1(val) VALUES ('a')")
13072 .await
13073 .unwrap();
13074 conn.execute("INSERT INTO t1(val) VALUES ('b')")
13075 .await
13076 .unwrap();
13077 conn.execute("DELETE FROM t1 WHERE id = 2").await.unwrap();
13079 conn.execute("INSERT INTO t1(val) VALUES ('c')")
13081 .await
13082 .unwrap();
13083 let r = conn.query("SELECT id FROM t1 ORDER BY id").await.unwrap();
13084 assert_eq!(row_values(&r[0])[0].to_text(), "1");
13085 assert_eq!(row_values(&r[1])[0].to_text(), "3");
13086 });
13087 }
13088
13089 #[test]
13090 fn conformance_042_sqlite_sequence_table() {
13091 asupersync::test_utils::run_test(|| async {
13092 let conn = Connection::open(":memory:").await.unwrap();
13093 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT, val TEXT)")
13094 .await
13095 .unwrap();
13096 conn.execute("INSERT INTO t1(val) VALUES ('x')")
13097 .await
13098 .unwrap();
13099 conn.execute("INSERT INTO t1(val) VALUES ('y')")
13100 .await
13101 .unwrap();
13102 let r = conn
13104 .query("SELECT seq FROM sqlite_sequence WHERE name = 't1'")
13105 .await
13106 .unwrap();
13107 assert_eq!(r.len(), 1);
13108 assert_eq!(row_values(&r[0])[0].to_text(), "2");
13109 });
13110 }
13111
13112 #[test]
13117 fn conformance_043_default_literal() {
13118 asupersync::test_utils::run_test(|| async {
13119 let conn = Connection::open(":memory:").await.unwrap();
13120 conn.execute(
13121 "CREATE TABLE t1(\
13122 id INTEGER PRIMARY KEY, \
13123 status TEXT DEFAULT 'active', \
13124 count INTEGER DEFAULT 0)",
13125 )
13126 .await
13127 .unwrap();
13128 conn.execute("INSERT INTO t1(id) VALUES (1)").await.unwrap();
13129 let r = conn
13130 .query("SELECT status, count FROM t1 WHERE id = 1")
13131 .await
13132 .unwrap();
13133 assert_eq!(row_values(&r[0])[0].to_text(), "active");
13134 assert_eq!(row_values(&r[0])[1].to_text(), "0");
13135 });
13136 }
13137
13138 #[test]
13139 fn conformance_043_default_null() {
13140 asupersync::test_utils::run_test(|| async {
13141 let conn = Connection::open(":memory:").await.unwrap();
13142 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val TEXT)")
13143 .await
13144 .unwrap();
13145 conn.execute("INSERT INTO t1(id) VALUES (1)").await.unwrap();
13147 let r = conn.query("SELECT val FROM t1 WHERE id = 1").await.unwrap();
13148 assert!(row_values(&r[0])[0].is_null());
13149 });
13150 }
13151
13152 #[test]
13153 fn conformance_043_default_override() {
13154 asupersync::test_utils::run_test(|| async {
13155 let conn = Connection::open(":memory:").await.unwrap();
13156 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val TEXT DEFAULT 'default_val')")
13157 .await
13158 .unwrap();
13159 conn.execute("INSERT INTO t1 VALUES (1, 'custom')")
13161 .await
13162 .unwrap();
13163 let r = conn.query("SELECT val FROM t1 WHERE id = 1").await.unwrap();
13164 assert_eq!(row_values(&r[0])[0].to_text(), "custom");
13165 });
13166 }
13167
13168 #[test]
13173 fn conformance_044_order_by_expression() {
13174 asupersync::test_utils::run_test(|| async {
13175 let conn = Connection::open(":memory:").await.unwrap();
13176 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val INTEGER)")
13177 .await
13178 .unwrap();
13179 conn.execute("INSERT INTO t1 VALUES (1, 10), (2, -5), (3, 3)")
13180 .await
13181 .unwrap();
13182 let r = conn
13183 .query("SELECT id, ABS(val) as a FROM t1 ORDER BY ABS(val)")
13184 .await
13185 .unwrap();
13186 assert_eq!(row_values(&r[0])[0].to_text(), "3"); assert_eq!(row_values(&r[1])[0].to_text(), "2"); assert_eq!(row_values(&r[2])[0].to_text(), "1"); });
13190 }
13191
13192 #[test]
13193 fn conformance_044_order_by_column_index() {
13194 asupersync::test_utils::run_test(|| async {
13195 let conn = Connection::open(":memory:").await.unwrap();
13196 conn.execute("CREATE TABLE t1(a TEXT, b INTEGER)")
13197 .await
13198 .unwrap();
13199 conn.execute("INSERT INTO t1 VALUES ('x', 3), ('y', 1), ('z', 2)")
13200 .await
13201 .unwrap();
13202 let r = conn.query("SELECT a, b FROM t1 ORDER BY 2").await.unwrap();
13204 assert_eq!(row_values(&r[0])[0].to_text(), "y"); assert_eq!(row_values(&r[1])[0].to_text(), "z"); assert_eq!(row_values(&r[2])[0].to_text(), "x"); });
13208 }
13209
13210 #[test]
13211 fn conformance_044_order_by_alias() {
13212 asupersync::test_utils::run_test(|| async {
13213 let conn = Connection::open(":memory:").await.unwrap();
13214 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, first TEXT, last TEXT)")
13215 .await
13216 .unwrap();
13217 conn.execute(
13218 "INSERT INTO t1 VALUES (1, 'Bob', 'Smith'), \
13219 (2, 'Alice', 'Jones'), (3, 'Charlie', 'Adams')",
13220 )
13221 .await
13222 .unwrap();
13223 let r = conn
13225 .query("SELECT first || ' ' || last AS full_name FROM t1 ORDER BY full_name")
13226 .await
13227 .unwrap();
13228 assert_eq!(row_values(&r[0])[0].to_text(), "Alice Jones");
13229 assert_eq!(row_values(&r[1])[0].to_text(), "Bob Smith");
13230 assert_eq!(row_values(&r[2])[0].to_text(), "Charlie Adams");
13231 });
13232 }
13233
13234 #[test]
13237 fn conformance_048_abs() {
13238 asupersync::test_utils::run_test(|| async {
13239 let conn = Connection::open(":memory:").await.unwrap();
13240 let r = conn
13241 .query("SELECT abs(-42), abs(3.14), abs(0)")
13242 .await
13243 .unwrap();
13244 assert_eq!(row_values(&r[0])[0].to_text(), "42");
13245 assert_eq!(row_values(&r[0])[1].to_text(), "3.14");
13246 assert_eq!(row_values(&r[0])[2].to_text(), "0");
13247 });
13248 }
13249
13250 #[test]
13251 fn conformance_048_abs_null() {
13252 asupersync::test_utils::run_test(|| async {
13253 let conn = Connection::open(":memory:").await.unwrap();
13254 let r = conn.query("SELECT abs(NULL)").await.unwrap();
13255 assert!(row_values(&r[0])[0].is_null());
13256 });
13257 }
13258
13259 #[test]
13260 fn conformance_048_round() {
13261 asupersync::test_utils::run_test(|| async {
13262 let conn = Connection::open(":memory:").await.unwrap();
13263 let r = conn
13264 .query("SELECT round(3.14159), round(3.14159, 2), round(3.5)")
13265 .await
13266 .unwrap();
13267 assert_eq!(row_values(&r[0])[0].to_text(), "3.0");
13268 assert_eq!(row_values(&r[0])[1].to_text(), "3.14");
13269 assert_eq!(row_values(&r[0])[2].to_text(), "4.0");
13270 });
13271 }
13272
13273 #[test]
13274 fn conformance_048_sign() {
13275 asupersync::test_utils::run_test(|| async {
13276 let conn = Connection::open(":memory:").await.unwrap();
13277 let r = conn
13278 .query("SELECT sign(-5), sign(0), sign(42), sign(NULL)")
13279 .await
13280 .unwrap();
13281 assert_eq!(row_values(&r[0])[0].to_text(), "-1");
13282 assert_eq!(row_values(&r[0])[1].to_text(), "0");
13283 assert_eq!(row_values(&r[0])[2].to_text(), "1");
13284 assert!(row_values(&r[0])[3].is_null());
13285 });
13286 }
13287
13288 #[test]
13289 fn conformance_048_trig_basic() {
13290 asupersync::test_utils::run_test(|| async {
13291 let conn = Connection::open(":memory:").await.unwrap();
13292 let r = conn.query("SELECT sin(0), cos(0), tan(0)").await.unwrap();
13293 assert_eq!(row_values(&r[0])[0].to_text(), "0.0");
13294 assert_eq!(row_values(&r[0])[1].to_text(), "1.0");
13295 assert_eq!(row_values(&r[0])[2].to_text(), "0.0");
13296 });
13297 }
13298
13299 #[test]
13300 fn conformance_048_acos_asin_atan() {
13301 asupersync::test_utils::run_test(|| async {
13302 let conn = Connection::open(":memory:").await.unwrap();
13303 let r = conn
13304 .query("SELECT acos(1), asin(0), atan(0)")
13305 .await
13306 .unwrap();
13307 assert_eq!(row_values(&r[0])[0].to_text(), "0.0");
13308 assert_eq!(row_values(&r[0])[1].to_text(), "0.0");
13309 assert_eq!(row_values(&r[0])[2].to_text(), "0.0");
13310 });
13311 }
13312
13313 #[test]
13314 fn conformance_048_acos_domain_error() {
13315 asupersync::test_utils::run_test(|| async {
13316 let conn = Connection::open(":memory:").await.unwrap();
13317 let r = conn.query("SELECT acos(2.0)").await.unwrap();
13318 assert!(row_values(&r[0])[0].is_null());
13319 });
13320 }
13321
13322 #[test]
13323 fn conformance_048_sqrt() {
13324 asupersync::test_utils::run_test(|| async {
13325 let conn = Connection::open(":memory:").await.unwrap();
13326 let r = conn.query("SELECT sqrt(144), sqrt(2)").await.unwrap();
13327 assert_eq!(row_values(&r[0])[0].to_text(), "12.0");
13328 let v = row_values(&r[0])[1].to_text();
13329 assert!(v.starts_with("1.41421356"), "got {v}");
13330 });
13331 }
13332
13333 #[test]
13334 fn conformance_048_sqrt_negative() {
13335 asupersync::test_utils::run_test(|| async {
13336 let conn = Connection::open(":memory:").await.unwrap();
13337 let r = conn.query("SELECT sqrt(-1)").await.unwrap();
13338 assert!(row_values(&r[0])[0].is_null());
13339 });
13340 }
13341
13342 #[test]
13343 fn conformance_048_pow() {
13344 asupersync::test_utils::run_test(|| async {
13345 let conn = Connection::open(":memory:").await.unwrap();
13346 let r = conn.query("SELECT pow(2, 10), power(3, 2)").await.unwrap();
13347 assert_eq!(row_values(&r[0])[0].to_text(), "1024.0");
13348 assert_eq!(row_values(&r[0])[1].to_text(), "9.0");
13349 });
13350 }
13351
13352 #[test]
13353 fn conformance_048_exp_ln() {
13354 asupersync::test_utils::run_test(|| async {
13355 let conn = Connection::open(":memory:").await.unwrap();
13356 let r = conn.query("SELECT exp(0), ln(1)").await.unwrap();
13357 assert_eq!(row_values(&r[0])[0].to_text(), "1.0");
13358 assert_eq!(row_values(&r[0])[1].to_text(), "0.0");
13359 });
13360 }
13361
13362 #[test]
13363 fn conformance_048_log_variants() {
13364 asupersync::test_utils::run_test(|| async {
13365 let conn = Connection::open(":memory:").await.unwrap();
13366 let r = conn
13367 .query("SELECT log(100), log10(1000), log2(8)")
13368 .await
13369 .unwrap();
13370 assert_eq!(row_values(&r[0])[0].to_text(), "2.0");
13371 assert_eq!(row_values(&r[0])[1].to_text(), "3.0");
13372 assert_eq!(row_values(&r[0])[2].to_text(), "3.0");
13373 });
13374 }
13375
13376 #[test]
13377 fn conformance_048_log_two_arg() {
13378 asupersync::test_utils::run_test(|| async {
13379 let conn = Connection::open(":memory:").await.unwrap();
13380 let r = conn.query("SELECT log(2, 8)").await.unwrap();
13381 assert_eq!(row_values(&r[0])[0].to_text(), "3.0");
13382 });
13383 }
13384
13385 #[test]
13386 fn conformance_048_ln_negative_null() {
13387 asupersync::test_utils::run_test(|| async {
13388 let conn = Connection::open(":memory:").await.unwrap();
13389 let r = conn.query("SELECT ln(-1), ln(0)").await.unwrap();
13390 assert!(row_values(&r[0])[0].is_null());
13391 assert!(row_values(&r[0])[1].is_null());
13392 });
13393 }
13394
13395 #[test]
13396 fn conformance_048_pi() {
13397 asupersync::test_utils::run_test(|| async {
13398 let conn = Connection::open(":memory:").await.unwrap();
13399 let r = conn.query("SELECT pi()").await.unwrap();
13400 let v = row_values(&r[0])[0].to_text();
13401 assert!(v.starts_with("3.14159265"), "got {v}");
13402 });
13403 }
13404
13405 #[test]
13406 fn conformance_048_ceil_floor_trunc() {
13407 asupersync::test_utils::run_test(|| async {
13408 let conn = Connection::open(":memory:").await.unwrap();
13409 let r = conn
13410 .query("SELECT ceil(1.2), floor(1.7), trunc(2.9)")
13411 .await
13412 .unwrap();
13413 assert_eq!(row_values(&r[0])[0].to_text(), "2.0");
13414 assert_eq!(row_values(&r[0])[1].to_text(), "1.0");
13415 assert_eq!(row_values(&r[0])[2].to_text(), "2.0");
13416 });
13417 }
13418
13419 #[test]
13420 fn conformance_048_ceil_floor_negative() {
13421 asupersync::test_utils::run_test(|| async {
13422 let conn = Connection::open(":memory:").await.unwrap();
13423 let r = conn
13424 .query("SELECT ceil(-1.2), floor(-1.2), trunc(-2.9)")
13425 .await
13426 .unwrap();
13427 assert_eq!(row_values(&r[0])[0].to_text(), "-1.0");
13428 assert_eq!(row_values(&r[0])[1].to_text(), "-2.0");
13429 assert_eq!(row_values(&r[0])[2].to_text(), "-2.0");
13430 });
13431 }
13432
13433 #[test]
13434 fn conformance_048_degrees_radians() {
13435 asupersync::test_utils::run_test(|| async {
13436 let conn = Connection::open(":memory:").await.unwrap();
13437 let r = conn
13438 .query("SELECT degrees(pi()), radians(180)")
13439 .await
13440 .unwrap();
13441 assert_eq!(row_values(&r[0])[0].to_text(), "180.0");
13442 let v = row_values(&r[0])[1].to_text();
13443 assert!(v.starts_with("3.14159265"), "got {v}");
13444 });
13445 }
13446
13447 #[test]
13448 fn conformance_048_mod_func() {
13449 asupersync::test_utils::run_test(|| async {
13450 let conn = Connection::open(":memory:").await.unwrap();
13451 let r = conn.query("SELECT mod(10, 3), mod(10, 0)").await.unwrap();
13452 assert_eq!(row_values(&r[0])[0].to_text(), "1.0");
13453 assert!(row_values(&r[0])[1].is_null());
13454 });
13455 }
13456
13457 #[test]
13458 fn conformance_048_atan2() {
13459 asupersync::test_utils::run_test(|| async {
13460 let conn = Connection::open(":memory:").await.unwrap();
13461 let r = conn.query("SELECT atan2(0, 1), atan2(1, 0)").await.unwrap();
13462 assert_eq!(row_values(&r[0])[0].to_text(), "0.0");
13463 let v = row_values(&r[0])[1].to_text();
13464 assert!(v.starts_with("1.57079632"), "got {v}");
13465 });
13466 }
13467
13468 #[test]
13469 fn conformance_048_math_null_propagation() {
13470 asupersync::test_utils::run_test(|| async {
13471 let conn = Connection::open(":memory:").await.unwrap();
13472 let r = conn
13473 .query("SELECT sin(NULL), sqrt(NULL), pow(NULL, 2), mod(NULL, 3)")
13474 .await
13475 .unwrap();
13476 assert!(row_values(&r[0])[0].is_null());
13477 assert!(row_values(&r[0])[1].is_null());
13478 assert!(row_values(&r[0])[2].is_null());
13479 assert!(row_values(&r[0])[3].is_null());
13480 });
13481 }
13482
13483 #[test]
13484 fn conformance_048_math_with_table() {
13485 asupersync::test_utils::run_test(|| async {
13486 let conn = Connection::open(":memory:").await.unwrap();
13487 conn.execute("CREATE TABLE nums(x REAL)").await.unwrap();
13488 conn.execute("INSERT INTO nums VALUES (4.0),(9.0),(16.0),(25.0)")
13489 .await
13490 .unwrap();
13491 let r = conn
13492 .query("SELECT sqrt(x) FROM nums ORDER BY x")
13493 .await
13494 .unwrap();
13495 assert_eq!(r.len(), 4);
13496 assert_eq!(row_values(&r[0])[0].to_text(), "2.0");
13497 assert_eq!(row_values(&r[1])[0].to_text(), "3.0");
13498 assert_eq!(row_values(&r[2])[0].to_text(), "4.0");
13499 assert_eq!(row_values(&r[3])[0].to_text(), "5.0");
13500 });
13501 }
13502
13503 #[test]
13504 fn conformance_048_hyperbolic() {
13505 asupersync::test_utils::run_test(|| async {
13506 let conn = Connection::open(":memory:").await.unwrap();
13507 let r = conn
13508 .query("SELECT sinh(0), cosh(0), tanh(0)")
13509 .await
13510 .unwrap();
13511 assert_eq!(row_values(&r[0])[0].to_text(), "0.0");
13512 assert_eq!(row_values(&r[0])[1].to_text(), "1.0");
13513 assert_eq!(row_values(&r[0])[2].to_text(), "0.0");
13514 });
13515 }
13516
13517 #[test]
13520 fn conformance_049_length() {
13521 asupersync::test_utils::run_test(|| async {
13522 let conn = Connection::open(":memory:").await.unwrap();
13523 let r = conn
13524 .query("SELECT length('hello'), length(''), length(NULL)")
13525 .await
13526 .unwrap();
13527 assert_eq!(row_values(&r[0])[0].to_text(), "5");
13528 assert_eq!(row_values(&r[0])[1].to_text(), "0");
13529 assert!(row_values(&r[0])[2].is_null());
13530 });
13531 }
13532
13533 #[test]
13534 fn conformance_049_upper_lower() {
13535 asupersync::test_utils::run_test(|| async {
13536 let conn = Connection::open(":memory:").await.unwrap();
13537 let r = conn
13538 .query("SELECT upper('hello'), lower('WORLD')")
13539 .await
13540 .unwrap();
13541 assert_eq!(row_values(&r[0])[0].to_text(), "HELLO");
13542 assert_eq!(row_values(&r[0])[1].to_text(), "world");
13543 });
13544 }
13545
13546 #[test]
13547 fn conformance_049_typeof() {
13548 asupersync::test_utils::run_test(|| async {
13549 let conn = Connection::open(":memory:").await.unwrap();
13550 let r = conn
13551 .query("SELECT typeof(42), typeof(3.14), typeof('hi'), typeof(NULL), typeof(X'00')")
13552 .await
13553 .unwrap();
13554 assert_eq!(row_values(&r[0])[0].to_text(), "integer");
13555 assert_eq!(row_values(&r[0])[1].to_text(), "real");
13556 assert_eq!(row_values(&r[0])[2].to_text(), "text");
13557 assert_eq!(row_values(&r[0])[3].to_text(), "null");
13558 assert_eq!(row_values(&r[0])[4].to_text(), "blob");
13559 });
13560 }
13561
13562 #[test]
13563 fn conformance_049_max_min_scalar() {
13564 asupersync::test_utils::run_test(|| async {
13565 let conn = Connection::open(":memory:").await.unwrap();
13566 let r = conn
13567 .query("SELECT max(1, 5, 3), min(10, 2, 7)")
13568 .await
13569 .unwrap();
13570 assert_eq!(row_values(&r[0])[0].to_text(), "5");
13571 assert_eq!(row_values(&r[0])[1].to_text(), "2");
13572 });
13573 }
13574
13575 #[test]
13576 fn conformance_049_total_changes() {
13577 asupersync::test_utils::run_test(|| async {
13578 let conn = Connection::open(":memory:").await.unwrap();
13579 conn.execute("CREATE TABLE t1(x INTEGER)").await.unwrap();
13580 conn.execute("INSERT INTO t1 VALUES (1),(2),(3)")
13581 .await
13582 .unwrap();
13583 let r = conn.query("SELECT changes()").await.unwrap();
13584 assert_eq!(row_values(&r[0])[0].to_text(), "3");
13585 });
13586 }
13587
13588 #[test]
13591 fn conformance_050_string_concat() {
13592 asupersync::test_utils::run_test(|| async {
13593 let conn = Connection::open(":memory:").await.unwrap();
13594 let r = conn
13595 .query("SELECT 'hello' || ' ' || 'world'")
13596 .await
13597 .unwrap();
13598 assert_eq!(row_values(&r[0])[0].to_text(), "hello world");
13599 });
13600 }
13601
13602 #[test]
13603 fn conformance_050_concat_with_numbers() {
13604 asupersync::test_utils::run_test(|| async {
13605 let conn = Connection::open(":memory:").await.unwrap();
13606 let r = conn.query("SELECT 'val=' || 42").await.unwrap();
13607 assert_eq!(row_values(&r[0])[0].to_text(), "val=42");
13608 });
13609 }
13610
13611 #[test]
13612 fn conformance_050_concat_null() {
13613 asupersync::test_utils::run_test(|| async {
13614 let conn = Connection::open(":memory:").await.unwrap();
13615 let r = conn.query("SELECT 'abc' || NULL").await.unwrap();
13616 assert!(row_values(&r[0])[0].is_null());
13617 });
13618 }
13619
13620 #[test]
13621 fn conformance_050_numeric_string_comparison() {
13622 asupersync::test_utils::run_test(|| async {
13623 let conn = Connection::open(":memory:").await.unwrap();
13624 let r = conn
13625 .query("SELECT CASE WHEN 10 = '10' THEN 'equal' ELSE 'not_equal' END")
13626 .await
13627 .unwrap();
13628 assert_eq!(row_values(&r[0])[0].to_text(), "not_equal");
13629 });
13630 }
13631
13632 #[test]
13633 fn conformance_050_mixed_arithmetic() {
13634 asupersync::test_utils::run_test(|| async {
13635 let conn = Connection::open(":memory:").await.unwrap();
13636 let r = conn
13637 .query("SELECT 1 + 2.5, 10 / 3, 10.0 / 3")
13638 .await
13639 .unwrap();
13640 assert_eq!(row_values(&r[0])[0].to_text(), "3.5");
13641 assert_eq!(row_values(&r[0])[1].to_text(), "3");
13642 let v = row_values(&r[0])[2].to_text();
13643 assert!(v.starts_with("3.333"), "got {v}");
13644 });
13645 }
13646
13647 #[test]
13648 fn conformance_050_cast_types() {
13649 asupersync::test_utils::run_test(|| async {
13650 let conn = Connection::open(":memory:").await.unwrap();
13651 let r = conn
13652 .query("SELECT CAST('123' AS INTEGER), CAST(3.14 AS INTEGER), CAST(42 AS TEXT)")
13653 .await
13654 .unwrap();
13655 assert_eq!(row_values(&r[0])[0].to_text(), "123");
13656 assert_eq!(row_values(&r[0])[1].to_text(), "3");
13657 assert_eq!(row_values(&r[0])[2].to_text(), "42");
13658 });
13659 }
13660
13661 #[test]
13664 fn conformance_051_unary_minus() {
13665 asupersync::test_utils::run_test(|| async {
13666 let conn = Connection::open(":memory:").await.unwrap();
13667 let r = conn.query("SELECT -(-5), -(3.14)").await.unwrap();
13668 assert_eq!(row_values(&r[0])[0].to_text(), "5");
13669 assert_eq!(row_values(&r[0])[1].to_text(), "-3.14");
13670 });
13671 }
13672
13673 #[test]
13674 fn conformance_051_modulo_operator() {
13675 asupersync::test_utils::run_test(|| async {
13676 let conn = Connection::open(":memory:").await.unwrap();
13677 let r = conn.query("SELECT 17 % 5, -7 % 3").await.unwrap();
13678 assert_eq!(row_values(&r[0])[0].to_text(), "2");
13679 assert_eq!(row_values(&r[0])[1].to_text(), "-1");
13680 });
13681 }
13682
13683 #[test]
13684 fn conformance_051_boolean_expressions() {
13685 asupersync::test_utils::run_test(|| async {
13686 let conn = Connection::open(":memory:").await.unwrap();
13687 let r = conn
13688 .query("SELECT 1 AND 1, 1 AND 0, 0 OR 1, 0 OR 0, NOT 0, NOT 1")
13689 .await
13690 .unwrap();
13691 assert_eq!(row_values(&r[0])[0].to_text(), "1");
13692 assert_eq!(row_values(&r[0])[1].to_text(), "0");
13693 assert_eq!(row_values(&r[0])[2].to_text(), "1");
13694 assert_eq!(row_values(&r[0])[3].to_text(), "0");
13695 assert_eq!(row_values(&r[0])[4].to_text(), "1");
13696 assert_eq!(row_values(&r[0])[5].to_text(), "0");
13697 });
13698 }
13699
13700 #[test]
13701 fn conformance_051_comparison_operators() {
13702 asupersync::test_utils::run_test(|| async {
13703 let conn = Connection::open(":memory:").await.unwrap();
13704 let r = conn
13705 .query("SELECT 5 > 3, 5 < 3, 5 >= 5, 5 <= 4, 5 != 3, 5 == 5")
13706 .await
13707 .unwrap();
13708 assert_eq!(row_values(&r[0])[0].to_text(), "1");
13709 assert_eq!(row_values(&r[0])[1].to_text(), "0");
13710 assert_eq!(row_values(&r[0])[2].to_text(), "1");
13711 assert_eq!(row_values(&r[0])[3].to_text(), "0");
13712 assert_eq!(row_values(&r[0])[4].to_text(), "1");
13713 assert_eq!(row_values(&r[0])[5].to_text(), "1");
13714 });
13715 }
13716
13717 #[test]
13720 fn conformance_052_multi_table_join_aggregate() {
13721 asupersync::test_utils::run_test(|| async {
13722 let conn = Connection::open(":memory:").await.unwrap();
13723 conn.execute("CREATE TABLE departments(id INTEGER PRIMARY KEY, name TEXT)")
13724 .await
13725 .unwrap();
13726 conn.execute("CREATE TABLE employees(id INTEGER PRIMARY KEY, name TEXT, dept_id INTEGER, salary REAL)")
13727 .await
13728 .unwrap();
13729 conn.execute("INSERT INTO departments VALUES (1,'Engineering'),(2,'Sales'),(3,'HR')")
13730 .await
13731 .unwrap();
13732 conn.execute("INSERT INTO employees VALUES (1,'Alice',1,80000),(2,'Bob',1,90000),(3,'Charlie',2,70000),(4,'Diana',2,75000),(5,'Eve',3,60000)")
13733 .await
13734 .unwrap();
13735 let r = conn
13736 .query("SELECT d.name, COUNT(e.id), SUM(e.salary) FROM departments d JOIN employees e ON d.id = e.dept_id GROUP BY d.name ORDER BY d.name")
13737 .await
13738 .unwrap();
13739 assert_eq!(r.len(), 3);
13740 assert_eq!(row_values(&r[0])[0].to_text(), "Engineering");
13741 assert_eq!(row_values(&r[0])[1].to_text(), "2");
13742 assert_eq!(row_values(&r[0])[2].to_text(), "170000.0");
13743 assert_eq!(row_values(&r[1])[0].to_text(), "HR");
13744 assert_eq!(row_values(&r[1])[1].to_text(), "1");
13745 assert_eq!(row_values(&r[1])[2].to_text(), "60000.0");
13746 assert_eq!(row_values(&r[2])[0].to_text(), "Sales");
13747 assert_eq!(row_values(&r[2])[1].to_text(), "2");
13748 assert_eq!(row_values(&r[2])[2].to_text(), "145000.0");
13749 });
13750 }
13751
13752 #[test]
13753 fn conformance_052_subquery_in_where() {
13754 asupersync::test_utils::run_test(|| async {
13755 let conn = Connection::open(":memory:").await.unwrap();
13756 conn.execute("CREATE TABLE products(id INTEGER PRIMARY KEY, name TEXT, price REAL)")
13757 .await
13758 .unwrap();
13759 conn.execute(
13760 "INSERT INTO products VALUES (1,'A',10.0),(2,'B',20.0),(3,'C',30.0),(4,'D',15.0)",
13761 )
13762 .await
13763 .unwrap();
13764 let r = conn
13765 .query("SELECT name FROM products WHERE price > (SELECT AVG(price) FROM products) ORDER BY name")
13766 .await
13767 .unwrap();
13768 assert_eq!(r.len(), 2);
13769 assert_eq!(row_values(&r[0])[0].to_text(), "B");
13770 assert_eq!(row_values(&r[1])[0].to_text(), "C");
13771 });
13772 }
13773
13774 #[test]
13775 fn conformance_052_insert_with_subquery() {
13776 asupersync::test_utils::run_test(|| async {
13777 let conn = Connection::open(":memory:").await.unwrap();
13778 conn.execute("CREATE TABLE src(val INTEGER)").await.unwrap();
13779 conn.execute("CREATE TABLE dst(val INTEGER)").await.unwrap();
13780 conn.execute("INSERT INTO src VALUES (1),(2),(3),(4),(5)")
13781 .await
13782 .unwrap();
13783 conn.execute("INSERT INTO dst SELECT val FROM src WHERE val > 3")
13784 .await
13785 .unwrap();
13786 let r = conn
13787 .query("SELECT val FROM dst ORDER BY val")
13788 .await
13789 .unwrap();
13790 assert_eq!(r.len(), 2);
13791 assert_eq!(row_values(&r[0])[0].to_text(), "4");
13792 assert_eq!(row_values(&r[1])[0].to_text(), "5");
13793 });
13794 }
13795
13796 #[test]
13801 fn conformance_053_group_concat_basic() {
13802 asupersync::test_utils::run_test(|| async {
13803 let conn = Connection::open(":memory:").await.unwrap();
13804 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, grp TEXT, val TEXT)")
13805 .await
13806 .unwrap();
13807 conn.execute("INSERT INTO t1 VALUES (1,'a','x'),(2,'a','y'),(3,'b','z'),(4,'a','w')")
13808 .await
13809 .unwrap();
13810 let r = conn
13811 .query("SELECT grp, GROUP_CONCAT(val) FROM t1 GROUP BY grp ORDER BY grp")
13812 .await
13813 .unwrap();
13814 assert_eq!(r.len(), 2);
13815 let a_vals = row_values(&r[0])[1].to_text();
13817 assert!(a_vals.contains('x'));
13818 assert!(a_vals.contains('y'));
13819 assert!(a_vals.contains('w'));
13820 assert_eq!(row_values(&r[1])[1].to_text(), "z");
13821 });
13822 }
13823
13824 #[test]
13825 fn conformance_053_group_concat_custom_separator() {
13826 asupersync::test_utils::run_test(|| async {
13827 let conn = Connection::open(":memory:").await.unwrap();
13828 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val TEXT)")
13829 .await
13830 .unwrap();
13831 conn.execute("INSERT INTO t1 VALUES (1,'a'),(2,'b'),(3,'c')")
13832 .await
13833 .unwrap();
13834 let r = conn
13835 .query("SELECT GROUP_CONCAT(val, ' | ') FROM t1")
13836 .await
13837 .unwrap();
13838 let result = row_values(&r[0])[0].to_text();
13839 assert!(result.contains('a'));
13841 assert!(result.contains('b'));
13842 assert!(result.contains('c'));
13843 assert!(result.contains('|'));
13844 });
13845 }
13846
13847 #[test]
13848 fn conformance_053_group_concat_null_skip() {
13849 asupersync::test_utils::run_test(|| async {
13850 let conn = Connection::open(":memory:").await.unwrap();
13851 conn.execute("CREATE TABLE t1(val TEXT)").await.unwrap();
13852 conn.execute("INSERT INTO t1 VALUES ('a'),(NULL),('c')")
13853 .await
13854 .unwrap();
13855 let r = conn
13856 .query("SELECT GROUP_CONCAT(val) FROM t1")
13857 .await
13858 .unwrap();
13859 let result = row_values(&r[0])[0].to_text();
13860 assert!(result.contains('a'));
13862 assert!(result.contains('c'));
13863 assert!(!result.contains("NULL"));
13864 });
13865 }
13866
13867 #[test]
13872 fn conformance_054_pragma_table_info() {
13873 asupersync::test_utils::run_test(|| async {
13874 let conn = Connection::open(":memory:").await.unwrap();
13875 conn.execute(
13876 "CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT NOT NULL, score REAL DEFAULT 0.0)",
13877 )
13878 .await
13879 .unwrap();
13880 let r = conn.query("PRAGMA table_info(t1)").await.unwrap();
13881 assert!(r.len() >= 3);
13882 let col_names: Vec<String> = r.iter().map(|row| row_values(row)[1].to_text()).collect();
13884 assert!(col_names.contains(&"id".to_owned()));
13885 assert!(col_names.contains(&"name".to_owned()));
13886 assert!(col_names.contains(&"score".to_owned()));
13887 });
13888 }
13889
13890 #[test]
13891 fn pragma_table_info_preserves_declared_type_arguments() {
13892 asupersync::test_utils::run_test(|| async {
13893 let conn = Connection::open(":memory:").await.unwrap();
13894 conn.execute("CREATE TABLE metrics(amount DECIMAL(10, 2), name VARCHAR(255))")
13895 .await
13896 .unwrap();
13897 let rows = conn.query("PRAGMA table_info(metrics)").await.unwrap();
13898 let amount = rows
13899 .iter()
13900 .find(|row| row_values(row)[1].to_text() == "amount")
13901 .expect("amount column metadata");
13902 let name = rows
13903 .iter()
13904 .find(|row| row_values(row)[1].to_text() == "name")
13905 .expect("name column metadata");
13906 assert_eq!(row_values(amount)[2].to_text(), "DECIMAL(10, 2)");
13907 assert_eq!(row_values(name)[2].to_text(), "VARCHAR(255)");
13908 });
13909 }
13910
13911 #[test]
13912 fn conformance_054_pragma_user_version() {
13913 asupersync::test_utils::run_test(|| async {
13914 let conn = Connection::open(":memory:").await.unwrap();
13915 conn.execute("PRAGMA user_version = 42").await.unwrap();
13916 let r = conn.query("PRAGMA user_version").await.unwrap();
13917 assert_eq!(r.len(), 1);
13918 assert_eq!(row_values(&r[0])[0].to_text(), "42");
13919 });
13920 }
13921
13922 #[test]
13923 fn conformance_054_pragma_table_list() {
13924 asupersync::test_utils::run_test(|| async {
13925 let conn = Connection::open(":memory:").await.unwrap();
13926 conn.execute("CREATE TABLE alpha(x INTEGER)").await.unwrap();
13927 conn.execute("CREATE TABLE beta(y TEXT)").await.unwrap();
13928 let r = conn
13930 .query("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name")
13931 .await
13932 .unwrap();
13933 assert!(r.len() >= 2);
13934 let names: Vec<String> = r.iter().map(|row| row_values(row)[0].to_text()).collect();
13935 assert!(names.contains(&"alpha".to_owned()));
13936 assert!(names.contains(&"beta".to_owned()));
13937 });
13938 }
13939
13940 #[test]
13945 fn conformance_055_nested_subquery() {
13946 asupersync::test_utils::run_test(|| async {
13947 let conn = Connection::open(":memory:").await.unwrap();
13948 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val INTEGER)")
13949 .await
13950 .unwrap();
13951 conn.execute("INSERT INTO t1 VALUES (1,10),(2,20),(3,30),(4,40),(5,50)")
13952 .await
13953 .unwrap();
13954 let r = conn
13956 .query(
13957 "SELECT val FROM t1 WHERE val > \
13958 (SELECT AVG(val) FROM t1 WHERE val < \
13959 (SELECT MAX(val) FROM t1)) ORDER BY val",
13960 )
13961 .await
13962 .unwrap();
13963 assert_eq!(r.len(), 3);
13966 assert_eq!(row_values(&r[0])[0].to_text(), "30");
13967 assert_eq!(row_values(&r[1])[0].to_text(), "40");
13968 assert_eq!(row_values(&r[2])[0].to_text(), "50");
13969 });
13970 }
13971
13972 #[test]
13973 fn conformance_055_in_subquery() {
13974 asupersync::test_utils::run_test(|| async {
13975 let conn = Connection::open(":memory:").await.unwrap();
13976 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT)")
13977 .await
13978 .unwrap();
13979 conn.execute("CREATE TABLE t2(id INTEGER PRIMARY KEY, t1_id INTEGER)")
13980 .await
13981 .unwrap();
13982 conn.execute("INSERT INTO t1 VALUES (1,'Alice'),(2,'Bob'),(3,'Charlie')")
13983 .await
13984 .unwrap();
13985 conn.execute("INSERT INTO t2 VALUES (1,1),(2,3)")
13986 .await
13987 .unwrap();
13988 let r = conn
13989 .query("SELECT name FROM t1 WHERE id IN (SELECT t1_id FROM t2) ORDER BY name")
13990 .await
13991 .unwrap();
13992 assert_eq!(r.len(), 2);
13993 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
13994 assert_eq!(row_values(&r[1])[0].to_text(), "Charlie");
13995 });
13996 }
13997
13998 #[test]
13999 fn conformance_055_not_in_subquery() {
14000 asupersync::test_utils::run_test(|| async {
14001 let conn = Connection::open(":memory:").await.unwrap();
14002 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT)")
14003 .await
14004 .unwrap();
14005 conn.execute("CREATE TABLE t2(ref_id INTEGER)")
14006 .await
14007 .unwrap();
14008 conn.execute("INSERT INTO t1 VALUES (1,'A'),(2,'B'),(3,'C')")
14009 .await
14010 .unwrap();
14011 conn.execute("INSERT INTO t2 VALUES (1),(3)").await.unwrap();
14012 let r = conn
14013 .query("SELECT name FROM t1 WHERE id NOT IN (SELECT ref_id FROM t2)")
14014 .await
14015 .unwrap();
14016 assert_eq!(r.len(), 1);
14017 assert_eq!(row_values(&r[0])[0].to_text(), "B");
14018 });
14019 }
14020
14021 #[test]
14026 fn conformance_056_three_table_join() {
14027 asupersync::test_utils::run_test(|| async {
14028 let conn = Connection::open(":memory:").await.unwrap();
14029 conn.execute("CREATE TABLE departments(id INTEGER PRIMARY KEY, name TEXT)")
14030 .await
14031 .unwrap();
14032 conn.execute(
14033 "CREATE TABLE employees(id INTEGER PRIMARY KEY, name TEXT, dept_id INTEGER)",
14034 )
14035 .await
14036 .unwrap();
14037 conn.execute(
14038 "CREATE TABLE projects(id INTEGER PRIMARY KEY, title TEXT, emp_id INTEGER)",
14039 )
14040 .await
14041 .unwrap();
14042 conn.execute("INSERT INTO departments VALUES (1,'Eng'),(2,'Sales')")
14043 .await
14044 .unwrap();
14045 conn.execute("INSERT INTO employees VALUES (1,'Alice',1),(2,'Bob',1),(3,'Charlie',2)")
14046 .await
14047 .unwrap();
14048 conn.execute("INSERT INTO projects VALUES (1,'Widget',1),(2,'Gadget',2),(3,'Deal',3)")
14049 .await
14050 .unwrap();
14051 let r = conn
14052 .query(
14053 "SELECT d.name, e.name, p.title \
14054 FROM departments d \
14055 JOIN employees e ON e.dept_id = d.id \
14056 JOIN projects p ON p.emp_id = e.id \
14057 WHERE d.name = 'Eng' \
14058 ORDER BY e.name",
14059 )
14060 .await
14061 .unwrap();
14062 assert_eq!(r.len(), 2);
14063 assert_eq!(row_values(&r[0])[1].to_text(), "Alice");
14064 assert_eq!(row_values(&r[0])[2].to_text(), "Widget");
14065 assert_eq!(row_values(&r[1])[1].to_text(), "Bob");
14066 assert_eq!(row_values(&r[1])[2].to_text(), "Gadget");
14067 });
14068 }
14069
14070 #[test]
14071 fn conformance_056_left_join_with_aggregate() {
14072 asupersync::test_utils::run_test(|| async {
14073 let conn = Connection::open(":memory:").await.unwrap();
14074 conn.execute("CREATE TABLE teams(id INTEGER PRIMARY KEY, name TEXT)")
14075 .await
14076 .unwrap();
14077 conn.execute(
14078 "CREATE TABLE members(id INTEGER PRIMARY KEY, team_id INTEGER, name TEXT)",
14079 )
14080 .await
14081 .unwrap();
14082 conn.execute("INSERT INTO teams VALUES (1,'Alpha'),(2,'Beta'),(3,'Gamma')")
14083 .await
14084 .unwrap();
14085 conn.execute("INSERT INTO members VALUES (1,1,'A1'),(2,1,'A2'),(3,2,'B1')")
14086 .await
14087 .unwrap();
14088 let r = conn
14089 .query(
14090 "SELECT t.name, SUM(CASE WHEN m.id IS NOT NULL THEN 1 ELSE 0 END) as member_count \
14091 FROM teams t \
14092 LEFT JOIN members m ON m.team_id = t.id \
14093 GROUP BY t.id, t.name \
14094 ORDER BY t.name",
14095 )
14096 .await
14097 .unwrap();
14098 assert_eq!(r.len(), 3);
14099 assert_eq!(row_values(&r[0])[0].to_text(), "Alpha");
14100 assert_eq!(row_values(&r[0])[1].to_text(), "2");
14101 assert_eq!(row_values(&r[1])[0].to_text(), "Beta");
14102 assert_eq!(row_values(&r[1])[1].to_text(), "1");
14103 assert_eq!(row_values(&r[2])[0].to_text(), "Gamma");
14104 assert_eq!(row_values(&r[2])[1].to_text(), "0");
14105 });
14106 }
14107
14108 #[test]
14113 fn conformance_057_integer_division() {
14114 asupersync::test_utils::run_test(|| async {
14115 let conn = Connection::open(":memory:").await.unwrap();
14116 let r = conn.query("SELECT 7/2, -7/2, 1/3").await.unwrap();
14118 assert_eq!(row_values(&r[0])[0].to_text(), "3");
14119 assert_eq!(row_values(&r[0])[1].to_text(), "-3");
14120 assert_eq!(row_values(&r[0])[2].to_text(), "0");
14121 });
14122 }
14123
14124 #[test]
14125 fn conformance_057_real_division() {
14126 asupersync::test_utils::run_test(|| async {
14127 let conn = Connection::open(":memory:").await.unwrap();
14128 let r = conn.query("SELECT 7.0/2, 1.0/3.0").await.unwrap();
14129 assert_eq!(row_values(&r[0])[0].to_text(), "3.5");
14130 let third: f64 = row_values(&r[0])[1].to_text().parse().unwrap();
14132 assert!((third - 1.0 / 3.0).abs() < 1e-10);
14133 });
14134 }
14135
14136 #[test]
14137 fn conformance_057_string_concatenation_operator() {
14138 asupersync::test_utils::run_test(|| async {
14139 let conn = Connection::open(":memory:").await.unwrap();
14140 let r = conn
14141 .query("SELECT 'hello' || ' ' || 'world'")
14142 .await
14143 .unwrap();
14144 assert_eq!(row_values(&r[0])[0].to_text(), "hello world");
14145 });
14146 }
14147
14148 #[test]
14149 fn conformance_057_null_arithmetic() {
14150 asupersync::test_utils::run_test(|| async {
14151 let conn = Connection::open(":memory:").await.unwrap();
14152 let r = conn
14154 .query("SELECT NULL + 1, NULL * 5, NULL || 'text'")
14155 .await
14156 .unwrap();
14157 assert!(row_values(&r[0])[0].is_null());
14158 assert!(row_values(&r[0])[1].is_null());
14159 assert!(row_values(&r[0])[2].is_null());
14160 });
14161 }
14162
14163 #[test]
14168 fn conformance_058_correlated_subquery_in_where() {
14169 asupersync::test_utils::run_test(|| async {
14170 let conn = Connection::open(":memory:").await.unwrap();
14171 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val INTEGER)")
14172 .await
14173 .unwrap();
14174 conn.execute("CREATE TABLE t2(id INTEGER, t1_id INTEGER, score INTEGER)")
14175 .await
14176 .unwrap();
14177 conn.execute("INSERT INTO t1 VALUES (1, 100), (2, 200), (3, 300)")
14178 .await
14179 .unwrap();
14180 conn.execute("INSERT INTO t2 VALUES (1,1,10),(2,1,20),(3,2,30),(4,3,5)")
14181 .await
14182 .unwrap();
14183 let r = conn
14185 .query(
14186 "SELECT t1.id, t1.val FROM t1 WHERE (SELECT MAX(score) FROM t2 WHERE t2.t1_id = t1.id) > 15 ORDER BY t1.id",
14187 )
14188 .await
14189 .unwrap();
14190 assert_eq!(r.len(), 2);
14191 assert_eq!(row_values(&r[0])[0], SqliteValue::Integer(1));
14192 assert_eq!(row_values(&r[0])[1], SqliteValue::Integer(100));
14193 assert_eq!(row_values(&r[1])[0], SqliteValue::Integer(2));
14194 assert_eq!(row_values(&r[1])[1], SqliteValue::Integer(200));
14195 });
14196 }
14197
14198 #[test]
14199 fn conformance_058_correlated_subquery_in_select() {
14200 asupersync::test_utils::run_test(|| async {
14201 let conn = Connection::open(":memory:").await.unwrap();
14202 conn.execute(
14203 "CREATE TABLE orders(id INTEGER PRIMARY KEY, customer_id INTEGER, amount REAL)",
14204 )
14205 .await
14206 .unwrap();
14207 conn.execute("CREATE TABLE customers(id INTEGER PRIMARY KEY, name TEXT)")
14208 .await
14209 .unwrap();
14210 conn.execute("INSERT INTO customers VALUES (1, 'Alice'), (2, 'Bob')")
14211 .await
14212 .unwrap();
14213 conn.execute("INSERT INTO orders VALUES (1,1,100.0),(2,1,200.0),(3,2,50.0)")
14214 .await
14215 .unwrap();
14216 let r = conn
14218 .query(
14219 "SELECT c.name, (SELECT SUM(amount) FROM orders o WHERE o.customer_id = c.id) AS total FROM customers c ORDER BY c.name",
14220 )
14221 .await
14222 .unwrap();
14223 assert_eq!(r.len(), 2);
14224 assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
14225 assert_eq!(row_values(&r[0])[1].to_text(), "300.0");
14226 assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
14227 assert_eq!(row_values(&r[1])[1].to_text(), "50.0");
14228 });
14229 }
14230
14231 #[test]
14232 fn conformance_058_create_table_as_select() {
14233 asupersync::test_utils::run_test(|| async {
14234 let conn = Connection::open(":memory:").await.unwrap();
14235 conn.execute("CREATE TABLE src(id INTEGER PRIMARY KEY, val INTEGER)")
14236 .await
14237 .unwrap();
14238 conn.execute("INSERT INTO src VALUES (1, 100), (2, 200), (3, 300)")
14239 .await
14240 .unwrap();
14241 conn.execute(
14243 "CREATE TABLE dst AS SELECT id, val * 2 AS doubled FROM src WHERE id <= 2",
14244 )
14245 .await
14246 .unwrap();
14247 let r = conn.query("SELECT * FROM dst ORDER BY id").await.unwrap();
14248 assert_eq!(r.len(), 2);
14249 assert_eq!(row_values(&r[0])[0], SqliteValue::Integer(1));
14250 assert_eq!(row_values(&r[0])[1], SqliteValue::Integer(200));
14251 assert_eq!(row_values(&r[1])[0], SqliteValue::Integer(2));
14252 assert_eq!(row_values(&r[1])[1], SqliteValue::Integer(400));
14253 });
14254 }
14255
14256 #[test]
14261 fn conformance_059_group_by_expression() {
14262 asupersync::test_utils::run_test(|| async {
14263 let conn = Connection::open(":memory:").await.unwrap();
14264 conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val INTEGER)")
14265 .await
14266 .unwrap();
14267 conn.execute("INSERT INTO t1 VALUES (1, 100), (2, 200), (3, 300)")
14268 .await
14269 .unwrap();
14270 let r = conn
14272 .query(
14273 "SELECT val / 100 AS bucket, COUNT(*) FROM t1 GROUP BY val / 100 ORDER BY bucket",
14274 )
14275 .await
14276 .unwrap();
14277 assert_eq!(r.len(), 3);
14278 assert_eq!(row_values(&r[0])[0], SqliteValue::Integer(1));
14279 assert_eq!(row_values(&r[0])[1], SqliteValue::Integer(1));
14280 assert_eq!(row_values(&r[1])[0], SqliteValue::Integer(2));
14281 assert_eq!(row_values(&r[1])[1], SqliteValue::Integer(1));
14282 assert_eq!(row_values(&r[2])[0], SqliteValue::Integer(3));
14283 assert_eq!(row_values(&r[2])[1], SqliteValue::Integer(1));
14284 });
14285 }
14286
14287 #[test]
14288 fn conformance_059_group_by_function() {
14289 asupersync::test_utils::run_test(|| async {
14290 let conn = Connection::open(":memory:").await.unwrap();
14291 conn.execute("CREATE TABLE names(id INTEGER PRIMARY KEY, name TEXT)")
14292 .await
14293 .unwrap();
14294 conn.execute("INSERT INTO names VALUES (1,'Alice'),(2,'alice'),(3,'Bob'),(4,'BOB')")
14295 .await
14296 .unwrap();
14297 let r = conn
14299 .query("SELECT UPPER(name) AS uname, COUNT(*) FROM names GROUP BY UPPER(name) ORDER BY uname")
14300 .await
14301 .unwrap();
14302 assert_eq!(r.len(), 2);
14303 assert_eq!(row_values(&r[0])[0].to_text(), "ALICE");
14304 assert_eq!(row_values(&r[0])[1], SqliteValue::Integer(2));
14305 assert_eq!(row_values(&r[1])[0].to_text(), "BOB");
14306 assert_eq!(row_values(&r[1])[1], SqliteValue::Integer(2));
14307 });
14308 }
14309
14310 #[test]
14311 fn conformance_059_having_without_group_by() {
14312 asupersync::test_utils::run_test(|| async {
14313 let conn = Connection::open(":memory:").await.unwrap();
14314 conn.execute("CREATE TABLE t1(val INTEGER)").await.unwrap();
14315 conn.execute("INSERT INTO t1 VALUES (1),(2),(3)")
14316 .await
14317 .unwrap();
14318 let r = conn
14321 .query("SELECT COUNT(*) FROM t1 HAVING COUNT(*) > 2")
14322 .await
14323 .unwrap();
14324 assert_eq!(r.len(), 1);
14325 assert_eq!(row_values(&r[0])[0], SqliteValue::Integer(3));
14326 });
14327 }
14328
14329 #[test]
14330 fn conformance_059_having_without_group_by_no_match() {
14331 asupersync::test_utils::run_test(|| async {
14332 let conn = Connection::open(":memory:").await.unwrap();
14333 conn.execute("CREATE TABLE t1(val INTEGER)").await.unwrap();
14334 conn.execute("INSERT INTO t1 VALUES (1),(2),(3)")
14335 .await
14336 .unwrap();
14337 let r = conn
14339 .query("SELECT COUNT(*) FROM t1 HAVING COUNT(*) > 5")
14340 .await
14341 .unwrap();
14342 assert_eq!(r.len(), 0);
14343 });
14344 }
14345
14346 #[test]
14350 fn conformance_060_lowercase_function_names() {
14351 asupersync::test_utils::run_test(|| async {
14352 let conn = Connection::open(":memory:").await.unwrap();
14353 let r = conn.query("SELECT typeof(42)").await.unwrap();
14355 assert_eq!(row_values(&r[0])[0].to_text(), "integer");
14356
14357 let r = conn.query("SELECT typeof(3.14)").await.unwrap();
14358 assert_eq!(row_values(&r[0])[0].to_text(), "real");
14359
14360 let r = conn.query("SELECT typeof('hello')").await.unwrap();
14361 assert_eq!(row_values(&r[0])[0].to_text(), "text");
14362
14363 let r = conn.query("SELECT typeof(NULL)").await.unwrap();
14364 assert_eq!(row_values(&r[0])[0].to_text(), "null");
14365 });
14366 }
14367
14368 #[test]
14369 fn conformance_060_mixed_case_function_names() {
14370 asupersync::test_utils::run_test(|| async {
14371 let conn = Connection::open(":memory:").await.unwrap();
14372 let r = conn.query("SELECT abs(-7)").await.unwrap();
14374 assert_eq!(row_values(&r[0])[0], SqliteValue::Integer(7));
14375
14376 let r = conn.query("SELECT ABS(-7)").await.unwrap();
14377 assert_eq!(row_values(&r[0])[0], SqliteValue::Integer(7));
14378
14379 let r = conn.query("SELECT Abs(-7)").await.unwrap();
14380 assert_eq!(row_values(&r[0])[0], SqliteValue::Integer(7));
14381 });
14382 }
14383
14384 #[test]
14385 fn conformance_060_hex_upper_lower() {
14386 asupersync::test_utils::run_test(|| async {
14387 let conn = Connection::open(":memory:").await.unwrap();
14388 let r = conn.query("SELECT hex(255)").await.unwrap();
14389 assert_eq!(row_values(&r[0])[0].to_text(), "323535");
14390
14391 let r = conn.query("SELECT HEX(255)").await.unwrap();
14392 assert_eq!(row_values(&r[0])[0].to_text(), "323535");
14393 });
14394 }
14395
14396 #[test]
14397 fn conformance_060_order_by_column_index_multi() {
14398 asupersync::test_utils::run_test(|| async {
14399 let conn = Connection::open(":memory:").await.unwrap();
14400 conn.execute("CREATE TABLE t2(x TEXT, y INTEGER, z REAL)")
14401 .await
14402 .unwrap();
14403 conn.execute("INSERT INTO t2 VALUES ('a', 3, 1.0), ('b', 1, 3.0), ('c', 2, 2.0)")
14404 .await
14405 .unwrap();
14406 let r = conn.query("SELECT x, y FROM t2 ORDER BY 1").await.unwrap();
14408 assert_eq!(row_values(&r[0])[0].to_text(), "a");
14409 assert_eq!(row_values(&r[1])[0].to_text(), "b");
14410 assert_eq!(row_values(&r[2])[0].to_text(), "c");
14411 });
14412 }
14413
14414 #[test]
14415 fn conformance_060_order_by_column_index_desc() {
14416 asupersync::test_utils::run_test(|| async {
14417 let conn = Connection::open(":memory:").await.unwrap();
14418 conn.execute("CREATE TABLE t3(a TEXT, b INTEGER)")
14419 .await
14420 .unwrap();
14421 conn.execute("INSERT INTO t3 VALUES ('x', 3), ('y', 1), ('z', 2)")
14422 .await
14423 .unwrap();
14424 let r = conn
14426 .query("SELECT a, b FROM t3 ORDER BY 2 DESC")
14427 .await
14428 .unwrap();
14429 assert_eq!(row_values(&r[0])[0].to_text(), "x"); assert_eq!(row_values(&r[1])[0].to_text(), "z"); assert_eq!(row_values(&r[2])[0].to_text(), "y"); });
14433 }
14434
14435 #[test]
14436 fn conformance_060_having_sum_aggregate() {
14437 asupersync::test_utils::run_test(|| async {
14438 let conn = Connection::open(":memory:").await.unwrap();
14439 conn.execute("CREATE TABLE sales(amount REAL)")
14440 .await
14441 .unwrap();
14442 conn.execute("INSERT INTO sales VALUES (100.0),(200.0),(300.0)")
14443 .await
14444 .unwrap();
14445 let r = conn
14447 .query("SELECT SUM(amount) FROM sales HAVING SUM(amount) > 500")
14448 .await
14449 .unwrap();
14450 assert_eq!(r.len(), 1);
14451 assert_eq!(row_values(&r[0])[0], SqliteValue::Float(600.0));
14452 });
14453 }
14454
14455 #[test]
14456 fn conformance_060_numeric_text_comparison_variants() {
14457 asupersync::test_utils::run_test(|| async {
14458 let conn = Connection::open(":memory:").await.unwrap();
14459 let r = conn
14461 .query("SELECT CASE WHEN 42 = '42' THEN 1 ELSE 0 END")
14462 .await
14463 .unwrap();
14464 assert_eq!(row_values(&r[0])[0], SqliteValue::Integer(0));
14465
14466 let r = conn
14468 .query("SELECT CASE WHEN 3.14 = '3.14' THEN 1 ELSE 0 END")
14469 .await
14470 .unwrap();
14471 assert_eq!(row_values(&r[0])[0], SqliteValue::Integer(0));
14472
14473 let r = conn
14475 .query("SELECT CASE WHEN 10 = 'ten' THEN 1 ELSE 0 END")
14476 .await
14477 .unwrap();
14478 assert_eq!(row_values(&r[0])[0], SqliteValue::Integer(0));
14479 });
14480 }
14481
14482 #[test]
14487 fn regression_having_aggregate_not_in_select() {
14488 asupersync::test_utils::run_test(|| async {
14489 let conn = Connection::open(":memory:").await.unwrap();
14490 conn.execute("CREATE TABLE emp (dept TEXT, salary INTEGER);")
14491 .await
14492 .unwrap();
14493 conn.execute(
14494 "INSERT INTO emp VALUES ('A', 100), ('A', 200), ('B', 50), ('B', 60), ('B', 70);",
14495 )
14496 .await
14497 .unwrap();
14498 let rows = conn
14500 .query("SELECT dept FROM emp GROUP BY dept HAVING COUNT(*) >= 3;")
14501 .await
14502 .unwrap();
14503 assert_eq!(rows.len(), 1);
14504 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("B".into()));
14505 });
14506 }
14507
14508 #[test]
14509 fn regression_having_sum_not_in_select() {
14510 asupersync::test_utils::run_test(|| async {
14511 let conn = Connection::open(":memory:").await.unwrap();
14512 conn.execute("CREATE TABLE sales (product TEXT, amount INTEGER);")
14513 .await
14514 .unwrap();
14515 conn.execute("INSERT INTO sales VALUES ('X', 10), ('X', 20), ('Y', 100), ('Y', 200);")
14516 .await
14517 .unwrap();
14518 let rows = conn
14520 .query("SELECT product FROM sales GROUP BY product HAVING SUM(amount) > 50;")
14521 .await
14522 .unwrap();
14523 assert_eq!(rows.len(), 1);
14524 assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("Y".into()));
14525 });
14526 }
14527
14528 #[test]
14529 fn regression_null_comparison_returns_null_not_zero() {
14530 asupersync::test_utils::run_test(|| async {
14531 let conn = Connection::open(":memory:").await.unwrap();
14532 let rows = conn.query("SELECT (1 = NULL);").await.unwrap();
14534 assert_eq!(rows.len(), 1);
14535 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
14536
14537 let rows = conn.query("SELECT (NULL > 5);").await.unwrap();
14538 assert_eq!(rows.len(), 1);
14539 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
14540
14541 let rows = conn.query("SELECT (NULL = NULL);").await.unwrap();
14542 assert_eq!(rows.len(), 1);
14543 assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
14544
14545 let rows = conn.query("SELECT (1 IS NULL);").await.unwrap();
14547 assert_eq!(rows.len(), 1);
14548 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(0));
14549
14550 let rows = conn.query("SELECT (NULL IS NULL);").await.unwrap();
14551 assert_eq!(rows.len(), 1);
14552 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
14553 });
14554 }
14555
14556 #[test]
14561 fn generated_column_stored_basic() {
14562 asupersync::test_utils::run_test(|| async {
14563 let conn = Connection::open(":memory:").await.unwrap();
14564 conn.execute(
14565 "CREATE TABLE t (a INTEGER, b INTEGER, c INTEGER GENERATED ALWAYS AS (a + b) STORED)",
14566 )
14567 .await
14568 .unwrap();
14569 conn.execute("INSERT INTO t (a, b) VALUES (3, 7)")
14570 .await
14571 .unwrap();
14572 let rows = conn.query("SELECT a, b, c FROM t").await.unwrap();
14573 assert_eq!(rows.len(), 1);
14574 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(3));
14575 assert_eq!(row_values(&rows[0])[1], SqliteValue::Integer(7));
14576 assert_eq!(
14577 row_values(&rows[0])[2],
14578 SqliteValue::Integer(10),
14579 "STORED generated column c = a + b should be 10"
14580 );
14581 });
14582 }
14583
14584 #[test]
14585 fn generated_column_stored_multiplication() {
14586 asupersync::test_utils::run_test(|| async {
14587 let conn = Connection::open(":memory:").await.unwrap();
14588 conn.execute(
14589 "CREATE TABLE prices (qty INTEGER, unit_price INTEGER, total INTEGER GENERATED ALWAYS AS (qty * unit_price) STORED)",
14590 )
14591 .await
14592 .unwrap();
14593 conn.execute("INSERT INTO prices (qty, unit_price) VALUES (5, 12)")
14594 .await
14595 .unwrap();
14596 let rows = conn.query("SELECT total FROM prices").await.unwrap();
14597 assert_eq!(
14598 row_values(&rows[0])[0],
14599 SqliteValue::Integer(60),
14600 "STORED generated column total = qty * unit_price should be 60"
14601 );
14602 });
14603 }
14604
14605 #[test]
14606 fn generated_column_stored_multi_row() {
14607 asupersync::test_utils::run_test(|| async {
14608 let conn = Connection::open(":memory:").await.unwrap();
14609 conn.execute(
14610 "CREATE TABLE t (x INTEGER, doubled INTEGER GENERATED ALWAYS AS (x * 2) STORED)",
14611 )
14612 .await
14613 .unwrap();
14614 conn.execute("INSERT INTO t (x) VALUES (1)").await.unwrap();
14615 conn.execute("INSERT INTO t (x) VALUES (5)").await.unwrap();
14616 conn.execute("INSERT INTO t (x) VALUES (100)")
14617 .await
14618 .unwrap();
14619 let rows = conn
14620 .query("SELECT doubled FROM t ORDER BY x")
14621 .await
14622 .unwrap();
14623 assert_eq!(rows.len(), 3);
14624 assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(2));
14625 assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(10));
14626 assert_eq!(row_values(&rows[2])[0], SqliteValue::Integer(200));
14627 });
14628 }
14629
14630 #[test]
14631 fn generated_column_stored_update_recomputes() {
14632 asupersync::test_utils::run_test(|| async {
14633 let conn = Connection::open(":memory:").await.unwrap();
14634 conn.execute(
14635 "CREATE TABLE t (a INTEGER, b INTEGER, c INTEGER GENERATED ALWAYS AS (a + b) STORED)",
14636 )
14637 .await
14638 .unwrap();
14639 conn.execute("INSERT INTO t (a, b) VALUES (3, 7)")
14640 .await
14641 .unwrap();
14642 conn.execute("UPDATE t SET a = 10 WHERE b = 7")
14643 .await
14644 .unwrap();
14645 let rows = conn.query("SELECT c FROM t").await.unwrap();
14646 assert_eq!(
14647 row_values(&rows[0])[0],
14648 SqliteValue::Integer(17),
14649 "STORED generated column should recompute after UPDATE: 10 + 7 = 17"
14650 );
14651 });
14652 }
14653
14654 #[test]
14659 fn fk_insert_valid_parent_succeeds() {
14660 asupersync::test_utils::run_test(|| async {
14661 let conn = Connection::open(":memory:").await.unwrap();
14662 conn.execute("PRAGMA foreign_keys = ON").await.unwrap();
14663 conn.execute("CREATE TABLE parent (id INTEGER PRIMARY KEY, name TEXT)")
14664 .await
14665 .unwrap();
14666 conn.execute(
14667 "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))",
14668 )
14669 .await
14670 .unwrap();
14671 conn.execute("INSERT INTO parent VALUES (1, 'Alice')")
14672 .await
14673 .unwrap();
14674 conn.execute("INSERT INTO child VALUES (1, 1)")
14676 .await
14677 .unwrap();
14678 let rows = conn.query("SELECT * FROM child").await.unwrap();
14679 assert_eq!(rows.len(), 1);
14680 });
14681 }
14682
14683 #[test]
14684 fn fk_insert_missing_parent_fails() {
14685 asupersync::test_utils::run_test(|| async {
14686 let conn = Connection::open(":memory:").await.unwrap();
14687 conn.execute("PRAGMA foreign_keys = ON").await.unwrap();
14688 conn.execute("CREATE TABLE parent (id INTEGER PRIMARY KEY, name TEXT)")
14689 .await
14690 .unwrap();
14691 conn.execute(
14692 "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))",
14693 )
14694 .await
14695 .unwrap();
14696 let result = conn.execute("INSERT INTO child VALUES (1, 99)").await;
14698 assert!(result.is_err(), "INSERT with missing FK parent should fail");
14699 });
14700 }
14701
14702 #[test]
14703 fn fk_insert_null_fk_value_succeeds() {
14704 asupersync::test_utils::run_test(|| async {
14705 let conn = Connection::open(":memory:").await.unwrap();
14706 conn.execute("PRAGMA foreign_keys = ON").await.unwrap();
14707 conn.execute("CREATE TABLE parent (id INTEGER PRIMARY KEY)")
14708 .await
14709 .unwrap();
14710 conn.execute(
14711 "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))",
14712 )
14713 .await
14714 .unwrap();
14715 conn.execute("INSERT INTO child VALUES (1, NULL)")
14717 .await
14718 .unwrap();
14719 let rows = conn.query("SELECT * FROM child").await.unwrap();
14720 assert_eq!(rows.len(), 1);
14721 });
14722 }
14723
14724 #[test]
14725 fn fk_off_by_default() {
14726 asupersync::test_utils::run_test(|| async {
14727 let conn = Connection::open(":memory:").await.unwrap();
14728 conn.execute("CREATE TABLE parent (id INTEGER PRIMARY KEY)")
14730 .await
14731 .unwrap();
14732 conn.execute(
14733 "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))",
14734 )
14735 .await
14736 .unwrap();
14737 conn.execute("INSERT INTO child VALUES (1, 99)")
14739 .await
14740 .unwrap();
14741 });
14742 }
14743
14744 #[test]
14745 fn fk_delete_parent_with_children_fails() {
14746 asupersync::test_utils::run_test(|| async {
14747 let conn = Connection::open(":memory:").await.unwrap();
14748 conn.execute("PRAGMA foreign_keys = ON").await.unwrap();
14749 conn.execute("CREATE TABLE parent (id INTEGER PRIMARY KEY, name TEXT)")
14750 .await
14751 .unwrap();
14752 conn.execute(
14753 "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))",
14754 )
14755 .await
14756 .unwrap();
14757 conn.execute("INSERT INTO parent VALUES (1, 'Alice')")
14758 .await
14759 .unwrap();
14760 conn.execute("INSERT INTO child VALUES (1, 1)")
14761 .await
14762 .unwrap();
14763 let result = conn.execute("DELETE FROM parent WHERE id = 1").await;
14765 assert!(
14766 result.is_err(),
14767 "DELETE parent with child references should fail with FK ON"
14768 );
14769 });
14770 }
14771
14772 #[test]
14773 fn fk_delete_cascade() {
14774 asupersync::test_utils::run_test(|| async {
14775 let conn = Connection::open(":memory:").await.unwrap();
14776 conn.execute("PRAGMA foreign_keys = ON").await.unwrap();
14777 conn.execute("CREATE TABLE parent (id INTEGER PRIMARY KEY, name TEXT)")
14778 .await
14779 .unwrap();
14780 conn.execute(
14781 "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id) ON DELETE CASCADE)",
14782 )
14783 .await
14784 .unwrap();
14785 conn.execute("INSERT INTO parent VALUES (1, 'Alice')")
14786 .await
14787 .unwrap();
14788 conn.execute("INSERT INTO child VALUES (1, 1)")
14789 .await
14790 .unwrap();
14791 conn.execute("INSERT INTO child VALUES (2, 1)")
14792 .await
14793 .unwrap();
14794 conn.execute("DELETE FROM parent WHERE id = 1")
14796 .await
14797 .unwrap();
14798 let rows = conn.query("SELECT * FROM child").await.unwrap();
14799 assert_eq!(
14800 rows.len(),
14801 0,
14802 "ON DELETE CASCADE should delete all child rows"
14803 );
14804 });
14805 }
14806
14807 #[test]
14808 fn fk_delete_set_null() {
14809 asupersync::test_utils::run_test(|| async {
14810 let conn = Connection::open(":memory:").await.unwrap();
14811 conn.execute("PRAGMA foreign_keys = ON").await.unwrap();
14812 conn.execute("CREATE TABLE parent (id INTEGER PRIMARY KEY, name TEXT)")
14813 .await
14814 .unwrap();
14815 conn.execute(
14816 "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id) ON DELETE SET NULL)",
14817 )
14818 .await
14819 .unwrap();
14820 conn.execute("INSERT INTO parent VALUES (1, 'Alice')")
14821 .await
14822 .unwrap();
14823 conn.execute("INSERT INTO child VALUES (1, 1)")
14824 .await
14825 .unwrap();
14826 conn.execute("DELETE FROM parent WHERE id = 1")
14828 .await
14829 .unwrap();
14830 let rows = conn.query("SELECT parent_id FROM child").await.unwrap();
14831 assert_eq!(rows.len(), 1);
14832 assert_eq!(
14833 row_values(&rows[0])[0],
14834 SqliteValue::Null,
14835 "ON DELETE SET NULL should set FK column to NULL"
14836 );
14837 });
14838 }
14839}