From 7e63135abbb19f76562db432e4e61261b71fa025 Mon Sep 17 00:00:00 2001 From: Nikita Sivukhin Date: Thu, 9 Oct 2025 11:38:44 +0400 Subject: [PATCH 1/8] reset statement after execution --- bindings/javascript/packages/common/promise.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/bindings/javascript/packages/common/promise.ts b/bindings/javascript/packages/common/promise.ts index db9dcb676..0ea8030b1 100644 --- a/bindings/javascript/packages/common/promise.ts +++ b/bindings/javascript/packages/common/promise.ts @@ -343,7 +343,6 @@ class Statement { */ async run(...bindParameters) { let stmt = await this.stmt.resolve(); - stmt.reset(); bindParams(stmt, bindParameters); @@ -370,6 +369,7 @@ class Statement { return { changes, lastInsertRowid }; } finally { + stmt.reset(); this.execLock.release(); } } @@ -382,7 +382,6 @@ class Statement { async get(...bindParameters) { let stmt = await this.stmt.resolve(); - stmt.reset(); bindParams(stmt, bindParameters); await this.execLock.acquire(); @@ -397,10 +396,12 @@ class Statement { return undefined; } if (stepResult === STEP_ROW) { - return stmt.row(); + const row = stmt.row(); + return row; } } } finally { + stmt.reset(); this.execLock.release(); } } @@ -413,7 +414,6 @@ class Statement { async *iterate(...bindParameters) { let stmt = await this.stmt.resolve(); - stmt.reset(); bindParams(stmt, bindParameters); await this.execLock.acquire(); @@ -432,6 +432,7 @@ class Statement { } } } finally { + stmt.reset(); this.execLock.release(); } } @@ -444,7 +445,6 @@ class Statement { async all(...bindParameters) { let stmt = await this.stmt.resolve(); - stmt.reset(); bindParams(stmt, bindParameters); const rows: any[] = []; @@ -466,6 +466,7 @@ class Statement { return rows; } finally { + stmt.reset(); this.execLock.release(); } } From 4c9886159021eef5703a9318039f995c8150a4f5 Mon Sep 17 00:00:00 2001 From: Nikita Sivukhin Date: Wed, 29 Oct 2025 11:47:21 +0400 Subject: [PATCH 2/8] adjust logs --- core/lib.rs | 2 +- core/storage/pager.rs | 6 +++--- core/storage/wal.rs | 3 ++- core/vdbe/mod.rs | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/core/lib.rs b/core/lib.rs index a276af239..128eb6a1f 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -1212,7 +1212,7 @@ impl Connection { } let sql = sql.as_ref(); - tracing::trace!("Preparing: {}", sql); + tracing::debug!("Preparing: {}", sql); let mut parser = Parser::new(sql.as_bytes()); let cmd = parser.next_cmd()?; let syms = self.syms.read(); diff --git a/core/storage/pager.rs b/core/storage/pager.rs index 315e2557c..676ec79a9 100644 --- a/core/storage/pager.rs +++ b/core/storage/pager.rs @@ -1528,7 +1528,7 @@ impl Pager { allow_empty_read: bool, ) -> Result<(PageRef, Completion)> { assert!(page_idx >= 0); - tracing::trace!("read_page_no_cache(page_idx = {})", page_idx); + tracing::debug!("read_page_no_cache(page_idx = {})", page_idx); let page = Arc::new(Page::new(page_idx)); let io_ctx = self.io_ctx.read(); let Some(wal) = self.wal.as_ref() else { @@ -1565,11 +1565,11 @@ impl Pager { #[tracing::instrument(skip_all, level = Level::DEBUG)] pub fn read_page(&self, page_idx: i64) -> Result<(PageRef, Option)> { assert!(page_idx >= 0, "pages in pager should be positive, negative might indicate unallocated pages from mvcc or any other nasty bug"); - tracing::trace!("read_page(page_idx = {})", page_idx); + tracing::debug!("read_page(page_idx = {})", page_idx); let mut page_cache = self.page_cache.write(); let page_key = PageCacheKey::new(page_idx as usize); if let Some(page) = page_cache.get(&page_key)? { - tracing::trace!("read_page(page_idx = {}) = cached", page_idx); + tracing::debug!("read_page(page_idx = {}) = cached", page_idx); turso_assert!( page_idx as usize == page.get().id, "attempted to read page {page_idx} but got page {}", diff --git a/core/storage/wal.rs b/core/storage/wal.rs index 5983e89ae..eebfbbd98 100644 --- a/core/storage/wal.rs +++ b/core/storage/wal.rs @@ -1139,7 +1139,7 @@ impl Wal for WalFile { #[instrument(skip_all, level = Level::DEBUG)] fn read_frame_raw(&self, frame_id: u64, frame: &mut [u8]) -> Result { - tracing::debug!("read_frame({})", frame_id); + tracing::debug!("read_frame_raw({})", frame_id); let offset = self.frame_offset(frame_id); let (frame_ptr, frame_len) = (frame.as_mut_ptr(), frame.len()); @@ -1511,6 +1511,7 @@ impl Wal for WalFile { let mut next_frame_id = self.max_frame.load(Ordering::Acquire) + 1; // Build every frame in order, updating the rolling checksum for (idx, page) in pages.iter().enumerate() { + tracing::debug!("append_frames_vectored: page_id={}", page.get().id); let page_id = page.get().id; let plain = page.get_contents().as_ptr(); diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index a7880dfbd..49979b68f 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -958,7 +958,7 @@ impl Program { } else { let connection = self.connection.clone(); let auto_commit = connection.auto_commit.load(Ordering::SeqCst); - tracing::trace!( + tracing::debug!( "Halt auto_commit {}, state={:?}", auto_commit, program_state.commit_state From b01cec2ba4237a993baa362b4c29c8ccbece6679 Mon Sep 17 00:00:00 2001 From: Nikita Sivukhin Date: Wed, 29 Oct 2025 11:50:31 +0400 Subject: [PATCH 3/8] wip --- bindings/javascript/packages/native/index.js | 92 +- .../javascript/sync/packages/native/index.js | 92 +- .../sync/packages/native/promise.test.ts | 119 +- .../sync/packages/wasm/promise.test.ts | 1120 +++++++++-------- 4 files changed, 795 insertions(+), 628 deletions(-) diff --git a/bindings/javascript/packages/native/index.js b/bindings/javascript/packages/native/index.js index 8e63811ea..306acef52 100644 --- a/bindings/javascript/packages/native/index.js +++ b/bindings/javascript/packages/native/index.js @@ -81,8 +81,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-android-arm64') const bindingPackageVersion = require('@tursodatabase/database-android-arm64/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -97,8 +97,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-android-arm-eabi') const bindingPackageVersion = require('@tursodatabase/database-android-arm-eabi/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -117,8 +117,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-win32-x64-msvc') const bindingPackageVersion = require('@tursodatabase/database-win32-x64-msvc/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -133,8 +133,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-win32-ia32-msvc') const bindingPackageVersion = require('@tursodatabase/database-win32-ia32-msvc/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -149,8 +149,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-win32-arm64-msvc') const bindingPackageVersion = require('@tursodatabase/database-win32-arm64-msvc/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -168,8 +168,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-darwin-universal') const bindingPackageVersion = require('@tursodatabase/database-darwin-universal/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -184,8 +184,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-darwin-x64') const bindingPackageVersion = require('@tursodatabase/database-darwin-x64/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -200,8 +200,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-darwin-arm64') const bindingPackageVersion = require('@tursodatabase/database-darwin-arm64/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -220,8 +220,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-freebsd-x64') const bindingPackageVersion = require('@tursodatabase/database-freebsd-x64/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -236,8 +236,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-freebsd-arm64') const bindingPackageVersion = require('@tursodatabase/database-freebsd-arm64/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -257,8 +257,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-linux-x64-musl') const bindingPackageVersion = require('@tursodatabase/database-linux-x64-musl/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -273,8 +273,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-linux-x64-gnu') const bindingPackageVersion = require('@tursodatabase/database-linux-x64-gnu/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -291,8 +291,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-linux-arm64-musl') const bindingPackageVersion = require('@tursodatabase/database-linux-arm64-musl/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -307,8 +307,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-linux-arm64-gnu') const bindingPackageVersion = require('@tursodatabase/database-linux-arm64-gnu/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -325,8 +325,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-linux-arm-musleabihf') const bindingPackageVersion = require('@tursodatabase/database-linux-arm-musleabihf/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -341,8 +341,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-linux-arm-gnueabihf') const bindingPackageVersion = require('@tursodatabase/database-linux-arm-gnueabihf/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -359,8 +359,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-linux-riscv64-musl') const bindingPackageVersion = require('@tursodatabase/database-linux-riscv64-musl/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -375,8 +375,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-linux-riscv64-gnu') const bindingPackageVersion = require('@tursodatabase/database-linux-riscv64-gnu/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -392,8 +392,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-linux-ppc64-gnu') const bindingPackageVersion = require('@tursodatabase/database-linux-ppc64-gnu/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -408,8 +408,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-linux-s390x-gnu') const bindingPackageVersion = require('@tursodatabase/database-linux-s390x-gnu/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -428,8 +428,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-openharmony-arm64') const bindingPackageVersion = require('@tursodatabase/database-openharmony-arm64/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -444,8 +444,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-openharmony-x64') const bindingPackageVersion = require('@tursodatabase/database-openharmony-x64/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -460,8 +460,8 @@ function requireNative() { try { const binding = require('@tursodatabase/database-openharmony-arm') const bindingPackageVersion = require('@tursodatabase/database-openharmony-arm/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.11' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.11 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { diff --git a/bindings/javascript/sync/packages/native/index.js b/bindings/javascript/sync/packages/native/index.js index 10d3073fa..ddc3d3f36 100644 --- a/bindings/javascript/sync/packages/native/index.js +++ b/bindings/javascript/sync/packages/native/index.js @@ -81,8 +81,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-android-arm64') const bindingPackageVersion = require('@tursodatabase/sync-android-arm64/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -97,8 +97,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-android-arm-eabi') const bindingPackageVersion = require('@tursodatabase/sync-android-arm-eabi/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -117,8 +117,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-win32-x64-msvc') const bindingPackageVersion = require('@tursodatabase/sync-win32-x64-msvc/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -133,8 +133,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-win32-ia32-msvc') const bindingPackageVersion = require('@tursodatabase/sync-win32-ia32-msvc/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -149,8 +149,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-win32-arm64-msvc') const bindingPackageVersion = require('@tursodatabase/sync-win32-arm64-msvc/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -168,8 +168,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-darwin-universal') const bindingPackageVersion = require('@tursodatabase/sync-darwin-universal/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -184,8 +184,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-darwin-x64') const bindingPackageVersion = require('@tursodatabase/sync-darwin-x64/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -200,8 +200,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-darwin-arm64') const bindingPackageVersion = require('@tursodatabase/sync-darwin-arm64/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -220,8 +220,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-freebsd-x64') const bindingPackageVersion = require('@tursodatabase/sync-freebsd-x64/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -236,8 +236,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-freebsd-arm64') const bindingPackageVersion = require('@tursodatabase/sync-freebsd-arm64/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -257,8 +257,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-linux-x64-musl') const bindingPackageVersion = require('@tursodatabase/sync-linux-x64-musl/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -273,8 +273,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-linux-x64-gnu') const bindingPackageVersion = require('@tursodatabase/sync-linux-x64-gnu/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -291,8 +291,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-linux-arm64-musl') const bindingPackageVersion = require('@tursodatabase/sync-linux-arm64-musl/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -307,8 +307,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-linux-arm64-gnu') const bindingPackageVersion = require('@tursodatabase/sync-linux-arm64-gnu/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -325,8 +325,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-linux-arm-musleabihf') const bindingPackageVersion = require('@tursodatabase/sync-linux-arm-musleabihf/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -341,8 +341,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-linux-arm-gnueabihf') const bindingPackageVersion = require('@tursodatabase/sync-linux-arm-gnueabihf/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -359,8 +359,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-linux-riscv64-musl') const bindingPackageVersion = require('@tursodatabase/sync-linux-riscv64-musl/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -375,8 +375,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-linux-riscv64-gnu') const bindingPackageVersion = require('@tursodatabase/sync-linux-riscv64-gnu/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -392,8 +392,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-linux-ppc64-gnu') const bindingPackageVersion = require('@tursodatabase/sync-linux-ppc64-gnu/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -408,8 +408,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-linux-s390x-gnu') const bindingPackageVersion = require('@tursodatabase/sync-linux-s390x-gnu/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -428,8 +428,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-openharmony-arm64') const bindingPackageVersion = require('@tursodatabase/sync-openharmony-arm64/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -444,8 +444,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-openharmony-x64') const bindingPackageVersion = require('@tursodatabase/sync-openharmony-x64/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -460,8 +460,8 @@ function requireNative() { try { const binding = require('@tursodatabase/sync-openharmony-arm') const bindingPackageVersion = require('@tursodatabase/sync-openharmony-arm/package.json').version - if (bindingPackageVersion !== '0.2.0-pre.13' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 0.2.0-pre.13 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { diff --git a/bindings/javascript/sync/packages/native/promise.test.ts b/bindings/javascript/sync/packages/native/promise.test.ts index 1f5801991..2e38b483e 100644 --- a/bindings/javascript/sync/packages/native/promise.test.ts +++ b/bindings/javascript/sync/packages/native/promise.test.ts @@ -13,6 +13,63 @@ function cleanup(path) { try { unlinkSync(`${path}-wal-revert`) } catch (e) { } } +test('concurrent-actions-consistency', async () => { + { + const db = await connect({ + path: ':memory:', + url: process.env.VITE_TURSO_DB_URL, + longPollTimeoutMs: 100, + // tracing: 'trace', + }); + await db.exec("CREATE TABLE IF NOT EXISTS rows(key TEXT PRIMARY KEY, value INTEGER)"); + await db.exec("DELETE FROM rows"); + await db.exec("INSERT INTO rows VALUES ('key', 0)"); + await db.push(); + await db.close(); + } + const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + console.info('run_info', await db1.prepare("SELECT * FROM sqlite_master").all()); + await db1.exec("PRAGMA busy_timeout=100"); + const pull = async function (iterations: number) { + for (let i = 0; i < iterations; i++) { + console.info('pull', i); + try { await db1.pull(); } + catch (e) { console.error('pull', e); } + await new Promise(resolve => setTimeout(resolve, 0)); + } + } + const push = async function (iterations: number) { + for (let i = 0; i < iterations; i++) { + await new Promise(resolve => setTimeout(resolve, (Math.random() + 1))); + // console.info('push', i); + try { + if ((await db1.stats()).operations > 0) { + const start = performance.now(); + await db1.push(); + console.info('push', performance.now() - start); + } + } + catch (e) { console.error('push', e); } + } + } + const run = async function (iterations: number) { + let rows = 0; + for (let i = 0; i < iterations; i++) { + // console.info('run', i, rows); + // console.info('run_info', 'update', 'start'); + await db1.prepare("UPDATE rows SET value = value + 1 WHERE key = ?").run('key'); + // console.info('run_info', 'update', 'end'); + rows += 1; + // console.info('run_info', 'select', 'start'); + const { cnt } = await db1.prepare("SELECT value as cnt FROM rows WHERE key = ?").get(['key']); + // console.info('run_info', 'select', 'end', cnt, '(', rows, ')'); + expect(cnt).toBe(rows); + await new Promise(resolve => setTimeout(resolve, 10 * (Math.random() + 1))); + } + } + await Promise.all([pull(20), push(20), run(200)]); +}) + test('simple-db', async () => { const db = new Database({ path: ':memory:' }); expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]) @@ -393,13 +450,14 @@ test('concurrent-updates', async () => { await db.push(); await db.close(); } - const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, tracing: 'info' }); async function pull(db) { try { await db.pull(); } catch (e) { - // ignore + console.error('pull error', e); } finally { + console.error('pull ok'); setTimeout(async () => await pull(db), 0); } } @@ -407,8 +465,9 @@ test('concurrent-updates', async () => { try { await db.push(); } catch (e) { - // ignore + console.error('push error', e); } finally { + console.error('push ok'); setTimeout(async () => await push(db), 0); } } @@ -416,6 +475,7 @@ test('concurrent-updates', async () => { setTimeout(async () => await push(db1), 0) for (let i = 0; i < 1000; i++) { try { + console.info('changes ' + JSON.stringify(await db1.prepare("SELECT change_id FROM turso_cdc").all())); await Promise.all([ db1.exec(`INSERT INTO q VALUES ('1', 0) ON CONFLICT DO UPDATE SET y = ${i + 1}`), db1.exec(`INSERT INTO q VALUES ('2', 0) ON CONFLICT DO UPDATE SET y = ${i + 1}`) @@ -483,6 +543,59 @@ test('pull-push-concurrent', async () => { console.info(await db.stats()); }) +test('checkpoint-and-actions', async () => { + { + const db = await connect({ + path: ':memory:', + url: process.env.VITE_TURSO_DB_URL, + longPollTimeoutMs: 100, + tracing: 'info' + }); + await db.exec("CREATE TABLE IF NOT EXISTS rows(key TEXT PRIMARY KEY, value INTEGER)"); + await db.exec("DELETE FROM rows"); + await db.exec("INSERT INTO rows VALUES ('key', 0)"); + await db.push(); + await db.close(); + } + const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db1.exec("PRAGMA busy_timeout=100"); + const pull = async function (iterations: number) { + for (let i = 0; i < iterations; i++) { + try { + await db1.pull(); + } + catch (e) { console.error('pull', e); } + await new Promise(resolve => setTimeout(resolve, 0)); + } + } + const push = async function (iterations: number) { + for (let i = 0; i < iterations; i++) { + await new Promise(resolve => setTimeout(resolve, 5)); + try { + if ((await db1.stats()).operations > 0) { + const start = performance.now(); + await db1.push(); + console.info('push', performance.now() - start); + } + } + catch (e) { console.error('push', e); } + } + } + let rows = 0; + const run = async function (iterations: number) { + for (let i = 0; i < iterations; i++) { + await db1.prepare("UPDATE rows SET value = value + 1 WHERE key = ?").run('key'); + rows += 1; + const { cnt } = await db1.prepare("SELECT value as cnt FROM rows WHERE key = ?").get(['key']); + console.info('CHECK', cnt, rows); + expect(cnt).toBe(rows); + await new Promise(resolve => setTimeout(resolve, 10 * (1 + Math.random()))); + } + } + // await run(100); + await Promise.all([pull(40), push(40), run(100)]); +}) + test('transform', async () => { { const db = await connect({ diff --git a/bindings/javascript/sync/packages/wasm/promise.test.ts b/bindings/javascript/sync/packages/wasm/promise.test.ts index e6be4e53a..feb2ce9ca 100644 --- a/bindings/javascript/sync/packages/wasm/promise.test.ts +++ b/bindings/javascript/sync/packages/wasm/promise.test.ts @@ -4,551 +4,605 @@ import { Database, connect, DatabaseRowMutation, DatabaseRowTransformResult } fr const localeCompare = (a, b) => a.x.localeCompare(b.x); const intCompare = (a, b) => a.x - b.x; -test('implicit connect', async () => { - const db = new Database({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - const defer = db.prepare("SELECT * FROM not_found"); - await expect(async () => await defer.all()).rejects.toThrowError(/no such table: not_found/); - expect(() => db.prepare("SELECT * FROM not_found")).toThrowError(/no such table: not_found/); - expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]); -}) - -test('simple-db', async () => { - const db = new Database({ path: ':memory:' }); - expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]) - await db.exec("CREATE TABLE t(x)"); - await db.exec("INSERT INTO t VALUES (1), (2), (3)"); - expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]) - await expect(async () => await db.pull()).rejects.toThrowError(/sync is disabled as database was opened without sync support/); -}) - -test('implicit connect', async () => { - const db = new Database({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - const defer = db.prepare("SELECT * FROM not_found"); - await expect(async () => await defer.all()).rejects.toThrowError(/no such table: not_found/); - expect(() => db.prepare("SELECT * FROM not_found")).toThrowError(/no such table: not_found/); - expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]); -}) - -test('defered sync', async () => { - { - const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); - await db.exec("DELETE FROM t"); - await db.exec("INSERT INTO t VALUES (100)"); - await db.push(); - await db.close(); - } - - let url = null; - const db = new Database({ path: ':memory:', url: () => url }); - await db.prepare("CREATE TABLE t(x)").run(); - await db.prepare("INSERT INTO t VALUES (1), (2), (3), (42)").run(); - expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 42 }]); - await expect(async () => await db.pull()).rejects.toThrow(/url is empty - sync is paused/); - url = process.env.VITE_TURSO_DB_URL; - await db.pull(); - expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 100 }, { x: 1 }, { x: 2 }, { x: 3 }, { x: 42 }]); -}) - -test('encryption sync', async () => { - const KEY = 'l/FWopMfZisTLgBX4A42AergrCrYKjiO3BfkJUwv83I='; - const URL = 'http://encrypted--a--a.localhost:10000'; - { - const db = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } }); - await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); - await db.exec("DELETE FROM t"); - await db.push(); - await db.close(); - } - const db1 = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } }); - const db2 = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } }); - await db1.exec("INSERT INTO t VALUES (1), (2), (3)"); - await db2.exec("INSERT INTO t VALUES (4), (5), (6)"); - expect(await db1.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]); - expect(await db2.prepare("SELECT * FROM t").all()).toEqual([{ x: 4 }, { x: 5 }, { x: 6 }]); - await Promise.all([db1.push(), db2.push()]); - await Promise.all([db1.pull(), db2.pull()]); - const expected = [{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }, { x: 5 }, { x: 6 }]; - expect((await db1.prepare("SELECT * FROM t").all()).sort(intCompare)).toEqual(expected.sort(intCompare)); - expect((await db2.prepare("SELECT * FROM t").all()).sort(intCompare)).toEqual(expected.sort(intCompare)); -}); - -test('defered encryption sync', async () => { - const URL = 'http://encrypted--a--a.localhost:10000'; - const KEY = 'l/FWopMfZisTLgBX4A42AergrCrYKjiO3BfkJUwv83I='; - let url = null; - { - const db = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } }); - await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); - await db.exec("DELETE FROM t"); - await db.exec("INSERT INTO t VALUES (100)"); - await db.push(); - await db.close(); - } - const db = await connect({ path: ':memory:', url: () => url, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } }); - await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); - await db.exec("INSERT INTO t VALUES (1), (2), (3)"); - expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]); - - url = URL; - await db.pull(); - - const expected = [{ x: 100 }, { x: 1 }, { x: 2 }, { x: 3 }]; - expect((await db.prepare("SELECT * FROM t").all())).toEqual(expected); -}); - -test('select-after-push', async () => { - { - const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); - await db.exec("DELETE FROM t"); - await db.push(); - await db.close(); - } - { - const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db.exec("INSERT INTO t VALUES (1), (2), (3)"); - await db.push(); - } - { - const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - const rows = await db.prepare('SELECT * FROM t').all(); - expect(rows).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]) - } -}) - -test('select-without-push', async () => { - { - const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); - await db.exec("DELETE FROM t"); - await db.push(); - await db.close(); - } - { - const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db.exec("INSERT INTO t VALUES (1), (2), (3)"); - } - { - const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - const rows = await db.prepare('SELECT * FROM t').all(); - expect(rows).toEqual([]) - } -}) - -test('merge-non-overlapping-keys', async () => { - { - const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); - await db.exec("DELETE FROM q"); - await db.push(); - await db.close(); - } - const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2')"); - - const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db2.exec("INSERT INTO q VALUES ('k3', 'value3'), ('k4', 'value4'), ('k5', 'value5')"); - - await Promise.all([db1.push(), db2.push()]); - await Promise.all([db1.pull(), db2.pull()]); - - const rows1 = await db1.prepare('SELECT * FROM q').all(); - const rows2 = await db1.prepare('SELECT * FROM q').all(); - const expected = [{ x: 'k1', y: 'value1' }, { x: 'k2', y: 'value2' }, { x: 'k3', y: 'value3' }, { x: 'k4', y: 'value4' }, { x: 'k5', y: 'value5' }]; - expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare)) - expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare)) -}) - -test('last-push-wins', async () => { - { - const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); - await db.exec("DELETE FROM q"); - await db.push(); - await db.close(); - } - const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2'), ('k4', 'value4')"); - - const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db2.exec("INSERT INTO q VALUES ('k1', 'value3'), ('k2', 'value4'), ('k3', 'value5')"); - - await db2.push(); - await db1.push(); - await Promise.all([db1.pull(), db2.pull()]); - - const rows1 = await db1.prepare('SELECT * FROM q').all(); - const rows2 = await db1.prepare('SELECT * FROM q').all(); - const expected = [{ x: 'k1', y: 'value1' }, { x: 'k2', y: 'value2' }, { x: 'k3', y: 'value5' }, { x: 'k4', y: 'value4' }]; - expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare)) - expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare)) -}) - -test('last-push-wins-with-delete', async () => { - { - const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); - await db.exec("DELETE FROM q"); - await db.push(); - await db.close(); - } - const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2'), ('k4', 'value4')"); - await db1.exec("DELETE FROM q") - - const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db2.exec("INSERT INTO q VALUES ('k1', 'value3'), ('k2', 'value4'), ('k3', 'value5')"); - - await db2.push(); - await db1.push(); - await Promise.all([db1.pull(), db2.pull()]); - - const rows1 = await db1.prepare('SELECT * FROM q').all(); - const rows2 = await db1.prepare('SELECT * FROM q').all(); - const expected = [{ x: 'k3', y: 'value5' }]; - expect(rows1).toEqual(expected) - expect(rows2).toEqual(expected) -}) - -test('constraint-conflict', async () => { - { - const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db.exec("CREATE TABLE IF NOT EXISTS u(x TEXT PRIMARY KEY, y UNIQUE)"); - await db.exec("DELETE FROM u"); - await db.push(); - await db.close(); - } - const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db1.exec("INSERT INTO u VALUES ('k1', 'value1')"); - - const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db2.exec("INSERT INTO u VALUES ('k2', 'value1')"); - - await db1.push(); - await expect(async () => await db2.push()).rejects.toThrow('SQLite error: UNIQUE constraint failed: u.y'); -}) - -test('checkpoint', async () => { - { - const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); - await db.exec("DELETE FROM q"); - await db.push(); - await db.close(); - } - const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - for (let i = 0; i < 1000; i++) { - await db1.exec(`INSERT INTO q VALUES ('k${i}', 'v${i}')`); - } - expect((await db1.stats()).mainWal).toBeGreaterThan(4096 * 1000); - await db1.checkpoint(); - expect((await db1.stats()).mainWal).toBe(0); - let revertWal = (await db1.stats()).revertWal; - expect(revertWal).toBeLessThan(4096 * 1000 / 100); - - for (let i = 0; i < 1000; i++) { - await db1.exec(`UPDATE q SET y = 'u${i}' WHERE x = 'k${i}'`); - } - await db1.checkpoint(); - expect((await db1.stats()).revertWal).toBe(revertWal); -}) - -test('persistence-push', async () => { - { - const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); - await db.exec("DELETE FROM q"); - await db.push(); - await db.close(); - } - const path = `test-${(Math.random() * 10000) | 0}.db`; - { - const db1 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); - await db1.exec(`INSERT INTO q VALUES ('k1', 'v1')`); - await db1.exec(`INSERT INTO q VALUES ('k2', 'v2')`); - await db1.close(); - } - - { - const db2 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); - await db2.exec(`INSERT INTO q VALUES ('k3', 'v3')`); - await db2.exec(`INSERT INTO q VALUES ('k4', 'v4')`); - const stmt = db2.prepare('SELECT * FROM q'); - const rows = await stmt.all(); - const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }]; - expect(rows).toEqual(expected) - stmt.close(); - await db2.close(); - } - - { - const db3 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); - await db3.push(); - await db3.close(); - } - - { - const db4 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); - const rows = await db4.prepare('SELECT * FROM q').all(); - const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }]; - expect(rows).toEqual(expected) - await db4.close(); - } -}) - -test('persistence-offline', async () => { - { - const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); - await db.exec("DELETE FROM q"); - await db.push(); - await db.close(); - } - const path = `test-${(Math.random() * 10000) | 0}.db`; - { - const db = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); - await db.exec(`INSERT INTO q VALUES ('k1', 'v1')`); - await db.exec(`INSERT INTO q VALUES ('k2', 'v2')`); - await db.push(); - await db.close(); - } - { - const db = await connect({ path: path, url: "https://not-valid-url.localhost" }); - const rows = await db.prepare("SELECT * FROM q").all(); - const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }]; - expect(rows.sort(localeCompare)).toEqual(expected.sort(localeCompare)) - await db.close(); - } -}) - -test('persistence-pull-push', async () => { - { - const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); - await db.exec("DELETE FROM q"); - await db.push(); - await db.close(); - } - const path1 = `test-${(Math.random() * 10000) | 0}.db`; - const path2 = `test-${(Math.random() * 10000) | 0}.db`; - const db1 = await connect({ path: path1, url: process.env.VITE_TURSO_DB_URL }); - await db1.exec(`INSERT INTO q VALUES ('k1', 'v1')`); - await db1.exec(`INSERT INTO q VALUES ('k2', 'v2')`); - const stats1 = await db1.stats(); - - const db2 = await connect({ path: path2, url: process.env.VITE_TURSO_DB_URL }); - await db2.exec(`INSERT INTO q VALUES ('k3', 'v3')`); - await db2.exec(`INSERT INTO q VALUES ('k4', 'v4')`); - - await Promise.all([db1.push(), db2.push()]); - await Promise.all([db1.pull(), db2.pull()]); - const stats2 = await db1.stats(); - console.info(stats1, stats2); - expect(stats1.revision).not.toBe(stats2.revision); - - const rows1 = await db1.prepare('SELECT * FROM q').all(); - const rows2 = await db2.prepare('SELECT * FROM q').all(); - const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }]; - expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare)) - expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare)) -}) - -test('pull-push-concurrent', async () => { - { - const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 5000 }); - await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); - await db.exec("DELETE FROM q"); - await db.push(); - await db.close(); - } - let pullResolve = null; - const pullFinish = new Promise(resolve => pullResolve = resolve); - let pushResolve = null; - const pushFinish = new Promise(resolve => pushResolve = resolve); - let stopPull = false; - let stopPush = false; - const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); - let pull = async () => { - try { - await db.pull(); - } catch (e) { - console.error('pull', e); - } finally { - if (!stopPull) { - setTimeout(pull, 0); - } else { - pullResolve() - } - } - } - let push = async () => { - try { - if ((await db.stats()).operations > 0) { - await db.push(); - } - } catch (e) { - console.error('push', e); - } finally { - if (!stopPush) { - setTimeout(push, 0); - } else { - pushResolve(); - } - } - } - setTimeout(pull, 0); - setTimeout(push, 0); - for (let i = 0; i < 1000; i++) { - await db.exec(`INSERT INTO q VALUES ('k${i}', 'v${i}')`); - } - await new Promise(resolve => setTimeout(resolve, 1000)); - stopPush = true; - await pushFinish; - stopPull = true; - await pullFinish; - console.info(await db.stats()); -}) - -test('concurrent-updates', { timeout: 60000 }, async () => { - { - const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 10 }); - await db.exec("CREATE TABLE IF NOT EXISTS three(x TEXT PRIMARY KEY, y, z)"); - await db.exec("DELETE FROM three"); - await db.push(); - await db.close(); - } - let stop = false; - const dbs = []; - for (let i = 0; i < 8; i++) { - dbs.push(await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL })); - } - async function pull(db, i) { - try { - await db.pull(); - } catch (e) { - console.error('pull', i, e); - } finally { - if (!stop) { - setTimeout(async () => await pull(db, i), 0); - } - } - } - async function push(db, i) { - try { - await db.push(); - } catch (e) { - console.error('push', i, e); - } finally { - if (!stop) { - setTimeout(async () => await push(db, i), 0); - } - } - } - for (let i = 0; i < dbs.length; i++) { - setTimeout(async () => await pull(dbs[i], i), 0) - setTimeout(async () => await push(dbs[i], i), 0) - } - for (let i = 0; i < 1000; i++) { - try { - const tasks = []; - for (let s = 0; s < dbs.length; s++) { - tasks.push(dbs[s].exec(`INSERT INTO three VALUES ('${s}', 0, randomblob(128)) ON CONFLICT DO UPDATE SET y = y + 1, z = randomblob(128)`)); - } - await Promise.all(tasks); - } catch (e) { - // ignore - } - await new Promise(resolve => setTimeout(resolve, 1)); - } - stop = true; - await Promise.all(dbs.map(db => db.push())); - await Promise.all(dbs.map(db => db.pull())); - let results = []; - for (let i = 0; i < dbs.length; i++) { - results.push(await dbs[i].prepare('SELECT x, y FROM three').all()); - } - for (let i = 0; i < dbs.length; i++) { - expect(results[i]).toEqual(results[0]); - for (let s = 0; s < dbs.length; s++) { - expect(results[i][s].y).toBeGreaterThan(500); - } - } -}) - -test('transform', async () => { +test('checkpoint-and-actions', async () => { { const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, + longPollTimeoutMs: 100, }); - await db.exec("CREATE TABLE IF NOT EXISTS counter(key TEXT PRIMARY KEY, value INTEGER)"); - await db.exec("DELETE FROM counter"); - await db.exec("INSERT INTO counter VALUES ('1', 0)") + await db.exec("CREATE TABLE IF NOT EXISTS rows(key TEXT PRIMARY KEY, value INTEGER)"); + await db.exec("DELETE FROM rows"); + await db.exec("INSERT INTO rows VALUES ('key', 0)"); await db.push(); await db.close(); } - const transform = (m: DatabaseRowMutation) => ({ - operation: 'rewrite', - stmt: { - sql: `UPDATE counter SET value = value + ? WHERE key = ?`, - values: [m.after.value - m.before.value, m.after.key] + const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db1.exec("PRAGMA busy_timeout=100"); + console.info('run_info', await db1.prepare("SELECT * FROM sqlite_master").all()); + const pull = async function (iterations: number) { + for (let i = 0; i < iterations; i++) { + console.info('pull', i); + try { + await db1.pull(); + } + catch (e) { console.error('pull', e); } + await new Promise(resolve => setTimeout(resolve, 0)); } - } as DatabaseRowTransformResult); - const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform }); - const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform }); - - await db1.exec("UPDATE counter SET value = value + 1 WHERE key = '1'"); - await db2.exec("UPDATE counter SET value = value + 1 WHERE key = '1'"); - - await Promise.all([db1.push(), db2.push()]); - await Promise.all([db1.pull(), db2.pull()]); - - const rows1 = await db1.prepare('SELECT * FROM counter').all(); - const rows2 = await db2.prepare('SELECT * FROM counter').all(); - expect(rows1).toEqual([{ key: '1', value: 2 }]); - expect(rows2).toEqual([{ key: '1', value: 2 }]); + } + const push = async function (iterations: number) { + for (let i = 0; i < iterations; i++) { + await new Promise(resolve => setTimeout(resolve, 10)); + console.info('push', i); + try { + if ((await db1.stats()).operations > 0) { + const start = performance.now(); + await db1.push(); + console.info('push', performance.now() - start); + } + } + catch (e) { console.error('push', e); } + } + } + const run = async function (iterations: number) { + let rows = 0; + for (let i = 0; i < iterations; i++) { + console.info('run', i, rows); + await db1.prepare("UPDATE rows SET value = value + 1 WHERE key = ?").run('key'); + rows += 1; + const { cnt } = await db1.prepare("SELECT value as cnt FROM rows WHERE key = ?").get(['key']); + expect(cnt).toBe(rows); + await new Promise(resolve => setTimeout(resolve, 1)); + } + } + await Promise.all([pull(20), push(20), run(1000)]); }) -test('transform-many', async () => { - { - const db = await connect({ - path: ':memory:', - url: process.env.VITE_TURSO_DB_URL, - }); - await db.exec("CREATE TABLE IF NOT EXISTS counter(key TEXT PRIMARY KEY, value INTEGER)"); - await db.exec("DELETE FROM counter"); - await db.exec("INSERT INTO counter VALUES ('1', 0)") - await db.push(); - await db.close(); - } - const transform = (m: DatabaseRowMutation) => ({ - operation: 'rewrite', - stmt: { - sql: `UPDATE counter SET value = value + ? WHERE key = ?`, - values: [m.after.value - m.before.value, m.after.key] - } - } as DatabaseRowTransformResult); - const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform }); - const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform }); +// test('implicit connect', async () => { +// const db = new Database({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// const defer = db.prepare("SELECT * FROM not_found"); +// await expect(async () => await defer.all()).rejects.toThrowError(/no such table: not_found/); +// expect(() => db.prepare("SELECT * FROM not_found")).toThrowError(/no such table: not_found/); +// expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]); +// }) - for (let i = 0; i < 1002; i++) { - await db1.exec("UPDATE counter SET value = value + 1 WHERE key = '1'"); - } - for (let i = 0; i < 1001; i++) { - await db2.exec("UPDATE counter SET value = value + 1 WHERE key = '1'"); - } +// test('simple-db', async () => { +// const db = new Database({ path: ':memory:' }); +// expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]) +// await db.exec("CREATE TABLE t(x)"); +// await db.exec("INSERT INTO t VALUES (1), (2), (3)"); +// expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]) +// await expect(async () => await db.pull()).rejects.toThrowError(/sync is disabled as database was opened without sync support/); +// }) - let start = performance.now(); - await Promise.all([db1.push(), db2.push()]); - console.info('push', performance.now() - start); +// test('implicit connect', async () => { +// const db = new Database({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// const defer = db.prepare("SELECT * FROM not_found"); +// await expect(async () => await defer.all()).rejects.toThrowError(/no such table: not_found/); +// expect(() => db.prepare("SELECT * FROM not_found")).toThrowError(/no such table: not_found/); +// expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]); +// }) - start = performance.now(); - await Promise.all([db1.pull(), db2.pull()]); - console.info('pull', performance.now() - start); +// test('defered sync', async () => { +// { +// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); +// await db.exec("DELETE FROM t"); +// await db.exec("INSERT INTO t VALUES (100)"); +// await db.push(); +// await db.close(); +// } - const rows1 = await db1.prepare('SELECT * FROM counter').all(); - const rows2 = await db2.prepare('SELECT * FROM counter').all(); - expect(rows1).toEqual([{ key: '1', value: 1001 + 1002 }]); - expect(rows2).toEqual([{ key: '1', value: 1001 + 1002 }]); -}) \ No newline at end of file +// let url = null; +// const db = new Database({ path: ':memory:', url: () => url }); +// await db.prepare("CREATE TABLE t(x)").run(); +// await db.prepare("INSERT INTO t VALUES (1), (2), (3), (42)").run(); +// expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 42 }]); +// await expect(async () => await db.pull()).rejects.toThrow(/url is empty - sync is paused/); +// url = process.env.VITE_TURSO_DB_URL; +// await db.pull(); +// expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 100 }, { x: 1 }, { x: 2 }, { x: 3 }, { x: 42 }]); +// }) + +// test('encryption sync', async () => { +// const KEY = 'l/FWopMfZisTLgBX4A42AergrCrYKjiO3BfkJUwv83I='; +// const URL = 'http://encrypted--a--a.localhost:10000'; +// { +// const db = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } }); +// await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); +// await db.exec("DELETE FROM t"); +// await db.push(); +// await db.close(); +// } +// const db1 = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } }); +// const db2 = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } }); +// await db1.exec("INSERT INTO t VALUES (1), (2), (3)"); +// await db2.exec("INSERT INTO t VALUES (4), (5), (6)"); +// expect(await db1.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]); +// expect(await db2.prepare("SELECT * FROM t").all()).toEqual([{ x: 4 }, { x: 5 }, { x: 6 }]); +// await Promise.all([db1.push(), db2.push()]); +// await Promise.all([db1.pull(), db2.pull()]); +// const expected = [{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }, { x: 5 }, { x: 6 }]; +// expect((await db1.prepare("SELECT * FROM t").all()).sort(intCompare)).toEqual(expected.sort(intCompare)); +// expect((await db2.prepare("SELECT * FROM t").all()).sort(intCompare)).toEqual(expected.sort(intCompare)); +// }); + +// test('defered encryption sync', async () => { +// const URL = 'http://encrypted--a--a.localhost:10000'; +// const KEY = 'l/FWopMfZisTLgBX4A42AergrCrYKjiO3BfkJUwv83I='; +// let url = null; +// { +// const db = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } }); +// await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); +// await db.exec("DELETE FROM t"); +// await db.exec("INSERT INTO t VALUES (100)"); +// await db.push(); +// await db.close(); +// } +// const db = await connect({ path: ':memory:', url: () => url, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } }); +// await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); +// await db.exec("INSERT INTO t VALUES (1), (2), (3)"); +// expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]); + +// url = URL; +// await db.pull(); + +// const expected = [{ x: 100 }, { x: 1 }, { x: 2 }, { x: 3 }]; +// expect((await db.prepare("SELECT * FROM t").all())).toEqual(expected); +// }); + +// test('select-after-push', async () => { +// { +// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); +// await db.exec("DELETE FROM t"); +// await db.push(); +// await db.close(); +// } +// { +// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db.exec("INSERT INTO t VALUES (1), (2), (3)"); +// await db.push(); +// } +// { +// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// const rows = await db.prepare('SELECT * FROM t').all(); +// expect(rows).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]) +// } +// }) + +// test('select-without-push', async () => { +// { +// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); +// await db.exec("DELETE FROM t"); +// await db.push(); +// await db.close(); +// } +// { +// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db.exec("INSERT INTO t VALUES (1), (2), (3)"); +// } +// { +// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// const rows = await db.prepare('SELECT * FROM t').all(); +// expect(rows).toEqual([]) +// } +// }) + +// test('merge-non-overlapping-keys', async () => { +// { +// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); +// await db.exec("DELETE FROM q"); +// await db.push(); +// await db.close(); +// } +// const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2')"); + +// const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db2.exec("INSERT INTO q VALUES ('k3', 'value3'), ('k4', 'value4'), ('k5', 'value5')"); + +// await Promise.all([db1.push(), db2.push()]); +// await Promise.all([db1.pull(), db2.pull()]); + +// const rows1 = await db1.prepare('SELECT * FROM q').all(); +// const rows2 = await db1.prepare('SELECT * FROM q').all(); +// const expected = [{ x: 'k1', y: 'value1' }, { x: 'k2', y: 'value2' }, { x: 'k3', y: 'value3' }, { x: 'k4', y: 'value4' }, { x: 'k5', y: 'value5' }]; +// expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare)) +// expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare)) +// }) + +// test('last-push-wins', async () => { +// { +// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); +// await db.exec("DELETE FROM q"); +// await db.push(); +// await db.close(); +// } +// const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2'), ('k4', 'value4')"); + +// const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db2.exec("INSERT INTO q VALUES ('k1', 'value3'), ('k2', 'value4'), ('k3', 'value5')"); + +// await db2.push(); +// await db1.push(); +// await Promise.all([db1.pull(), db2.pull()]); + +// const rows1 = await db1.prepare('SELECT * FROM q').all(); +// const rows2 = await db1.prepare('SELECT * FROM q').all(); +// const expected = [{ x: 'k1', y: 'value1' }, { x: 'k2', y: 'value2' }, { x: 'k3', y: 'value5' }, { x: 'k4', y: 'value4' }]; +// expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare)) +// expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare)) +// }) + +// test('last-push-wins-with-delete', async () => { +// { +// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); +// await db.exec("DELETE FROM q"); +// await db.push(); +// await db.close(); +// } +// const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2'), ('k4', 'value4')"); +// await db1.exec("DELETE FROM q") + +// const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db2.exec("INSERT INTO q VALUES ('k1', 'value3'), ('k2', 'value4'), ('k3', 'value5')"); + +// await db2.push(); +// await db1.push(); +// await Promise.all([db1.pull(), db2.pull()]); + +// const rows1 = await db1.prepare('SELECT * FROM q').all(); +// const rows2 = await db1.prepare('SELECT * FROM q').all(); +// const expected = [{ x: 'k3', y: 'value5' }]; +// expect(rows1).toEqual(expected) +// expect(rows2).toEqual(expected) +// }) + +// test('constraint-conflict', async () => { +// { +// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db.exec("CREATE TABLE IF NOT EXISTS u(x TEXT PRIMARY KEY, y UNIQUE)"); +// await db.exec("DELETE FROM u"); +// await db.push(); +// await db.close(); +// } +// const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db1.exec("INSERT INTO u VALUES ('k1', 'value1')"); + +// const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db2.exec("INSERT INTO u VALUES ('k2', 'value1')"); + +// await db1.push(); +// await expect(async () => await db2.push()).rejects.toThrow('SQLite error: UNIQUE constraint failed: u.y'); +// }) + +// test('checkpoint', async () => { +// { +// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); +// await db.exec("DELETE FROM q"); +// await db.push(); +// await db.close(); +// } +// const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// for (let i = 0; i < 1000; i++) { +// await db1.exec(`INSERT INTO q VALUES ('k${i}', 'v${i}')`); +// } +// expect((await db1.stats()).mainWal).toBeGreaterThan(4096 * 1000); +// await db1.checkpoint(); +// expect((await db1.stats()).mainWal).toBe(0); +// let revertWal = (await db1.stats()).revertWal; +// expect(revertWal).toBeLessThan(4096 * 1000 / 100); + +// for (let i = 0; i < 1000; i++) { +// await db1.exec(`UPDATE q SET y = 'u${i}' WHERE x = 'k${i}'`); +// } +// await db1.checkpoint(); +// expect((await db1.stats()).revertWal).toBe(revertWal); +// }) + +// test('persistence-push', async () => { +// { +// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); +// await db.exec("DELETE FROM q"); +// await db.push(); +// await db.close(); +// } +// const path = `test-${(Math.random() * 10000) | 0}.db`; +// { +// const db1 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); +// await db1.exec(`INSERT INTO q VALUES ('k1', 'v1')`); +// await db1.exec(`INSERT INTO q VALUES ('k2', 'v2')`); +// await db1.close(); +// } + +// { +// const db2 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); +// await db2.exec(`INSERT INTO q VALUES ('k3', 'v3')`); +// await db2.exec(`INSERT INTO q VALUES ('k4', 'v4')`); +// const stmt = db2.prepare('SELECT * FROM q'); +// const rows = await stmt.all(); +// const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }]; +// expect(rows).toEqual(expected) +// stmt.close(); +// await db2.close(); +// } + +// { +// const db3 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); +// await db3.push(); +// await db3.close(); +// } + +// { +// const db4 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); +// const rows = await db4.prepare('SELECT * FROM q').all(); +// const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }]; +// expect(rows).toEqual(expected) +// await db4.close(); +// } +// }) + +// test('persistence-offline', async () => { +// { +// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); +// await db.exec("DELETE FROM q"); +// await db.push(); +// await db.close(); +// } +// const path = `test-${(Math.random() * 10000) | 0}.db`; +// { +// const db = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); +// await db.exec(`INSERT INTO q VALUES ('k1', 'v1')`); +// await db.exec(`INSERT INTO q VALUES ('k2', 'v2')`); +// await db.push(); +// await db.close(); +// } +// { +// const db = await connect({ path: path, url: "https://not-valid-url.localhost" }); +// const rows = await db.prepare("SELECT * FROM q").all(); +// const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }]; +// expect(rows.sort(localeCompare)).toEqual(expected.sort(localeCompare)) +// await db.close(); +// } +// }) + +// test('persistence-pull-push', async () => { +// { +// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); +// await db.exec("DELETE FROM q"); +// await db.push(); +// await db.close(); +// } +// const path1 = `test-${(Math.random() * 10000) | 0}.db`; +// const path2 = `test-${(Math.random() * 10000) | 0}.db`; +// const db1 = await connect({ path: path1, url: process.env.VITE_TURSO_DB_URL }); +// await db1.exec(`INSERT INTO q VALUES ('k1', 'v1')`); +// await db1.exec(`INSERT INTO q VALUES ('k2', 'v2')`); +// const stats1 = await db1.stats(); + +// const db2 = await connect({ path: path2, url: process.env.VITE_TURSO_DB_URL }); +// await db2.exec(`INSERT INTO q VALUES ('k3', 'v3')`); +// await db2.exec(`INSERT INTO q VALUES ('k4', 'v4')`); + +// await Promise.all([db1.push(), db2.push()]); +// await Promise.all([db1.pull(), db2.pull()]); +// const stats2 = await db1.stats(); +// console.info(stats1, stats2); +// expect(stats1.revision).not.toBe(stats2.revision); + +// const rows1 = await db1.prepare('SELECT * FROM q').all(); +// const rows2 = await db2.prepare('SELECT * FROM q').all(); +// const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }]; +// expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare)) +// expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare)) +// }) + +// test('pull-push-concurrent', async () => { +// { +// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 5000 }); +// await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); +// await db.exec("DELETE FROM q"); +// await db.push(); +// await db.close(); +// } +// let pullResolve = null; +// const pullFinish = new Promise(resolve => pullResolve = resolve); +// let pushResolve = null; +// const pushFinish = new Promise(resolve => pushResolve = resolve); +// let stopPull = false; +// let stopPush = false; +// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); +// let pull = async () => { +// try { +// await db.pull(); +// } catch (e) { +// console.error('pull', e); +// } finally { +// if (!stopPull) { +// setTimeout(pull, 0); +// } else { +// pullResolve() +// } +// } +// } +// let push = async () => { +// try { +// if ((await db.stats()).operations > 0) { +// await db.push(); +// } +// } catch (e) { +// console.error('push', e); +// } finally { +// if (!stopPush) { +// setTimeout(push, 0); +// } else { +// pushResolve(); +// } +// } +// } +// setTimeout(pull, 0); +// setTimeout(push, 0); +// for (let i = 0; i < 1000; i++) { +// await db.exec(`INSERT INTO q VALUES ('k${i}', 'v${i}')`); +// } +// await new Promise(resolve => setTimeout(resolve, 1000)); +// stopPush = true; +// await pushFinish; +// stopPull = true; +// await pullFinish; +// console.info(await db.stats()); +// }) + +// test('concurrent-updates', { timeout: 60000 }, async () => { +// { +// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 10 }); +// await db.exec("CREATE TABLE IF NOT EXISTS three(x TEXT PRIMARY KEY, y, z)"); +// await db.exec("DELETE FROM three"); +// await db.push(); +// await db.close(); +// } +// let stop = false; +// const dbs = []; +// for (let i = 0; i < 8; i++) { +// dbs.push(await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL })); +// } +// async function pull(db, i) { +// try { +// await db.pull(); +// } catch (e) { +// console.error('pull', i, e); +// } finally { +// if (!stop) { +// setTimeout(async () => await pull(db, i), 0); +// } +// } +// } +// async function push(db, i) { +// try { +// await db.push(); +// } catch (e) { +// console.error('push', i, e); +// } finally { +// if (!stop) { +// setTimeout(async () => await push(db, i), 0); +// } +// } +// } +// for (let i = 0; i < dbs.length; i++) { +// setTimeout(async () => await pull(dbs[i], i), 0) +// setTimeout(async () => await push(dbs[i], i), 0) +// } +// for (let i = 0; i < 1000; i++) { +// try { +// const tasks = []; +// for (let s = 0; s < dbs.length; s++) { +// tasks.push(dbs[s].exec(`INSERT INTO three VALUES ('${s}', 0, randomblob(128)) ON CONFLICT DO UPDATE SET y = y + 1, z = randomblob(128)`)); +// } +// await Promise.all(tasks); +// } catch (e) { +// // ignore +// } +// await new Promise(resolve => setTimeout(resolve, 1)); +// } +// stop = true; +// await Promise.all(dbs.map(db => db.push())); +// await Promise.all(dbs.map(db => db.pull())); +// let results = []; +// for (let i = 0; i < dbs.length; i++) { +// results.push(await dbs[i].prepare('SELECT x, y FROM three').all()); +// } +// for (let i = 0; i < dbs.length; i++) { +// expect(results[i]).toEqual(results[0]); +// for (let s = 0; s < dbs.length; s++) { +// expect(results[i][s].y).toBeGreaterThan(500); +// } +// } +// }) + +// test('transform', async () => { +// { +// const db = await connect({ +// path: ':memory:', +// url: process.env.VITE_TURSO_DB_URL, +// }); +// await db.exec("CREATE TABLE IF NOT EXISTS counter(key TEXT PRIMARY KEY, value INTEGER)"); +// await db.exec("DELETE FROM counter"); +// await db.exec("INSERT INTO counter VALUES ('1', 0)") +// await db.push(); +// await db.close(); +// } +// const transform = (m: DatabaseRowMutation) => ({ +// operation: 'rewrite', +// stmt: { +// sql: `UPDATE counter SET value = value + ? WHERE key = ?`, +// values: [m.after.value - m.before.value, m.after.key] +// } +// } as DatabaseRowTransformResult); +// const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform }); +// const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform }); + +// await db1.exec("UPDATE counter SET value = value + 1 WHERE key = '1'"); +// await db2.exec("UPDATE counter SET value = value + 1 WHERE key = '1'"); + +// await Promise.all([db1.push(), db2.push()]); +// await Promise.all([db1.pull(), db2.pull()]); + +// const rows1 = await db1.prepare('SELECT * FROM counter').all(); +// const rows2 = await db2.prepare('SELECT * FROM counter').all(); +// expect(rows1).toEqual([{ key: '1', value: 2 }]); +// expect(rows2).toEqual([{ key: '1', value: 2 }]); +// }) + +// test('transform-many', async () => { +// { +// const db = await connect({ +// path: ':memory:', +// url: process.env.VITE_TURSO_DB_URL, +// }); +// await db.exec("CREATE TABLE IF NOT EXISTS counter(key TEXT PRIMARY KEY, value INTEGER)"); +// await db.exec("DELETE FROM counter"); +// await db.exec("INSERT INTO counter VALUES ('1', 0)") +// await db.push(); +// await db.close(); +// } +// const transform = (m: DatabaseRowMutation) => ({ +// operation: 'rewrite', +// stmt: { +// sql: `UPDATE counter SET value = value + ? WHERE key = ?`, +// values: [m.after.value - m.before.value, m.after.key] +// } +// } as DatabaseRowTransformResult); +// const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform }); +// const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform }); + +// for (let i = 0; i < 1002; i++) { +// await db1.exec("UPDATE counter SET value = value + 1 WHERE key = '1'"); +// } +// for (let i = 0; i < 1001; i++) { +// await db2.exec("UPDATE counter SET value = value + 1 WHERE key = '1'"); +// } + +// let start = performance.now(); +// await Promise.all([db1.push(), db2.push()]); +// console.info('push', performance.now() - start); + +// start = performance.now(); +// await Promise.all([db1.pull(), db2.pull()]); +// console.info('pull', performance.now() - start); + +// const rows1 = await db1.prepare('SELECT * FROM counter').all(); +// const rows2 = await db2.prepare('SELECT * FROM counter').all(); +// expect(rows1).toEqual([{ key: '1', value: 1001 + 1002 }]); +// expect(rows2).toEqual([{ key: '1', value: 1001 + 1002 }]); +// }) \ No newline at end of file From 82d54999b1411897c634a37976fcf165f3c50a3c Mon Sep 17 00:00:00 2001 From: Nikita Sivukhin Date: Wed, 29 Oct 2025 12:23:11 +0400 Subject: [PATCH 4/8] fix pull operation in sync engine - before we fetched pull generation and last_change_id from the remote during pull - which is incorrect because fetched information can be inconsistent with WAL updates we received from the server (latest server state can be in "future" compared to the WAL updates we got since we can make push in parallel with updates pull operation) - now we read information about "server state" (pull generation, last_change_id) directly from the local DB right after we applied changes from the remote which get us consistent view on the state considering WAL updates we got - also fetching remote in the pull is bad - since pull block writes and network call with unpredictable latency poorly affect writes to the database --- sync/engine/src/database_sync_engine.rs | 162 +++++++++++++------- sync/engine/src/database_sync_operations.rs | 62 +++++--- 2 files changed, 141 insertions(+), 83 deletions(-) diff --git a/sync/engine/src/database_sync_engine.rs b/sync/engine/src/database_sync_engine.rs index 54f6c91a3..63145daf8 100644 --- a/sync/engine/src/database_sync_engine.rs +++ b/sync/engine/src/database_sync_engine.rs @@ -9,7 +9,7 @@ use crate::{ database_replay_generator::DatabaseReplayGenerator, database_sync_operations::{ acquire_slot, apply_transformation, bootstrap_db_file, connect_untracked, - count_local_changes, fetch_last_change_id, has_table, push_logical_changes, read_wal_salt, + count_local_changes, has_table, push_logical_changes, read_last_change_id, read_wal_salt, reset_wal_file, update_last_change_id, wait_all_results, wal_apply_from_file, wal_pull_to_file, PAGE_SIZE, WAL_FRAME_HEADER, WAL_FRAME_SIZE, }, @@ -507,51 +507,36 @@ impl DatabaseSyncEngine

{ let mut revert_session = WalSession::new(revert_conn.clone()); revert_session.begin()?; + // start of the pull updates apply process + // during this process we need to be very careful with the state of the WAL as at some points it can be not safe to read data from it + // the reasons why this can be not safe: + // 1. we are in the middle of rollback or apply from remote WAL - so DB now is in some weird state and no operations can be made safely + // 2. after rollback or apply from remote WAL it's unsafe to prepare statements because schema cookie can go "back in time" and we first need to adjust it before executing any statement over DB let mut main_session = WalSession::new(main_conn.clone()); main_session.begin()?; + // we need to make sure that updates from the session will not be commited accidentally in the middle of the pull process + // in order to achieve that we mark current session as "nested program" which eliminates possibility that data will be actually commited without our explicit command + // + // the reason to not use auto-commit is because it has its own rules which resets the flag in case of statement reset - which we do under the hood sometimes + // that's why nested executed was chosen instead of auto-commit=false mode + main_conn.start_nested(); + let had_cdc_table = has_table(coro, &main_conn, "turso_cdc").await?; + // read current pull generation from local table for the given client + let (local_pull_gen, _) = + read_last_change_id(coro, &main_conn, &self.client_unique_id).await?; + // read schema version after initiating WAL session (in order to read it with consistent max_frame_no) + // note, that as we initiated WAL session earlier - no changes can be made in between and we will have consistent race-free view of schema version let main_conn_schema_version = main_conn.read_schema_version()?; let mut main_session = DatabaseWalSession::new(coro, main_session).await?; - // fetch last_change_id from remote - let (pull_gen, last_change_id) = fetch_last_change_id( - coro, - self.protocol.as_ref(), - &main_conn, - &self.client_unique_id, - ) - .await?; + // Phase 1 (start): rollback local changes from the WAL - // collect local changes before doing anything with the main DB - // it's important to do this after opening WAL session - otherwise we can miss some updates - let iterate_opts = DatabaseChangesIteratorOpts { - first_change_id: last_change_id.map(|x| x + 1), - mode: DatabaseChangesIteratorMode::Apply, - ignore_schema_changes: false, - ..Default::default() - }; - let mut local_changes = Vec::new(); - let mut iterator = self.main_tape.iterate_changes(iterate_opts)?; - while let Some(operation) = iterator.next(coro).await? { - match operation { - DatabaseTapeOperation::StmtReplay(_) => { - panic!("changes iterator must not use StmtReplay option") - } - DatabaseTapeOperation::RowChange(change) => local_changes.push(change), - DatabaseTapeOperation::Commit => continue, - } - } - tracing::info!( - "apply_changes(path={}): collected {} changes", - self.main_db_path, - local_changes.len() - ); - - // rollback local changes not checkpointed to the revert-db + // Phase 1.a: rollback local changes not checkpointed to the revert-db tracing::info!( "apply_changes(path={}): rolling back frames after {} watermark, max_frame={}", self.main_db_path, @@ -567,14 +552,14 @@ impl DatabaseSyncEngine

{ self.main_db_path, remote_rollback ); - // rollback local changes by using frames from revert-db + // Phase 1.b: rollback local changes by using frames from revert-db // it's important to append pages from revert-db after local revert - because pages from revert-db must overwrite rollback from main DB for frame_no in 1..=remote_rollback { let info = revert_session.read_at(frame_no, &mut frame)?; main_session.append_page(info.page_no, &frame[WAL_FRAME_HEADER..])?; } - // after rollback - WAL state is aligned with remote - let's apply changes from it + // Phase 2: after revert DB has no local changes in its latest state - so its safe to apply changes from remote let db_size = wal_apply_from_file(coro, changes_file, &mut main_session).await?; tracing::info!( "apply_changes(path={}): applied changes from remote: db_size={}", @@ -582,27 +567,83 @@ impl DatabaseSyncEngine

{ db_size, ); - let revert_since_wal_watermark; - if local_changes.is_empty() && local_rollback == 0 && remote_rollback == 0 && !had_cdc_table - { - main_session.commit(db_size)?; - revert_since_wal_watermark = main_session.frames_count()?; - main_session.wal_session.end(false)?; - } else { - main_session.commit(0)?; - let current_schema_version = main_conn.read_schema_version()?; - revert_since_wal_watermark = main_session.frames_count()?; - let final_schema_version = current_schema_version.max(main_conn_schema_version) + 1; - main_conn.write_schema_version(final_schema_version)?; - tracing::info!( - "apply_changes(path={}): updated schema version to {}", - self.main_db_path, - final_schema_version - ); + main_session.commit(0)?; + // now DB is equivalent to the some remote state (all local changes reverted, all remote changes applied) + // remember this frame watermark as a checkpoint for revert for pull operations in future + let revert_since_wal_watermark = main_session.frames_count()?; - update_last_change_id(coro, &main_conn, &self.client_unique_id, pull_gen + 1, 0) - .await - .inspect_err(|e| tracing::error!("update_last_change_id failed: {e}"))?; + // Phase 3: DB now has sane WAL - but schema cookie can be arbitrary - so we need to bump it (potentially forcing re-prepare for cached statement) + let current_schema_version = main_conn.read_schema_version()?; + let final_schema_version = current_schema_version.max(main_conn_schema_version) + 1; + main_conn.write_schema_version(final_schema_version)?; + tracing::info!( + "apply_changes(path={}): updated schema version to {}", + self.main_db_path, + final_schema_version + ); + + // Phase 4: as now DB has all data from remote - let's read pull generation and last change id for current client + // we will use last_change_id in order to replay local changes made strictly after that id locally + let (remote_pull_gen, remote_last_change_id) = + read_last_change_id(coro, &main_conn, &self.client_unique_id).await?; + + // we update pull generation and last_change_id at remote on push, but locally its updated on pull + // so its impossible to have remote pull generation to be greater than local one + if remote_pull_gen > local_pull_gen { + return Err(Error::DatabaseSyncEngineError(format!("protocol error: remote_pull_gen > local_pull_gen: {remote_pull_gen} > {local_pull_gen}"))); + } + let last_change_id = if remote_pull_gen == local_pull_gen { + // if remote_pull_gen == local_pull gen - this means that remote portion of WAL have overlap with our local changes + // (because we did one or more push operations since last pull) - so we need to take some suffix of local changes for replay + remote_last_change_id + } else { + // if remove_pull_gen < local_pull_gen - this means that remote portion of WAL have no overlaps with all our local changes and we need to replay all of them + Some(0) + }; + + // Phase 5: collect local changes + // note, that collecting chanages from main_conn will yield zero rows as we already rolled back everything from it + // but since we didn't commited these changes yet - we can just collect changes from another connection + let iterate_opts = DatabaseChangesIteratorOpts { + first_change_id: last_change_id.map(|x| x + 1), + mode: DatabaseChangesIteratorMode::Apply, + ignore_schema_changes: false, + ..Default::default() + }; + let mut local_changes = Vec::new(); + { + // it's important here that DatabaseTape create fresh connection under the hood + let mut iterator = self.main_tape.iterate_changes(iterate_opts)?; + while let Some(operation) = iterator.next(coro).await? { + match operation { + DatabaseTapeOperation::StmtReplay(_) => { + panic!("changes iterator must not use StmtReplay option") + } + DatabaseTapeOperation::RowChange(change) => local_changes.push(change), + DatabaseTapeOperation::Commit => continue, + } + } + } + tracing::info!( + "apply_changes(path={}): collected {} changes", + self.main_db_path, + local_changes.len() + ); + + // Phase 6: replay local changes + // we can skip this phase if we are sure that we had no local changes before + if !local_changes.is_empty() || local_rollback != 0 || remote_rollback != 0 || had_cdc_table + { + // first, we update last_change id in the local meta table for sync + update_last_change_id( + coro, + &main_conn, + &self.client_unique_id, + local_pull_gen + 1, + 0, + ) + .await + .inspect_err(|e| tracing::error!("update_last_change_id failed: {e}"))?; if had_cdc_table { tracing::info!( @@ -641,6 +682,7 @@ impl DatabaseSyncEngine

{ }; assert!(!replay.conn().get_auto_commit()); + // Replay local changes collected on Phase 5 for (i, change) in local_changes.into_iter().enumerate() { let operation = if let Some(transformed) = &mut transformed { match std::mem::replace(&mut transformed[i], DatabaseRowTransformResult::Skip) { @@ -658,10 +700,12 @@ impl DatabaseSyncEngine

{ replay.replay(coro, operation).await?; } assert!(!replay.conn().get_auto_commit()); - - main_session.wal_session.end(true)?; } + // Final: now we did all necessary operations as one big transaction and we are ready to commit + main_conn.end_nested(); + main_session.wal_session.end(true)?; + Ok(revert_since_wal_watermark) } diff --git a/sync/engine/src/database_sync_operations.rs b/sync/engine/src/database_sync_operations.rs index cc8d63aa3..327a691c4 100644 --- a/sync/engine/src/database_sync_operations.rs +++ b/sync/engine/src/database_sync_operations.rs @@ -589,6 +589,40 @@ pub async fn update_last_change_id( Ok(()) } +pub async fn read_last_change_id( + coro: &Coro, + conn: &Arc, + client_id: &str, +) -> Result<(i64, Option)> { + tracing::info!("read_last_change_id: client_id={client_id}"); + + // fetch last_change_id from the target DB in order to guarantee atomic replay of changes and avoid conflicts in case of failure + let mut select_last_change_id_stmt = match conn.prepare(TURSO_SYNC_SELECT_LAST_CHANGE_ID) + { + Ok(stmt) => stmt, + Err(LimboError::ParseError(..)) => return Ok((0, None)), + Err(err) => return Err(err.into()), + }; + + select_last_change_id_stmt.bind_at(1.try_into().unwrap(), Value::Text(Text::new(client_id))); + + match run_stmt_expect_one_row(coro, &mut select_last_change_id_stmt).await? { + Some(row) => { + let pull_gen = row[0].as_int().ok_or_else(|| { + Error::DatabaseSyncEngineError("unexpected source pull_gen type".to_string()) + })?; + let change_id = row[1].as_int().ok_or_else(|| { + Error::DatabaseSyncEngineError("unexpected source change_id type".to_string()) + })?; + Ok((pull_gen, Some(change_id))) + } + None => { + tracing::info!("read_last_change_id: client_id={client_id}, turso_sync_last_change_id client id is not found"); + Ok((0, None)) + } + } +} + pub async fn fetch_last_change_id( coro: &Coro, client: &C, @@ -598,27 +632,7 @@ pub async fn fetch_last_change_id( tracing::info!("fetch_last_change_id: client_id={client_id}"); // fetch last_change_id from the target DB in order to guarantee atomic replay of changes and avoid conflicts in case of failure - let source_pull_gen = 'source_pull_gen: { - let mut select_last_change_id_stmt = - match source_conn.prepare(TURSO_SYNC_SELECT_LAST_CHANGE_ID) { - Ok(stmt) => stmt, - Err(LimboError::ParseError(..)) => break 'source_pull_gen 0, - Err(err) => return Err(err.into()), - }; - - select_last_change_id_stmt - .bind_at(1.try_into().unwrap(), Value::Text(Text::new(client_id))); - - match run_stmt_expect_one_row(coro, &mut select_last_change_id_stmt).await? { - Some(row) => row[0].as_int().ok_or_else(|| { - Error::DatabaseSyncEngineError("unexpected source pull_gen type".to_string()) - })?, - None => { - tracing::info!("fetch_last_change_id: client_id={client_id}, turso_sync_last_change_id table is not found"); - 0 - } - } - }; + let (source_pull_gen, _) = read_last_change_id(coro, source_conn, client_id).await?; tracing::info!( "fetch_last_change_id: client_id={client_id}, source_pull_gen={source_pull_gen}" ); @@ -676,8 +690,8 @@ pub async fn fetch_last_change_id( )); }; tracing::debug!( - "fetch_last_change_id: client_id={client_id}, target_pull_gen={target_pull_gen}, target_change_id={target_change_id}" - ); + "fetch_last_change_id: client_id={client_id}, target_pull_gen={target_pull_gen}, target_change_id={target_change_id}" + ); if target_pull_gen > source_pull_gen { return Err(Error::DatabaseSyncEngineError(format!("protocol error: target_pull_gen > source_pull_gen: {target_pull_gen} > {source_pull_gen}"))); } @@ -893,7 +907,7 @@ pub async fn push_logical_changes( } sql_over_http_requests.push(step("COMMIT".to_string(), Vec::new())); - tracing::trace!("hrana request: {:?}", sql_over_http_requests); + tracing::debug!("hrana request: {:?}", sql_over_http_requests); let replay_hrana_request = server_proto::PipelineReqBody { baton: None, requests: vec![StreamRequest::Batch(BatchStreamReq { From e27b0d5d6bd965926ed9dd81038d6a9fbfdb4b23 Mon Sep 17 00:00:00 2001 From: Nikita Sivukhin Date: Wed, 29 Oct 2025 14:21:30 +0400 Subject: [PATCH 5/8] add more tests --- .../sync/packages/native/promise.test.ts | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/bindings/javascript/sync/packages/native/promise.test.ts b/bindings/javascript/sync/packages/native/promise.test.ts index 2e38b483e..ea95e0f13 100644 --- a/bindings/javascript/sync/packages/native/promise.test.ts +++ b/bindings/javascript/sync/packages/native/promise.test.ts @@ -19,7 +19,6 @@ test('concurrent-actions-consistency', async () => { path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 100, - // tracing: 'trace', }); await db.exec("CREATE TABLE IF NOT EXISTS rows(key TEXT PRIMARY KEY, value INTEGER)"); await db.exec("DELETE FROM rows"); @@ -41,7 +40,7 @@ test('concurrent-actions-consistency', async () => { const push = async function (iterations: number) { for (let i = 0; i < iterations; i++) { await new Promise(resolve => setTimeout(resolve, (Math.random() + 1))); - // console.info('push', i); + console.info('push', i); try { if ((await db1.stats()).operations > 0) { const start = performance.now(); @@ -67,7 +66,7 @@ test('concurrent-actions-consistency', async () => { await new Promise(resolve => setTimeout(resolve, 10 * (Math.random() + 1))); } } - await Promise.all([pull(20), push(20), run(200)]); + await Promise.all([pull(100), push(100), run(200)]); }) test('simple-db', async () => { @@ -450,7 +449,11 @@ test('concurrent-updates', async () => { await db.push(); await db.close(); } - const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, tracing: 'info' }); + const db1 = await connect({ + path: ':memory:', + url: process.env.VITE_TURSO_DB_URL, + }); + await db1.exec("PRAGMA busy_timeout=100"); async function pull(db) { try { await db.pull(); @@ -471,22 +474,47 @@ test('concurrent-updates', async () => { setTimeout(async () => await push(db), 0); } } + setTimeout(async () => await pull(db1), 0) setTimeout(async () => await push(db1), 0) for (let i = 0; i < 1000; i++) { try { - console.info('changes ' + JSON.stringify(await db1.prepare("SELECT change_id FROM turso_cdc").all())); await Promise.all([ - db1.exec(`INSERT INTO q VALUES ('1', 0) ON CONFLICT DO UPDATE SET y = ${i + 1}`), - db1.exec(`INSERT INTO q VALUES ('2', 0) ON CONFLICT DO UPDATE SET y = ${i + 1}`) + db1.exec(`INSERT INTO q VALUES ('1', 0) ON CONFLICT DO UPDATE SET y = randomblob(1024)`), + db1.exec(`INSERT INTO q VALUES ('1', 0) ON CONFLICT DO UPDATE SET y = randomblob(1024)`) ]); + console.info('insert ok'); } catch (e) { - // ignore + console.error('insert error', e); } await new Promise(resolve => setTimeout(resolve, 1)); } }) +test('corruption-bug-1', async () => { + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 5000 }); + await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); + await db.exec("DELETE FROM q"); + await db.push(); + await db.close(); + } + const db1 = await connect({ + path: ':memory:', + url: process.env.VITE_TURSO_DB_URL, + }); + for (let i = 0; i < 100; i++) { + await db1.exec(`INSERT INTO q VALUES ('1', 0) ON CONFLICT DO UPDATE SET y = randomblob(1024)`); + } + await db1.pull(); + await db1.push(); + for (let i = 0; i < 100; i++) { + await db1.exec(`INSERT INTO q VALUES ('1', 0) ON CONFLICT DO UPDATE SET y = randomblob(1024)`); + } + await db1.pull(); + await db1.push(); +}) + test('pull-push-concurrent', async () => { { const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 5000 }); @@ -549,7 +577,6 @@ test('checkpoint-and-actions', async () => { path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 100, - tracing: 'info' }); await db.exec("CREATE TABLE IF NOT EXISTS rows(key TEXT PRIMARY KEY, value INTEGER)"); await db.exec("DELETE FROM rows"); From e5b11a327866ba1fad65c1d1310487edfb408fd5 Mon Sep 17 00:00:00 2001 From: Nikita Sivukhin Date: Wed, 29 Oct 2025 15:43:48 +0400 Subject: [PATCH 6/8] uncomment tests --- .../sync/packages/wasm/promise.test.ts | 1002 ++++++++--------- 1 file changed, 501 insertions(+), 501 deletions(-) diff --git a/bindings/javascript/sync/packages/wasm/promise.test.ts b/bindings/javascript/sync/packages/wasm/promise.test.ts index feb2ce9ca..0e1dd1668 100644 --- a/bindings/javascript/sync/packages/wasm/promise.test.ts +++ b/bindings/javascript/sync/packages/wasm/promise.test.ts @@ -58,551 +58,551 @@ test('checkpoint-and-actions', async () => { await Promise.all([pull(20), push(20), run(1000)]); }) -// test('implicit connect', async () => { -// const db = new Database({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// const defer = db.prepare("SELECT * FROM not_found"); -// await expect(async () => await defer.all()).rejects.toThrowError(/no such table: not_found/); -// expect(() => db.prepare("SELECT * FROM not_found")).toThrowError(/no such table: not_found/); -// expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]); -// }) +test('implicit connect', async () => { + const db = new Database({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + const defer = db.prepare("SELECT * FROM not_found"); + await expect(async () => await defer.all()).rejects.toThrowError(/no such table: not_found/); + expect(() => db.prepare("SELECT * FROM not_found")).toThrowError(/no such table: not_found/); + expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]); +}) -// test('simple-db', async () => { -// const db = new Database({ path: ':memory:' }); -// expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]) -// await db.exec("CREATE TABLE t(x)"); -// await db.exec("INSERT INTO t VALUES (1), (2), (3)"); -// expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]) -// await expect(async () => await db.pull()).rejects.toThrowError(/sync is disabled as database was opened without sync support/); -// }) +test('simple-db', async () => { + const db = new Database({ path: ':memory:' }); + expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]) + await db.exec("CREATE TABLE t(x)"); + await db.exec("INSERT INTO t VALUES (1), (2), (3)"); + expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]) + await expect(async () => await db.pull()).rejects.toThrowError(/sync is disabled as database was opened without sync support/); +}) -// test('implicit connect', async () => { -// const db = new Database({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// const defer = db.prepare("SELECT * FROM not_found"); -// await expect(async () => await defer.all()).rejects.toThrowError(/no such table: not_found/); -// expect(() => db.prepare("SELECT * FROM not_found")).toThrowError(/no such table: not_found/); -// expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]); -// }) +test('implicit connect', async () => { + const db = new Database({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + const defer = db.prepare("SELECT * FROM not_found"); + await expect(async () => await defer.all()).rejects.toThrowError(/no such table: not_found/); + expect(() => db.prepare("SELECT * FROM not_found")).toThrowError(/no such table: not_found/); + expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]); +}) -// test('defered sync', async () => { -// { -// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); -// await db.exec("DELETE FROM t"); -// await db.exec("INSERT INTO t VALUES (100)"); -// await db.push(); -// await db.close(); -// } +test('defered sync', async () => { + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); + await db.exec("DELETE FROM t"); + await db.exec("INSERT INTO t VALUES (100)"); + await db.push(); + await db.close(); + } -// let url = null; -// const db = new Database({ path: ':memory:', url: () => url }); -// await db.prepare("CREATE TABLE t(x)").run(); -// await db.prepare("INSERT INTO t VALUES (1), (2), (3), (42)").run(); -// expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 42 }]); -// await expect(async () => await db.pull()).rejects.toThrow(/url is empty - sync is paused/); -// url = process.env.VITE_TURSO_DB_URL; -// await db.pull(); -// expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 100 }, { x: 1 }, { x: 2 }, { x: 3 }, { x: 42 }]); -// }) + let url = null; + const db = new Database({ path: ':memory:', url: () => url }); + await db.prepare("CREATE TABLE t(x)").run(); + await db.prepare("INSERT INTO t VALUES (1), (2), (3), (42)").run(); + expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 42 }]); + await expect(async () => await db.pull()).rejects.toThrow(/url is empty - sync is paused/); + url = process.env.VITE_TURSO_DB_URL; + await db.pull(); + expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 100 }, { x: 1 }, { x: 2 }, { x: 3 }, { x: 42 }]); +}) -// test('encryption sync', async () => { -// const KEY = 'l/FWopMfZisTLgBX4A42AergrCrYKjiO3BfkJUwv83I='; -// const URL = 'http://encrypted--a--a.localhost:10000'; -// { -// const db = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } }); -// await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); -// await db.exec("DELETE FROM t"); -// await db.push(); -// await db.close(); -// } -// const db1 = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } }); -// const db2 = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } }); -// await db1.exec("INSERT INTO t VALUES (1), (2), (3)"); -// await db2.exec("INSERT INTO t VALUES (4), (5), (6)"); -// expect(await db1.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]); -// expect(await db2.prepare("SELECT * FROM t").all()).toEqual([{ x: 4 }, { x: 5 }, { x: 6 }]); -// await Promise.all([db1.push(), db2.push()]); -// await Promise.all([db1.pull(), db2.pull()]); -// const expected = [{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }, { x: 5 }, { x: 6 }]; -// expect((await db1.prepare("SELECT * FROM t").all()).sort(intCompare)).toEqual(expected.sort(intCompare)); -// expect((await db2.prepare("SELECT * FROM t").all()).sort(intCompare)).toEqual(expected.sort(intCompare)); -// }); +test('encryption sync', async () => { + const KEY = 'l/FWopMfZisTLgBX4A42AergrCrYKjiO3BfkJUwv83I='; + const URL = 'http://encrypted--a--a.localhost:10000'; + { + const db = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } }); + await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); + await db.exec("DELETE FROM t"); + await db.push(); + await db.close(); + } + const db1 = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } }); + const db2 = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } }); + await db1.exec("INSERT INTO t VALUES (1), (2), (3)"); + await db2.exec("INSERT INTO t VALUES (4), (5), (6)"); + expect(await db1.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]); + expect(await db2.prepare("SELECT * FROM t").all()).toEqual([{ x: 4 }, { x: 5 }, { x: 6 }]); + await Promise.all([db1.push(), db2.push()]); + await Promise.all([db1.pull(), db2.pull()]); + const expected = [{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }, { x: 5 }, { x: 6 }]; + expect((await db1.prepare("SELECT * FROM t").all()).sort(intCompare)).toEqual(expected.sort(intCompare)); + expect((await db2.prepare("SELECT * FROM t").all()).sort(intCompare)).toEqual(expected.sort(intCompare)); +}); -// test('defered encryption sync', async () => { -// const URL = 'http://encrypted--a--a.localhost:10000'; -// const KEY = 'l/FWopMfZisTLgBX4A42AergrCrYKjiO3BfkJUwv83I='; -// let url = null; -// { -// const db = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } }); -// await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); -// await db.exec("DELETE FROM t"); -// await db.exec("INSERT INTO t VALUES (100)"); -// await db.push(); -// await db.close(); -// } -// const db = await connect({ path: ':memory:', url: () => url, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } }); -// await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); -// await db.exec("INSERT INTO t VALUES (1), (2), (3)"); -// expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]); +test('defered encryption sync', async () => { + const URL = 'http://encrypted--a--a.localhost:10000'; + const KEY = 'l/FWopMfZisTLgBX4A42AergrCrYKjiO3BfkJUwv83I='; + let url = null; + { + const db = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } }); + await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); + await db.exec("DELETE FROM t"); + await db.exec("INSERT INTO t VALUES (100)"); + await db.push(); + await db.close(); + } + const db = await connect({ path: ':memory:', url: () => url, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } }); + await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); + await db.exec("INSERT INTO t VALUES (1), (2), (3)"); + expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]); -// url = URL; -// await db.pull(); + url = URL; + await db.pull(); -// const expected = [{ x: 100 }, { x: 1 }, { x: 2 }, { x: 3 }]; -// expect((await db.prepare("SELECT * FROM t").all())).toEqual(expected); -// }); + const expected = [{ x: 100 }, { x: 1 }, { x: 2 }, { x: 3 }]; + expect((await db.prepare("SELECT * FROM t").all())).toEqual(expected); +}); -// test('select-after-push', async () => { -// { -// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); -// await db.exec("DELETE FROM t"); -// await db.push(); -// await db.close(); -// } -// { -// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db.exec("INSERT INTO t VALUES (1), (2), (3)"); -// await db.push(); -// } -// { -// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// const rows = await db.prepare('SELECT * FROM t').all(); -// expect(rows).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]) -// } -// }) +test('select-after-push', async () => { + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); + await db.exec("DELETE FROM t"); + await db.push(); + await db.close(); + } + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db.exec("INSERT INTO t VALUES (1), (2), (3)"); + await db.push(); + } + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + const rows = await db.prepare('SELECT * FROM t').all(); + expect(rows).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]) + } +}) -// test('select-without-push', async () => { -// { -// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); -// await db.exec("DELETE FROM t"); -// await db.push(); -// await db.close(); -// } -// { -// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db.exec("INSERT INTO t VALUES (1), (2), (3)"); -// } -// { -// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// const rows = await db.prepare('SELECT * FROM t').all(); -// expect(rows).toEqual([]) -// } -// }) +test('select-without-push', async () => { + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db.exec("CREATE TABLE IF NOT EXISTS t(x)"); + await db.exec("DELETE FROM t"); + await db.push(); + await db.close(); + } + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db.exec("INSERT INTO t VALUES (1), (2), (3)"); + } + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + const rows = await db.prepare('SELECT * FROM t').all(); + expect(rows).toEqual([]) + } +}) -// test('merge-non-overlapping-keys', async () => { -// { -// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); -// await db.exec("DELETE FROM q"); -// await db.push(); -// await db.close(); -// } -// const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2')"); +test('merge-non-overlapping-keys', async () => { + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); + await db.exec("DELETE FROM q"); + await db.push(); + await db.close(); + } + const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2')"); -// const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db2.exec("INSERT INTO q VALUES ('k3', 'value3'), ('k4', 'value4'), ('k5', 'value5')"); + const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db2.exec("INSERT INTO q VALUES ('k3', 'value3'), ('k4', 'value4'), ('k5', 'value5')"); -// await Promise.all([db1.push(), db2.push()]); -// await Promise.all([db1.pull(), db2.pull()]); + await Promise.all([db1.push(), db2.push()]); + await Promise.all([db1.pull(), db2.pull()]); -// const rows1 = await db1.prepare('SELECT * FROM q').all(); -// const rows2 = await db1.prepare('SELECT * FROM q').all(); -// const expected = [{ x: 'k1', y: 'value1' }, { x: 'k2', y: 'value2' }, { x: 'k3', y: 'value3' }, { x: 'k4', y: 'value4' }, { x: 'k5', y: 'value5' }]; -// expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare)) -// expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare)) -// }) + const rows1 = await db1.prepare('SELECT * FROM q').all(); + const rows2 = await db1.prepare('SELECT * FROM q').all(); + const expected = [{ x: 'k1', y: 'value1' }, { x: 'k2', y: 'value2' }, { x: 'k3', y: 'value3' }, { x: 'k4', y: 'value4' }, { x: 'k5', y: 'value5' }]; + expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare)) + expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare)) +}) -// test('last-push-wins', async () => { -// { -// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); -// await db.exec("DELETE FROM q"); -// await db.push(); -// await db.close(); -// } -// const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2'), ('k4', 'value4')"); +test('last-push-wins', async () => { + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); + await db.exec("DELETE FROM q"); + await db.push(); + await db.close(); + } + const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2'), ('k4', 'value4')"); -// const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db2.exec("INSERT INTO q VALUES ('k1', 'value3'), ('k2', 'value4'), ('k3', 'value5')"); + const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db2.exec("INSERT INTO q VALUES ('k1', 'value3'), ('k2', 'value4'), ('k3', 'value5')"); -// await db2.push(); -// await db1.push(); -// await Promise.all([db1.pull(), db2.pull()]); + await db2.push(); + await db1.push(); + await Promise.all([db1.pull(), db2.pull()]); -// const rows1 = await db1.prepare('SELECT * FROM q').all(); -// const rows2 = await db1.prepare('SELECT * FROM q').all(); -// const expected = [{ x: 'k1', y: 'value1' }, { x: 'k2', y: 'value2' }, { x: 'k3', y: 'value5' }, { x: 'k4', y: 'value4' }]; -// expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare)) -// expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare)) -// }) + const rows1 = await db1.prepare('SELECT * FROM q').all(); + const rows2 = await db1.prepare('SELECT * FROM q').all(); + const expected = [{ x: 'k1', y: 'value1' }, { x: 'k2', y: 'value2' }, { x: 'k3', y: 'value5' }, { x: 'k4', y: 'value4' }]; + expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare)) + expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare)) +}) -// test('last-push-wins-with-delete', async () => { -// { -// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); -// await db.exec("DELETE FROM q"); -// await db.push(); -// await db.close(); -// } -// const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2'), ('k4', 'value4')"); -// await db1.exec("DELETE FROM q") +test('last-push-wins-with-delete', async () => { + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); + await db.exec("DELETE FROM q"); + await db.push(); + await db.close(); + } + const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db1.exec("INSERT INTO q VALUES ('k1', 'value1'), ('k2', 'value2'), ('k4', 'value4')"); + await db1.exec("DELETE FROM q") -// const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db2.exec("INSERT INTO q VALUES ('k1', 'value3'), ('k2', 'value4'), ('k3', 'value5')"); + const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db2.exec("INSERT INTO q VALUES ('k1', 'value3'), ('k2', 'value4'), ('k3', 'value5')"); -// await db2.push(); -// await db1.push(); -// await Promise.all([db1.pull(), db2.pull()]); + await db2.push(); + await db1.push(); + await Promise.all([db1.pull(), db2.pull()]); -// const rows1 = await db1.prepare('SELECT * FROM q').all(); -// const rows2 = await db1.prepare('SELECT * FROM q').all(); -// const expected = [{ x: 'k3', y: 'value5' }]; -// expect(rows1).toEqual(expected) -// expect(rows2).toEqual(expected) -// }) + const rows1 = await db1.prepare('SELECT * FROM q').all(); + const rows2 = await db1.prepare('SELECT * FROM q').all(); + const expected = [{ x: 'k3', y: 'value5' }]; + expect(rows1).toEqual(expected) + expect(rows2).toEqual(expected) +}) -// test('constraint-conflict', async () => { -// { -// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db.exec("CREATE TABLE IF NOT EXISTS u(x TEXT PRIMARY KEY, y UNIQUE)"); -// await db.exec("DELETE FROM u"); -// await db.push(); -// await db.close(); -// } -// const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db1.exec("INSERT INTO u VALUES ('k1', 'value1')"); +test('constraint-conflict', async () => { + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db.exec("CREATE TABLE IF NOT EXISTS u(x TEXT PRIMARY KEY, y UNIQUE)"); + await db.exec("DELETE FROM u"); + await db.push(); + await db.close(); + } + const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db1.exec("INSERT INTO u VALUES ('k1', 'value1')"); -// const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db2.exec("INSERT INTO u VALUES ('k2', 'value1')"); + const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db2.exec("INSERT INTO u VALUES ('k2', 'value1')"); -// await db1.push(); -// await expect(async () => await db2.push()).rejects.toThrow('SQLite error: UNIQUE constraint failed: u.y'); -// }) + await db1.push(); + await expect(async () => await db2.push()).rejects.toThrow('SQLite error: UNIQUE constraint failed: u.y'); +}) -// test('checkpoint', async () => { -// { -// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); -// await db.exec("DELETE FROM q"); -// await db.push(); -// await db.close(); -// } -// const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// for (let i = 0; i < 1000; i++) { -// await db1.exec(`INSERT INTO q VALUES ('k${i}', 'v${i}')`); -// } -// expect((await db1.stats()).mainWal).toBeGreaterThan(4096 * 1000); -// await db1.checkpoint(); -// expect((await db1.stats()).mainWal).toBe(0); -// let revertWal = (await db1.stats()).revertWal; -// expect(revertWal).toBeLessThan(4096 * 1000 / 100); +test('checkpoint', async () => { + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); + await db.exec("DELETE FROM q"); + await db.push(); + await db.close(); + } + const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + for (let i = 0; i < 1000; i++) { + await db1.exec(`INSERT INTO q VALUES ('k${i}', 'v${i}')`); + } + expect((await db1.stats()).mainWal).toBeGreaterThan(4096 * 1000); + await db1.checkpoint(); + expect((await db1.stats()).mainWal).toBe(0); + let revertWal = (await db1.stats()).revertWal; + expect(revertWal).toBeLessThan(4096 * 1000 / 100); -// for (let i = 0; i < 1000; i++) { -// await db1.exec(`UPDATE q SET y = 'u${i}' WHERE x = 'k${i}'`); -// } -// await db1.checkpoint(); -// expect((await db1.stats()).revertWal).toBe(revertWal); -// }) + for (let i = 0; i < 1000; i++) { + await db1.exec(`UPDATE q SET y = 'u${i}' WHERE x = 'k${i}'`); + } + await db1.checkpoint(); + expect((await db1.stats()).revertWal).toBe(revertWal); +}) -// test('persistence-push', async () => { -// { -// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); -// await db.exec("DELETE FROM q"); -// await db.push(); -// await db.close(); -// } -// const path = `test-${(Math.random() * 10000) | 0}.db`; -// { -// const db1 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); -// await db1.exec(`INSERT INTO q VALUES ('k1', 'v1')`); -// await db1.exec(`INSERT INTO q VALUES ('k2', 'v2')`); -// await db1.close(); -// } +test('persistence-push', async () => { + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); + await db.exec("DELETE FROM q"); + await db.push(); + await db.close(); + } + const path = `test-${(Math.random() * 10000) | 0}.db`; + { + const db1 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); + await db1.exec(`INSERT INTO q VALUES ('k1', 'v1')`); + await db1.exec(`INSERT INTO q VALUES ('k2', 'v2')`); + await db1.close(); + } -// { -// const db2 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); -// await db2.exec(`INSERT INTO q VALUES ('k3', 'v3')`); -// await db2.exec(`INSERT INTO q VALUES ('k4', 'v4')`); -// const stmt = db2.prepare('SELECT * FROM q'); -// const rows = await stmt.all(); -// const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }]; -// expect(rows).toEqual(expected) -// stmt.close(); -// await db2.close(); -// } + { + const db2 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); + await db2.exec(`INSERT INTO q VALUES ('k3', 'v3')`); + await db2.exec(`INSERT INTO q VALUES ('k4', 'v4')`); + const stmt = db2.prepare('SELECT * FROM q'); + const rows = await stmt.all(); + const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }]; + expect(rows).toEqual(expected) + stmt.close(); + await db2.close(); + } -// { -// const db3 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); -// await db3.push(); -// await db3.close(); -// } + { + const db3 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); + await db3.push(); + await db3.close(); + } -// { -// const db4 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); -// const rows = await db4.prepare('SELECT * FROM q').all(); -// const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }]; -// expect(rows).toEqual(expected) -// await db4.close(); -// } -// }) + { + const db4 = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); + const rows = await db4.prepare('SELECT * FROM q').all(); + const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }]; + expect(rows).toEqual(expected) + await db4.close(); + } +}) -// test('persistence-offline', async () => { -// { -// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); -// await db.exec("DELETE FROM q"); -// await db.push(); -// await db.close(); -// } -// const path = `test-${(Math.random() * 10000) | 0}.db`; -// { -// const db = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); -// await db.exec(`INSERT INTO q VALUES ('k1', 'v1')`); -// await db.exec(`INSERT INTO q VALUES ('k2', 'v2')`); -// await db.push(); -// await db.close(); -// } -// { -// const db = await connect({ path: path, url: "https://not-valid-url.localhost" }); -// const rows = await db.prepare("SELECT * FROM q").all(); -// const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }]; -// expect(rows.sort(localeCompare)).toEqual(expected.sort(localeCompare)) -// await db.close(); -// } -// }) +test('persistence-offline', async () => { + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); + await db.exec("DELETE FROM q"); + await db.push(); + await db.close(); + } + const path = `test-${(Math.random() * 10000) | 0}.db`; + { + const db = await connect({ path: path, url: process.env.VITE_TURSO_DB_URL }); + await db.exec(`INSERT INTO q VALUES ('k1', 'v1')`); + await db.exec(`INSERT INTO q VALUES ('k2', 'v2')`); + await db.push(); + await db.close(); + } + { + const db = await connect({ path: path, url: "https://not-valid-url.localhost" }); + const rows = await db.prepare("SELECT * FROM q").all(); + const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }]; + expect(rows.sort(localeCompare)).toEqual(expected.sort(localeCompare)) + await db.close(); + } +}) -// test('persistence-pull-push', async () => { -// { -// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); -// await db.exec("DELETE FROM q"); -// await db.push(); -// await db.close(); -// } -// const path1 = `test-${(Math.random() * 10000) | 0}.db`; -// const path2 = `test-${(Math.random() * 10000) | 0}.db`; -// const db1 = await connect({ path: path1, url: process.env.VITE_TURSO_DB_URL }); -// await db1.exec(`INSERT INTO q VALUES ('k1', 'v1')`); -// await db1.exec(`INSERT INTO q VALUES ('k2', 'v2')`); -// const stats1 = await db1.stats(); +test('persistence-pull-push', async () => { + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); + await db.exec("DELETE FROM q"); + await db.push(); + await db.close(); + } + const path1 = `test-${(Math.random() * 10000) | 0}.db`; + const path2 = `test-${(Math.random() * 10000) | 0}.db`; + const db1 = await connect({ path: path1, url: process.env.VITE_TURSO_DB_URL }); + await db1.exec(`INSERT INTO q VALUES ('k1', 'v1')`); + await db1.exec(`INSERT INTO q VALUES ('k2', 'v2')`); + const stats1 = await db1.stats(); -// const db2 = await connect({ path: path2, url: process.env.VITE_TURSO_DB_URL }); -// await db2.exec(`INSERT INTO q VALUES ('k3', 'v3')`); -// await db2.exec(`INSERT INTO q VALUES ('k4', 'v4')`); + const db2 = await connect({ path: path2, url: process.env.VITE_TURSO_DB_URL }); + await db2.exec(`INSERT INTO q VALUES ('k3', 'v3')`); + await db2.exec(`INSERT INTO q VALUES ('k4', 'v4')`); -// await Promise.all([db1.push(), db2.push()]); -// await Promise.all([db1.pull(), db2.pull()]); -// const stats2 = await db1.stats(); -// console.info(stats1, stats2); -// expect(stats1.revision).not.toBe(stats2.revision); + await Promise.all([db1.push(), db2.push()]); + await Promise.all([db1.pull(), db2.pull()]); + const stats2 = await db1.stats(); + console.info(stats1, stats2); + expect(stats1.revision).not.toBe(stats2.revision); -// const rows1 = await db1.prepare('SELECT * FROM q').all(); -// const rows2 = await db2.prepare('SELECT * FROM q').all(); -// const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }]; -// expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare)) -// expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare)) -// }) + const rows1 = await db1.prepare('SELECT * FROM q').all(); + const rows2 = await db2.prepare('SELECT * FROM q').all(); + const expected = [{ x: 'k1', y: 'v1' }, { x: 'k2', y: 'v2' }, { x: 'k3', y: 'v3' }, { x: 'k4', y: 'v4' }]; + expect(rows1.sort(localeCompare)).toEqual(expected.sort(localeCompare)) + expect(rows2.sort(localeCompare)).toEqual(expected.sort(localeCompare)) +}) -// test('pull-push-concurrent', async () => { -// { -// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 5000 }); -// await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); -// await db.exec("DELETE FROM q"); -// await db.push(); -// await db.close(); -// } -// let pullResolve = null; -// const pullFinish = new Promise(resolve => pullResolve = resolve); -// let pushResolve = null; -// const pushFinish = new Promise(resolve => pushResolve = resolve); -// let stopPull = false; -// let stopPush = false; -// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); -// let pull = async () => { -// try { -// await db.pull(); -// } catch (e) { -// console.error('pull', e); -// } finally { -// if (!stopPull) { -// setTimeout(pull, 0); -// } else { -// pullResolve() -// } -// } -// } -// let push = async () => { -// try { -// if ((await db.stats()).operations > 0) { -// await db.push(); -// } -// } catch (e) { -// console.error('push', e); -// } finally { -// if (!stopPush) { -// setTimeout(push, 0); -// } else { -// pushResolve(); -// } -// } -// } -// setTimeout(pull, 0); -// setTimeout(push, 0); -// for (let i = 0; i < 1000; i++) { -// await db.exec(`INSERT INTO q VALUES ('k${i}', 'v${i}')`); -// } -// await new Promise(resolve => setTimeout(resolve, 1000)); -// stopPush = true; -// await pushFinish; -// stopPull = true; -// await pullFinish; -// console.info(await db.stats()); -// }) +test('pull-push-concurrent', async () => { + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 5000 }); + await db.exec("CREATE TABLE IF NOT EXISTS q(x TEXT PRIMARY KEY, y)"); + await db.exec("DELETE FROM q"); + await db.push(); + await db.close(); + } + let pullResolve = null; + const pullFinish = new Promise(resolve => pullResolve = resolve); + let pushResolve = null; + const pushFinish = new Promise(resolve => pushResolve = resolve); + let stopPull = false; + let stopPush = false; + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + let pull = async () => { + try { + await db.pull(); + } catch (e) { + console.error('pull', e); + } finally { + if (!stopPull) { + setTimeout(pull, 0); + } else { + pullResolve() + } + } + } + let push = async () => { + try { + if ((await db.stats()).operations > 0) { + await db.push(); + } + } catch (e) { + console.error('push', e); + } finally { + if (!stopPush) { + setTimeout(push, 0); + } else { + pushResolve(); + } + } + } + setTimeout(pull, 0); + setTimeout(push, 0); + for (let i = 0; i < 1000; i++) { + await db.exec(`INSERT INTO q VALUES ('k${i}', 'v${i}')`); + } + await new Promise(resolve => setTimeout(resolve, 1000)); + stopPush = true; + await pushFinish; + stopPull = true; + await pullFinish; + console.info(await db.stats()); +}) -// test('concurrent-updates', { timeout: 60000 }, async () => { -// { -// const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 10 }); -// await db.exec("CREATE TABLE IF NOT EXISTS three(x TEXT PRIMARY KEY, y, z)"); -// await db.exec("DELETE FROM three"); -// await db.push(); -// await db.close(); -// } -// let stop = false; -// const dbs = []; -// for (let i = 0; i < 8; i++) { -// dbs.push(await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL })); -// } -// async function pull(db, i) { -// try { -// await db.pull(); -// } catch (e) { -// console.error('pull', i, e); -// } finally { -// if (!stop) { -// setTimeout(async () => await pull(db, i), 0); -// } -// } -// } -// async function push(db, i) { -// try { -// await db.push(); -// } catch (e) { -// console.error('push', i, e); -// } finally { -// if (!stop) { -// setTimeout(async () => await push(db, i), 0); -// } -// } -// } -// for (let i = 0; i < dbs.length; i++) { -// setTimeout(async () => await pull(dbs[i], i), 0) -// setTimeout(async () => await push(dbs[i], i), 0) -// } -// for (let i = 0; i < 1000; i++) { -// try { -// const tasks = []; -// for (let s = 0; s < dbs.length; s++) { -// tasks.push(dbs[s].exec(`INSERT INTO three VALUES ('${s}', 0, randomblob(128)) ON CONFLICT DO UPDATE SET y = y + 1, z = randomblob(128)`)); -// } -// await Promise.all(tasks); -// } catch (e) { -// // ignore -// } -// await new Promise(resolve => setTimeout(resolve, 1)); -// } -// stop = true; -// await Promise.all(dbs.map(db => db.push())); -// await Promise.all(dbs.map(db => db.pull())); -// let results = []; -// for (let i = 0; i < dbs.length; i++) { -// results.push(await dbs[i].prepare('SELECT x, y FROM three').all()); -// } -// for (let i = 0; i < dbs.length; i++) { -// expect(results[i]).toEqual(results[0]); -// for (let s = 0; s < dbs.length; s++) { -// expect(results[i][s].y).toBeGreaterThan(500); -// } -// } -// }) +test('concurrent-updates', { timeout: 60000 }, async () => { + { + const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, longPollTimeoutMs: 10 }); + await db.exec("CREATE TABLE IF NOT EXISTS three(x TEXT PRIMARY KEY, y, z)"); + await db.exec("DELETE FROM three"); + await db.push(); + await db.close(); + } + let stop = false; + const dbs = []; + for (let i = 0; i < 8; i++) { + dbs.push(await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL })); + } + async function pull(db, i) { + try { + await db.pull(); + } catch (e) { + console.error('pull', i, e); + } finally { + if (!stop) { + setTimeout(async () => await pull(db, i), 0); + } + } + } + async function push(db, i) { + try { + await db.push(); + } catch (e) { + console.error('push', i, e); + } finally { + if (!stop) { + setTimeout(async () => await push(db, i), 0); + } + } + } + for (let i = 0; i < dbs.length; i++) { + setTimeout(async () => await pull(dbs[i], i), 0) + setTimeout(async () => await push(dbs[i], i), 0) + } + for (let i = 0; i < 1000; i++) { + try { + const tasks = []; + for (let s = 0; s < dbs.length; s++) { + tasks.push(dbs[s].exec(`INSERT INTO three VALUES ('${s}', 0, randomblob(128)) ON CONFLICT DO UPDATE SET y = y + 1, z = randomblob(128)`)); + } + await Promise.all(tasks); + } catch (e) { + // ignore + } + await new Promise(resolve => setTimeout(resolve, 1)); + } + stop = true; + await Promise.all(dbs.map(db => db.push())); + await Promise.all(dbs.map(db => db.pull())); + let results = []; + for (let i = 0; i < dbs.length; i++) { + results.push(await dbs[i].prepare('SELECT x, y FROM three').all()); + } + for (let i = 0; i < dbs.length; i++) { + expect(results[i]).toEqual(results[0]); + for (let s = 0; s < dbs.length; s++) { + expect(results[i][s].y).toBeGreaterThan(500); + } + } +}) -// test('transform', async () => { -// { -// const db = await connect({ -// path: ':memory:', -// url: process.env.VITE_TURSO_DB_URL, -// }); -// await db.exec("CREATE TABLE IF NOT EXISTS counter(key TEXT PRIMARY KEY, value INTEGER)"); -// await db.exec("DELETE FROM counter"); -// await db.exec("INSERT INTO counter VALUES ('1', 0)") -// await db.push(); -// await db.close(); -// } -// const transform = (m: DatabaseRowMutation) => ({ -// operation: 'rewrite', -// stmt: { -// sql: `UPDATE counter SET value = value + ? WHERE key = ?`, -// values: [m.after.value - m.before.value, m.after.key] -// } -// } as DatabaseRowTransformResult); -// const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform }); -// const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform }); +test('transform', async () => { + { + const db = await connect({ + path: ':memory:', + url: process.env.VITE_TURSO_DB_URL, + }); + await db.exec("CREATE TABLE IF NOT EXISTS counter(key TEXT PRIMARY KEY, value INTEGER)"); + await db.exec("DELETE FROM counter"); + await db.exec("INSERT INTO counter VALUES ('1', 0)") + await db.push(); + await db.close(); + } + const transform = (m: DatabaseRowMutation) => ({ + operation: 'rewrite', + stmt: { + sql: `UPDATE counter SET value = value + ? WHERE key = ?`, + values: [m.after.value - m.before.value, m.after.key] + } + } as DatabaseRowTransformResult); + const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform }); + const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform }); -// await db1.exec("UPDATE counter SET value = value + 1 WHERE key = '1'"); -// await db2.exec("UPDATE counter SET value = value + 1 WHERE key = '1'"); + await db1.exec("UPDATE counter SET value = value + 1 WHERE key = '1'"); + await db2.exec("UPDATE counter SET value = value + 1 WHERE key = '1'"); -// await Promise.all([db1.push(), db2.push()]); -// await Promise.all([db1.pull(), db2.pull()]); + await Promise.all([db1.push(), db2.push()]); + await Promise.all([db1.pull(), db2.pull()]); -// const rows1 = await db1.prepare('SELECT * FROM counter').all(); -// const rows2 = await db2.prepare('SELECT * FROM counter').all(); -// expect(rows1).toEqual([{ key: '1', value: 2 }]); -// expect(rows2).toEqual([{ key: '1', value: 2 }]); -// }) + const rows1 = await db1.prepare('SELECT * FROM counter').all(); + const rows2 = await db2.prepare('SELECT * FROM counter').all(); + expect(rows1).toEqual([{ key: '1', value: 2 }]); + expect(rows2).toEqual([{ key: '1', value: 2 }]); +}) -// test('transform-many', async () => { -// { -// const db = await connect({ -// path: ':memory:', -// url: process.env.VITE_TURSO_DB_URL, -// }); -// await db.exec("CREATE TABLE IF NOT EXISTS counter(key TEXT PRIMARY KEY, value INTEGER)"); -// await db.exec("DELETE FROM counter"); -// await db.exec("INSERT INTO counter VALUES ('1', 0)") -// await db.push(); -// await db.close(); -// } -// const transform = (m: DatabaseRowMutation) => ({ -// operation: 'rewrite', -// stmt: { -// sql: `UPDATE counter SET value = value + ? WHERE key = ?`, -// values: [m.after.value - m.before.value, m.after.key] -// } -// } as DatabaseRowTransformResult); -// const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform }); -// const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform }); +test('transform-many', async () => { + { + const db = await connect({ + path: ':memory:', + url: process.env.VITE_TURSO_DB_URL, + }); + await db.exec("CREATE TABLE IF NOT EXISTS counter(key TEXT PRIMARY KEY, value INTEGER)"); + await db.exec("DELETE FROM counter"); + await db.exec("INSERT INTO counter VALUES ('1', 0)") + await db.push(); + await db.close(); + } + const transform = (m: DatabaseRowMutation) => ({ + operation: 'rewrite', + stmt: { + sql: `UPDATE counter SET value = value + ? WHERE key = ?`, + values: [m.after.value - m.before.value, m.after.key] + } + } as DatabaseRowTransformResult); + const db1 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform }); + const db2 = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL, transform: transform }); -// for (let i = 0; i < 1002; i++) { -// await db1.exec("UPDATE counter SET value = value + 1 WHERE key = '1'"); -// } -// for (let i = 0; i < 1001; i++) { -// await db2.exec("UPDATE counter SET value = value + 1 WHERE key = '1'"); -// } + for (let i = 0; i < 1002; i++) { + await db1.exec("UPDATE counter SET value = value + 1 WHERE key = '1'"); + } + for (let i = 0; i < 1001; i++) { + await db2.exec("UPDATE counter SET value = value + 1 WHERE key = '1'"); + } -// let start = performance.now(); -// await Promise.all([db1.push(), db2.push()]); -// console.info('push', performance.now() - start); + let start = performance.now(); + await Promise.all([db1.push(), db2.push()]); + console.info('push', performance.now() - start); -// start = performance.now(); -// await Promise.all([db1.pull(), db2.pull()]); -// console.info('pull', performance.now() - start); + start = performance.now(); + await Promise.all([db1.pull(), db2.pull()]); + console.info('pull', performance.now() - start); -// const rows1 = await db1.prepare('SELECT * FROM counter').all(); -// const rows2 = await db2.prepare('SELECT * FROM counter').all(); -// expect(rows1).toEqual([{ key: '1', value: 1001 + 1002 }]); -// expect(rows2).toEqual([{ key: '1', value: 1001 + 1002 }]); -// }) \ No newline at end of file + const rows1 = await db1.prepare('SELECT * FROM counter').all(); + const rows2 = await db2.prepare('SELECT * FROM counter').all(); + expect(rows1).toEqual([{ key: '1', value: 1001 + 1002 }]); + expect(rows2).toEqual([{ key: '1', value: 1001 + 1002 }]); +}) \ No newline at end of file From 9e04687108fdcf373ecc3e6ae710fb1832c79abe Mon Sep 17 00:00:00 2001 From: Nikita Sivukhin Date: Wed, 29 Oct 2025 16:22:56 +0400 Subject: [PATCH 7/8] add one more test --- .../javascript/sync/packages/wasm/promise.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/bindings/javascript/sync/packages/wasm/promise.test.ts b/bindings/javascript/sync/packages/wasm/promise.test.ts index 0e1dd1668..e49856e6b 100644 --- a/bindings/javascript/sync/packages/wasm/promise.test.ts +++ b/bindings/javascript/sync/packages/wasm/promise.test.ts @@ -75,6 +75,21 @@ test('simple-db', async () => { await expect(async () => await db.pull()).rejects.toThrowError(/sync is disabled as database was opened without sync support/); }) +test('reconnect-db', async () => { + { + const db = await connect({ path: 'local.db', url: process.env.VITE_TURSO_DB_URL }); + const stmt = db.prepare("SELECT * FROM turso_cdc"); + expect(await stmt.all()).toEqual([]) + stmt.close(); + } + { + const db = await connect({ path: 'local.db', url: process.env.VITE_TURSO_DB_URL }); + const stmt = db.prepare("SELECT * FROM turso_cdc"); + expect(await stmt.all()).toEqual([]) + stmt.close(); + } +}) + test('implicit connect', async () => { const db = new Database({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); const defer = db.prepare("SELECT * FROM not_found"); From d0138769986413d9308a4f230fc2cc51f3edc956 Mon Sep 17 00:00:00 2001 From: Nikita Sivukhin Date: Wed, 29 Oct 2025 16:46:51 +0400 Subject: [PATCH 8/8] cargo fmt --- sync/engine/src/database_sync_operations.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sync/engine/src/database_sync_operations.rs b/sync/engine/src/database_sync_operations.rs index 327a691c4..3cb069e98 100644 --- a/sync/engine/src/database_sync_operations.rs +++ b/sync/engine/src/database_sync_operations.rs @@ -597,8 +597,7 @@ pub async fn read_last_change_id( tracing::info!("read_last_change_id: client_id={client_id}"); // fetch last_change_id from the target DB in order to guarantee atomic replay of changes and avoid conflicts in case of failure - let mut select_last_change_id_stmt = match conn.prepare(TURSO_SYNC_SELECT_LAST_CHANGE_ID) - { + let mut select_last_change_id_stmt = match conn.prepare(TURSO_SYNC_SELECT_LAST_CHANGE_ID) { Ok(stmt) => stmt, Err(LimboError::ParseError(..)) => return Ok((0, None)), Err(err) => return Err(err.into()),