From ccb14e35cbd2f4924afe0d55be3913c93ddc3627 Mon Sep 17 00:00:00 2001 From: Rita Agafonova <36133358+rita-aga@users.noreply.github.com> Date: Wed, 9 Sep 2026 17:08:30 -0400 Subject: [PATCH 1/4] docs: define official Turso migration contract --- docs/adrs/0176-official-turso-engine.md | 26 +++++++++++++++++++++++++ docs/efforts/ARN-467/decisions.md | 13 +++++++++++++ docs/efforts/ARN-467/intent.md | 5 +++++ docs/efforts/ARN-467/plan.md | 9 +++++++++ docs/efforts/ARN-467/spec.md | 5 +++++ 5 files changed, 58 insertions(+) create mode 100644 docs/adrs/0176-official-turso-engine.md diff --git a/docs/adrs/0176-official-turso-engine.md b/docs/adrs/0176-official-turso-engine.md new file mode 100644 index 00000000..ac5d94b6 --- /dev/null +++ b/docs/adrs/0176-official-turso-engine.md @@ -0,0 +1,26 @@ +# ADR-0176: Use the official Turso engine without a maintained database fork + +- Status: Proposed +- Date: 2026-09-09 +- Decider: Rita Agafonova +- Related: ADR-0175, ARN-467 + +## Context + +ARN-467 copied libSQL into the kernel repository to repair a connection teardown defect. Rita rejected maintaining either copied database source or a fork, and selected the current official Turso engine. This migration is independent of Foundry delivery. + +## Decision + +Use published stable Turso packages, starting qualification with turso 0.7.2 and turso_serverless 0.1.3. Preserve the existing storage contract: durable event append and replay, atomic writes, tenant isolation, schema migrations, local file data, and remote Turso connections. Remove vendor/libsql and its dedicated patch checks when the replacement passes qualification. Do not introduce another database fork, patch upstream engine internals, silently drop remote support, or change product semantics to fit an incompatible engine. + +## Readiness gates + +Qualify existing schema and transaction operations against the published engine before adapting callers. Preserve existing-data compatibility with a real old-format fixture and restart proof. Run store contract tests and relevant server e2e before review and deployment. An upstream incompatibility is a concrete limitation to report, not authorization for another engine repair project. + +## Rollout and rollback + +Keep production unchanged during qualification. A completed migration requires reviewed code, current-head proof, and a separately verified kernel rollout through Temper. Keep an untouched backup of any persistent database before a changed runtime opens it; do not assume reverse file compatibility. + +## Alternatives + +Vendored libSQL and a separate libSQL fork were rejected by Rita. Restoring the published libSQL package restores a known teardown defect. Replacing Turso with SQLite or PostgreSQL was not selected. diff --git a/docs/efforts/ARN-467/decisions.md b/docs/efforts/ARN-467/decisions.md index 5d56aa3d..f29bacb8 100644 --- a/docs/efforts/ARN-467/decisions.md +++ b/docs/efforts/ARN-467/decisions.md @@ -554,3 +554,16 @@ D30 caller review: Generic stream uploads also need to materialize their authori **Where:** crates/temper-spec/src/automaton/contracts.rs; toml_parser/inline.rs and mod.rs; crates/temper-actor-runtime/src/spec_actor.rs and tests/spec_actor_strict/typed.rs. **Review dispositions:** Grok's single-table and non-string typed-shape examples reproduce the fallback. Its trailing-comma example was already refused by a later strict TOML metadata parse. Ordinary from_ioa already reports the first parse error; the constructor panic requires inconsistent inputs or changed parsing conditions and is not necessarily a process abort. The final Grok output reports divergence and is not a passing review. D31's native-actor DST exception, five findings and ARN-179 remain unchanged. + + +## D44 — Qualify the official Turso engine for dependency cleanup + +**Decision:** Replace the maintained libSQL patch with current official Turso packages, subject to preserving the existing storage contract. + +**Came up because:** Rita rejected both vendored libSQL and a separate fork and explicitly selected the newer Turso tooling. + +**Options:** Keep the vendor patch; move the patch to a fork; restore the known failing package; qualify current official Turso packages. + +**Chose current Turso over a maintained libSQL patch because:** It keeps database implementation maintenance upstream. Qualification must preserve existing data and local/remote behavior; an engine incompatibility will be reported without starting another upstream repair project. + +**Where:** ADR-0176; crates/temper-store-turso; codex/arn467-turso-engine. diff --git a/docs/efforts/ARN-467/intent.md b/docs/efforts/ARN-467/intent.md index 753d191e..4288f5a1 100644 --- a/docs/efforts/ARN-467/intent.md +++ b/docs/efforts/ARN-467/intent.md @@ -1,3 +1,8 @@ # Intent DSF factory observations must not rewrite intended configuration, operation callbacks must retain the accepted target, and generic writes must not bypass those contracts. This is the kernel dependency of [ARN-467](https://linear.app/arni-build/issue/ARN-467/deliver-the-dsf-software-factory-live-operational-model-resource). The full application contract is in nerdsane/temperpaw, docs/efforts/ARN-467/spec.md. + + +## Approved cleanup: current Turso engine + +Rita selected current official Turso packages to replace the copied libSQL dependency. No fork or vendored engine. Preserve local and remote storage behavior and data. This remains independent of Foundry delivery. Rita explicitly authorized an isolated local worktree while governed Copy is unavailable. diff --git a/docs/efforts/ARN-467/plan.md b/docs/efforts/ARN-467/plan.md index 8af68907..831059af 100644 --- a/docs/efforts/ARN-467/plan.md +++ b/docs/efforts/ARN-467/plan.md @@ -5,3 +5,12 @@ 3. Reproduce generic create, PATCH, PUT and DELETE bypasses through HTTP and direct state APIs, then reject them before mutation. 4. Align reaction simulator field projection with production and exercise actual multi-entity factory contracts. 5. Run parser, transition, actor and HTTP regressions. review and merge the kernel dependency, pin it in TemperPaw, then prove the deployed factory. + + +## Turso cleanup implementation + +1. Qualify official stable packages against the existing schema, transaction API and local/remote connection requirements. +2. Adapt the store and required callers; remove vendored libSQL and its dedicated patch machinery. Preserve existing behavior and data. +3. Run existing store invariants, old-file/restart proof and relevant server e2e. Review the complete diff, record proof/review, merge and deploy through Temper only after qualification. + +Foundry and Copy delivery are not waiting on this migration. Local worktree execution is authorized by Rita for this migration. diff --git a/docs/efforts/ARN-467/spec.md b/docs/efforts/ARN-467/spec.md index be239070..6522a68c 100644 --- a/docs/efforts/ARN-467/spec.md +++ b/docs/efforts/ARN-467/spec.md @@ -25,3 +25,8 @@ An IOA with strict parameters or parameter constraints declares each action name Explicit parameter types on strict or constrained actions survive table serialization and validate present values before any effect. Supported types are string/status, bool, signed 64-bit int/integer, and natural-number counter/uint64. Null is not a value of these types; omission remains optional unless a constraint requires the parameter. Bare-name parameters retain their constraint-inferred semantics. Unsupported types and duplicate names in these contracts fail specification validation. Typed parameter declarations must decode as an array of valid names and types; malformed tables never become bare names. Unsigned uint64 parameters can compare against counter fields, but uint64 state fields are not numeric comparison targets because their stored representation is a string. Actor construction compiles transitions and initial state from the same parsed automaton. A first action whose input violates its parameter contract leaves no actor, index, bootstrap, event, or snapshot. Static and registry specifications use the same lookup for declared child initialization. Existing and durably persisted entities validate their hydrated values at execution time. HTTP and reaction entry points retain their existing Cedar authorization before core dispatch; the public Rust core is not itself a new authorization boundary. + + +## Turso cleanup contract (September 9) + +Replace vendored libSQL with current official Turso packages. Existing event ordering, idempotency, tenant isolation, atomic commit/rollback, authorization persistence, schema upgrades, local files and remote connections remain required. A successful migration removes the vendored source and its checker, changes the storage adapter, and proves the actual runtime path before deployment. No new engine fork or engine patches are permitted. Compatibility qualification may establish a blocker; do not weaken invariants to claim support. ADR-0176 records this decision. From 4aaf1c8b2fd80799355fd377c8a99f47be28ad07 Mon Sep 17 00:00:00 2001 From: Rita Agafonova <36133358+rita-aga@users.noreply.github.com> Date: Wed, 9 Sep 2026 18:44:49 -0400 Subject: [PATCH 2/4] refactor(store): replace vendored libSQL with official Turso --- .claude/hooks/pre-commit.sh | 4 +- .claude/hooks/test-pre-commit.sh | 2 +- .github/workflows/ci.yml | 3 - Cargo.lock | 1920 ++++++----- Cargo.toml | 1 - crates/temper-store-turso/Cargo.toml | 8 +- crates/temper-store-turso/src/driver.rs | 221 ++ crates/temper-store-turso/src/driver/tests.rs | 88 + crates/temper-store-turso/src/lib.rs | 5 +- crates/temper-store-turso/src/router.rs | 20 +- crates/temper-store-turso/src/store/authz.rs | 2 +- crates/temper-store-turso/src/store/blobs.rs | 6 +- .../src/store/constraints.rs | 2 +- .../src/store/entity_listing.rs | 2 +- .../src/store/event_store.rs | 24 +- .../temper-store-turso/src/store/evolution.rs | 4 +- .../src/store/field_index.rs | 51 +- .../src/store/instrumentation.rs | 14 +- crates/temper-store-turso/src/store/mod.rs | 22 +- crates/temper-store-turso/src/store/ots.rs | 6 +- .../temper-store-turso/src/store/ots_test.rs | 2 +- crates/temper-store-turso/src/store/policy.rs | 2 +- .../src/store/policy_approval.rs | 12 +- .../src/store/published_artifacts.rs | 2 +- .../src/store/query_page.rs | 26 +- .../temper-store-turso/src/store/secrets.rs | 2 +- crates/temper-store-turso/src/store/specs.rs | 21 +- .../src/store/tests/evolution_tenant.rs | 2 +- .../temper-store-turso/src/store/tests/mod.rs | 2 +- .../src/store/trajectory.rs | 2 +- .../src/store/trajectory_queries.rs | 6 +- crates/temper-store-turso/src/store/wasm.rs | 12 +- .../temper-store-turso/tests/blob_ttl_e2e.rs | 2 +- .../tests/connection_lifetime.rs | 10 +- docs/adrs/0175-libsql-connection-lifetime.md | 5 +- docs/adrs/0176-official-turso-engine.md | 4 +- docs/efforts/ARN-467/decisions.md | 25 + scripts/check-vendored-libsql.py | 47 - vendor/libsql/.cargo_vcs_info.json | 6 - vendor/libsql/Cargo.lock | 2889 ----------------- vendor/libsql/Cargo.toml | 394 --- vendor/libsql/Cargo.toml.orig | 161 - vendor/libsql/DEVELOPING.md | 32 - vendor/libsql/LICENSE.md | 20 - vendor/libsql/README.md | 27 - vendor/libsql/TEMPER-PATCH.md | 22 - vendor/libsql/TEMPER-SHA256SUMS | 71 - vendor/libsql/assets/test/snapshot.snap | Bin 8288 -> 0 bytes vendor/libsql/benches/benchmark.rs | 224 -- vendor/libsql/examples/deserialization.rs | 58 - vendor/libsql/examples/encryption_local.rs | 72 - vendor/libsql/examples/encryption_sync.rs | 86 - vendor/libsql/examples/example.rs | 43 - vendor/libsql/examples/example_v2.rs | 43 - vendor/libsql/examples/flutter.rs | 52 - vendor/libsql/examples/local_sync.rs | 63 - vendor/libsql/examples/offline_writes.rs | 82 - vendor/libsql/examples/remote_sync.rs | 86 - vendor/libsql/examples/replica.rs | 93 - vendor/libsql/examples/transaction.rs | 42 - vendor/libsql/src/auth.rs | 263 -- vendor/libsql/src/connection.rs | 311 -- vendor/libsql/src/database.rs | 816 ----- vendor/libsql/src/database/builder.rs | 873 ----- vendor/libsql/src/de.rs | 86 - vendor/libsql/src/errors.rs | 119 - vendor/libsql/src/hrana/connection.rs | 101 - vendor/libsql/src/hrana/cursor.rs | 490 --- vendor/libsql/src/hrana/hyper.rs | 446 --- vendor/libsql/src/hrana/mod.rs | 470 --- vendor/libsql/src/hrana/stream.rs | 556 ---- vendor/libsql/src/hrana/transaction.rs | 81 - vendor/libsql/src/lib.rs | 193 -- vendor/libsql/src/load_extension_guard.rs | 29 - vendor/libsql/src/local/connection.rs | 908 ------ vendor/libsql/src/local/database.rs | 494 --- vendor/libsql/src/local/impls.rs | 234 -- vendor/libsql/src/local/mod.rs | 36 - vendor/libsql/src/local/rows.rs | 396 --- vendor/libsql/src/local/statement.rs | 361 -- vendor/libsql/src/local/transaction.rs | 93 - vendor/libsql/src/macros.rs | 111 - vendor/libsql/src/params.rs | 351 -- vendor/libsql/src/parser.rs | 301 -- vendor/libsql/src/replication/client.rs | 217 -- vendor/libsql/src/replication/connection.rs | 1036 ------ vendor/libsql/src/replication/local_client.rs | 121 - vendor/libsql/src/replication/mod.rs | 396 --- .../libsql/src/replication/remote_client.rs | 403 --- vendor/libsql/src/rows.rs | 302 -- vendor/libsql/src/statement.rs | 109 - vendor/libsql/src/sync.rs | 1057 ------ vendor/libsql/src/sync/connection.rs | 200 -- vendor/libsql/src/sync/statement.rs | 74 - vendor/libsql/src/sync/test.rs | 601 ---- vendor/libsql/src/sync/transaction.rs | 41 - vendor/libsql/src/transaction.rs | 58 - vendor/libsql/src/util/box_clone_service.rs | 86 - vendor/libsql/src/util/http.rs | 24 - vendor/libsql/src/util/mod.rs | 21 - vendor/libsql/src/value.rs | 821 ----- vendor/libsql/src/wasm/cloudflare.rs | 96 - vendor/libsql/src/wasm/mod.rs | 190 -- vendor/libsql/src/wasm/rows.rs | 62 - vendor/libsql/tests/encryption.rs | 52 - vendor/libsql/tests/integration_tests.rs | 927 ------ vendor/libsql/tests/replication.rs | 164 - vendor/libsql/tests/template.db | Bin 8192 -> 0 bytes vendor/libsql/tests/template.db-wal | 0 vendor/libsql/tests/test.db | Bin 12288 -> 0 bytes 110 files changed, 1626 insertions(+), 20706 deletions(-) create mode 100644 crates/temper-store-turso/src/driver.rs create mode 100644 crates/temper-store-turso/src/driver/tests.rs delete mode 100755 scripts/check-vendored-libsql.py delete mode 100644 vendor/libsql/.cargo_vcs_info.json delete mode 100644 vendor/libsql/Cargo.lock delete mode 100644 vendor/libsql/Cargo.toml delete mode 100644 vendor/libsql/Cargo.toml.orig delete mode 100644 vendor/libsql/DEVELOPING.md delete mode 100644 vendor/libsql/LICENSE.md delete mode 100644 vendor/libsql/README.md delete mode 100644 vendor/libsql/TEMPER-PATCH.md delete mode 100644 vendor/libsql/TEMPER-SHA256SUMS delete mode 100644 vendor/libsql/assets/test/snapshot.snap delete mode 100644 vendor/libsql/benches/benchmark.rs delete mode 100644 vendor/libsql/examples/deserialization.rs delete mode 100644 vendor/libsql/examples/encryption_local.rs delete mode 100644 vendor/libsql/examples/encryption_sync.rs delete mode 100644 vendor/libsql/examples/example.rs delete mode 100644 vendor/libsql/examples/example_v2.rs delete mode 100644 vendor/libsql/examples/flutter.rs delete mode 100644 vendor/libsql/examples/local_sync.rs delete mode 100644 vendor/libsql/examples/offline_writes.rs delete mode 100644 vendor/libsql/examples/remote_sync.rs delete mode 100644 vendor/libsql/examples/replica.rs delete mode 100644 vendor/libsql/examples/transaction.rs delete mode 100644 vendor/libsql/src/auth.rs delete mode 100644 vendor/libsql/src/connection.rs delete mode 100644 vendor/libsql/src/database.rs delete mode 100644 vendor/libsql/src/database/builder.rs delete mode 100644 vendor/libsql/src/de.rs delete mode 100644 vendor/libsql/src/errors.rs delete mode 100644 vendor/libsql/src/hrana/connection.rs delete mode 100644 vendor/libsql/src/hrana/cursor.rs delete mode 100644 vendor/libsql/src/hrana/hyper.rs delete mode 100644 vendor/libsql/src/hrana/mod.rs delete mode 100644 vendor/libsql/src/hrana/stream.rs delete mode 100644 vendor/libsql/src/hrana/transaction.rs delete mode 100644 vendor/libsql/src/lib.rs delete mode 100644 vendor/libsql/src/load_extension_guard.rs delete mode 100644 vendor/libsql/src/local/connection.rs delete mode 100644 vendor/libsql/src/local/database.rs delete mode 100644 vendor/libsql/src/local/impls.rs delete mode 100644 vendor/libsql/src/local/mod.rs delete mode 100644 vendor/libsql/src/local/rows.rs delete mode 100644 vendor/libsql/src/local/statement.rs delete mode 100644 vendor/libsql/src/local/transaction.rs delete mode 100644 vendor/libsql/src/macros.rs delete mode 100644 vendor/libsql/src/params.rs delete mode 100644 vendor/libsql/src/parser.rs delete mode 100644 vendor/libsql/src/replication/client.rs delete mode 100644 vendor/libsql/src/replication/connection.rs delete mode 100644 vendor/libsql/src/replication/local_client.rs delete mode 100644 vendor/libsql/src/replication/mod.rs delete mode 100644 vendor/libsql/src/replication/remote_client.rs delete mode 100644 vendor/libsql/src/rows.rs delete mode 100644 vendor/libsql/src/statement.rs delete mode 100644 vendor/libsql/src/sync.rs delete mode 100644 vendor/libsql/src/sync/connection.rs delete mode 100644 vendor/libsql/src/sync/statement.rs delete mode 100644 vendor/libsql/src/sync/test.rs delete mode 100644 vendor/libsql/src/sync/transaction.rs delete mode 100644 vendor/libsql/src/transaction.rs delete mode 100644 vendor/libsql/src/util/box_clone_service.rs delete mode 100644 vendor/libsql/src/util/http.rs delete mode 100644 vendor/libsql/src/util/mod.rs delete mode 100644 vendor/libsql/src/value.rs delete mode 100644 vendor/libsql/src/wasm/cloudflare.rs delete mode 100644 vendor/libsql/src/wasm/mod.rs delete mode 100644 vendor/libsql/src/wasm/rows.rs delete mode 100644 vendor/libsql/tests/encryption.rs delete mode 100644 vendor/libsql/tests/integration_tests.rs delete mode 100644 vendor/libsql/tests/replication.rs delete mode 100644 vendor/libsql/tests/template.db delete mode 100644 vendor/libsql/tests/template.db-wal delete mode 100644 vendor/libsql/tests/test.db diff --git a/.claude/hooks/pre-commit.sh b/.claude/hooks/pre-commit.sh index 14fc0823..99cdd9ec 100755 --- a/.claude/hooks/pre-commit.sh +++ b/.claude/hooks/pre-commit.sh @@ -11,9 +11,7 @@ WORKSPACE_ROOT="$(git rev-parse --show-toplevel)" echo "Pre-commit: checking for placeholders in staged files..." >&2 -# Preserve the reviewed upstream crate verbatim; this lint governs our code. -# The exact directory is intentional: adjacent vendor paths remain checked. -STAGED_RS="$(git diff --cached --name-only --diff-filter=ACM -- '*.rs' | grep -v '^vendor/libsql/' | grep -v '/tests/' | grep -v '_test\.rs' | grep -v 'test_' || true)" +STAGED_RS="$(git diff --cached --name-only --diff-filter=ACM -- '*.rs' | grep -v '/tests/' | grep -v '_test\.rs' | grep -v 'test_' || true)" if [ -n "$STAGED_RS" ]; then INTEGRITY_FAIL=false diff --git a/.claude/hooks/test-pre-commit.sh b/.claude/hooks/test-pre-commit.sh index 62379367..d6f50361 100644 --- a/.claude/hooks/test-pre-commit.sh +++ b/.claude/hooks/test-pre-commit.sh @@ -21,7 +21,7 @@ check_case() { printf 'PASS %s\n' "$name" } -check_case upstream vendor/libsql/src/lib.rs 'fn upstream() { todo!(); }' 0 +check_case removed_vendor_exception vendor/libsql/src/lib.rs 'fn upstream() { todo!(); }' 1 check_case adjacent vendor/libsql-extra/src/lib.rs 'fn unexpected() { todo!(); }' 1 check_case first_party crates/example/src/lib.rs 'fn incomplete() { todo!(); }' 1 check_case complete crates/example/src/lib.rs 'fn complete() -> bool { true }' 0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0776231..f4cfa782 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -176,9 +176,6 @@ jobs: if [ "$FOUND" = true ]; then exit 1; fi echo "Integrity check: OK" - - name: Verify patched libSQL source - run: python3 scripts/check-vendored-libsql.py - - name: Pre-commit exclusion regression run: bash .claude/hooks/test-pre-commit.sh diff --git a/Cargo.lock b/Cargo.lock index 3386b3bb..04aa9144 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,6 +27,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "aegis" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58541132f980da31e9aa99f7bdee69bc84bf1e168b9b91ef2dbe8abb7b4ce5dd" +dependencies = [ + "cc", + "softaes", +] + [[package]] name = "aes" version = "0.8.4" @@ -63,7 +73,7 @@ dependencies = [ "once_cell", "serde", "version_check", - "zerocopy 0.8.42", + "zerocopy", ] [[package]] @@ -90,6 +100,12 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "allocator-api2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880" + [[package]] name = "ambient-authority" version = "0.0.2" @@ -161,6 +177,22 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "antithesis_sdk" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08410fcac93669a476c006cd6c4512ac1e2b30fd117231a5d55d8a2c76599b82" +dependencies = [ + "libc", + "libloading", + "linkme", + "once_cell", + "rand 0.8.5", + "rustc_version_runtime", + "serde", + "serde_json", +] + [[package]] name = "anyhow" version = "1.0.102" @@ -191,6 +223,26 @@ dependencies = [ "rustversion", ] +[[package]] +name = "aristo" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcdeefa800110050103e2459a2f8dbdbd560ad046e30f1f87e24ce19c2cb8bde" +dependencies = [ + "aristo-macros", +] + +[[package]] +name = "aristo-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d64a66d21a80182b35b1741997a6d2456911f54b7eb1918aa4e2382fc205268d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "arrayvec" version = "0.5.2" @@ -222,6 +274,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "assoc" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdc70193dadb9d7287fa4b633f15f90c876915b31f6af17da307fc59c9859a8" + [[package]] name = "async-channel" version = "1.9.0" @@ -385,7 +443,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -402,7 +460,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -440,7 +498,7 @@ dependencies = [ "manyhow", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -456,7 +514,7 @@ dependencies = [ "proc-macro2", "quote", "quote-use", - "syn", + "syn 2.0.117", ] [[package]] @@ -466,31 +524,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] -name = "axum" -version = "0.6.20" +name = "aws-lc-rs" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e" dependencies = [ - "async-trait", - "axum-core 0.3.4", - "bitflags 1.3.2", - "bytes", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "itoa", - "matchit 0.7.3", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "sync_wrapper 0.1.2", - "tower 0.4.13", - "tower-layer", - "tower-service", + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", ] [[package]] @@ -499,17 +552,17 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ - "axum-core 0.5.6", + "axum-core", "bytes", "form_urlencoded", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-util", "itoa", - "matchit 0.8.4", + "matchit", "memchr", "mime", "percent-encoding", @@ -518,31 +571,14 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", - "tower 0.5.3", + "tower", "tower-layer", "tower-service", "tracing", ] -[[package]] -name = "axum-core" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "mime", - "rustversion", - "tower-layer", - "tower-service", -] - [[package]] name = "axum-core" version = "0.5.6" @@ -551,12 +587,12 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "mime", "pin-project-lite", - "sync_wrapper 1.0.2", + "sync_wrapper", "tower-layer", "tower-service", "tracing", @@ -595,6 +631,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "base64ct" version = "1.8.3" @@ -602,34 +644,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] -name = "bincode" -version = "1.3.3" +name = "bigdecimal" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695" dependencies = [ - "serde", + "autocfg", + "libm", + "num-bigint", + "num-integer", + "num-traits", ] [[package]] name = "bindgen" -version = "0.66.1" +version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ "bitflags 2.11.0", "cexpr", "clang-sys", + "itertools 0.12.1", "lazy_static", "lazycell", "log", - "peeking_take_while", "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash 1.1.0", - "shlex", - "syn", + "shlex 1.3.0", + "syn 2.0.117", "which", ] @@ -647,8 +693,8 @@ dependencies = [ "quote", "regex", "rustc-hash 2.1.1", - "shlex", - "syn", + "shlex 1.3.0", + "syn 2.0.117", ] [[package]] @@ -681,6 +727,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "bitvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -699,15 +757,6 @@ dependencies = [ "hybrid-array", ] -[[package]] -name = "block-padding" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" -dependencies = [ - "generic-array", -] - [[package]] name = "blocking" version = "1.6.2" @@ -734,17 +783,17 @@ dependencies = [ "futures-util", "hex", "home", - "http 1.4.0", + "http", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-named-pipe", - "hyper-rustls 0.27.7", + "hyper-rustls", "hyper-util", "hyperlocal", "log", "pin-project-lite", - "rustls 0.23.37", - "rustls-native-certs 0.8.3", + "rustls", + "rustls-native-certs", "rustls-pemfile", "rustls-pki-types", "serde", @@ -780,6 +829,15 @@ dependencies = [ "cfg_aliases", ] +[[package]] +name = "branches" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7436b21fd6195415058eb41c9090e156b556bc7e0377bb57c5c026a421521525" +dependencies = [ + "rustc_version", +] + [[package]] name = "bstr" version = "1.12.1" @@ -797,7 +855,27 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" dependencies = [ - "allocator-api2", + "allocator-api2 0.2.21", +] + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", ] [[package]] @@ -811,9 +889,6 @@ name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -dependencies = [ - "serde", -] [[package]] name = "bytes-utils" @@ -918,25 +993,16 @@ dependencies = [ "rustversion", ] -[[package]] -name = "cbc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" -dependencies = [ - "cipher", -] - [[package]] name = "cc" -version = "1.2.57" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex", + "shlex 2.0.1", ] [[package]] @@ -1023,6 +1089,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "cfg_block" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18758054972164c3264f7c8386f5fc6da6114cb46b619fd365d4e3b2dc3ae487" + [[package]] name = "chacha20" version = "0.10.0" @@ -1139,7 +1211,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1184,6 +1256,16 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "combine" +version = "4.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "compact_str" version = "0.9.0" @@ -1467,6 +1549,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff" +[[package]] +name = "crc32c" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +dependencies = [ + "rustc_version", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -1541,7 +1632,7 @@ dependencies = [ "proc-macro2", "quote", "strict", - "syn", + "syn 2.0.117", ] [[package]] @@ -1632,11 +1723,11 @@ name = "crucible-reference" version = "0.1.0" dependencies = [ "anyhow", - "axum 0.8.8", + "axum", "clap", "glob", - "hyper 1.8.1", - "reqwest", + "hyper", + "reqwest 0.12.28", "serde", "serde_json", "temper-authz", @@ -1647,7 +1738,7 @@ dependencies = [ "temper-verify", "thiserror 2.0.18", "tokio", - "tower 0.5.3", + "tower", "tracing", "tracing-subscriber", "uuid", @@ -1733,7 +1824,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1756,7 +1847,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.117", ] [[package]] @@ -1767,7 +1858,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1863,7 +1954,7 @@ checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1885,7 +1976,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.117", ] [[package]] @@ -1974,7 +2065,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2003,6 +2094,12 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.20" @@ -2027,9 +2124,9 @@ dependencies = [ name = "ecommerce-reference" version = "0.1.0" dependencies = [ - "axum 0.8.8", + "axum", "criterion", - "hyper 1.8.1", + "hyper", "serde_json", "sqlx", "temper-jit", @@ -2039,7 +2136,7 @@ dependencies = [ "temper-store-postgres", "temper-verify", "tokio", - "tower 0.5.3", + "tower", ] [[package]] @@ -2075,7 +2172,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2160,7 +2257,27 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "env_filter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +dependencies = [ + "env_filter", + "log", ] [[package]] @@ -2180,7 +2297,7 @@ checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2249,12 +2366,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - [[package]] name = "fancy-regex" version = "0.17.0" @@ -2266,6 +2377,17 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "fastbloom" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" +dependencies = [ + "getrandom 0.3.4", + "libm", + "siphasher", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -2312,9 +2434,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" [[package]] name = "findshlibs" @@ -2437,7 +2559,7 @@ checksum = "1458c6e22d36d61507034d5afecc64f105c1d39712b7ac6ec3b352c423f715cc" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2451,6 +2573,18 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.32" @@ -2531,7 +2665,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2585,6 +2719,36 @@ dependencies = [ "serde_json", ] +[[package]] +name = "genawaiter" +version = "0.99.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86bd0361bcbde39b13475e6e36cb24c329964aa2611be285289d1e4b751c1a0" +dependencies = [ + "genawaiter-macro", +] + +[[package]] +name = "genawaiter-macro" +version = "0.99.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b32dfe1fdfc0bbde1f22a5da25355514b5e450c33a6af6770884c8750aedfbc" + +[[package]] +name = "generator" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -2604,7 +2768,7 @@ checksum = "f2b6d1e2f75c16bfbcd0f95d84f99858a6e2f885c2287d1f5c3a96e8444a34b4" dependencies = [ "attribute-derive", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2720,25 +2884,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "h2" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.13.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "h2" version = "0.4.13" @@ -2750,7 +2895,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.4.0", + "http", "indexmap 2.13.0", "slab", "tokio", @@ -2766,7 +2911,7 @@ checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", - "zerocopy 0.8.42", + "zerocopy", ] [[package]] @@ -2789,10 +2934,6 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] [[package]] name = "hashbrown" @@ -2800,7 +2941,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "allocator-api2", + "allocator-api2 0.2.21", "equivalent", "foldhash 0.1.5", "serde", @@ -2812,20 +2953,11 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ - "allocator-api2", + "allocator-api2 0.2.21", "equivalent", "foldhash 0.2.0", ] -[[package]] -name = "hashlink" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" -dependencies = [ - "hashbrown 0.14.5", -] - [[package]] name = "hashlink" version = "0.10.0" @@ -2914,17 +3046,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.4.0" @@ -2935,17 +3056,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.1" @@ -2953,7 +3063,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.0", + "http", ] [[package]] @@ -2964,17 +3074,11 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "pin-project-lite", ] -[[package]] -name = "http-range-header" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" - [[package]] name = "httparse" version = "1.10.1" @@ -2996,30 +3100,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "hyper" -version = "0.14.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.27", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2 0.5.10", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.8.1" @@ -3030,9 +3110,9 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.13", - "http 1.4.0", - "http-body 1.0.1", + "h2", + "http", + "http-body", "httparse", "httpdate", "itoa", @@ -3050,7 +3130,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" dependencies = [ "hex", - "hyper 1.8.1", + "hyper", "hyper-util", "pin-project-lite", "tokio", @@ -3058,51 +3138,21 @@ dependencies = [ "winapi", ] -[[package]] -name = "hyper-rustls" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "399c78f9338483cb7e630c8474b07268983c6bd5acee012e4211f9f7bb21b070" -dependencies = [ - "futures-util", - "http 0.2.12", - "hyper 0.14.32", - "log", - "rustls 0.22.4", - "rustls-native-certs 0.7.3", - "rustls-pki-types", - "tokio", - "tokio-rustls 0.25.0", - "webpki-roots 0.26.11", -] - [[package]] name = "hyper-rustls" version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http 1.4.0", - "hyper 1.8.1", + "http", + "hyper", "hyper-util", - "rustls 0.23.37", + "rustls", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls", "tower-service", - "webpki-roots 1.0.6", -] - -[[package]] -name = "hyper-timeout" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" -dependencies = [ - "hyper 0.14.32", - "pin-project-lite", - "tokio", - "tokio-io-timeout", + "webpki-roots", ] [[package]] @@ -3113,7 +3163,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-util", "native-tls", "tokio", @@ -3131,9 +3181,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.0", - "http-body 1.0.1", - "hyper 1.8.1", + "http", + "http-body", + "hyper", "ipnet", "libc", "percent-encoding", @@ -3154,7 +3204,7 @@ checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" dependencies = [ "hex", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-util", "pin-project-lite", "tokio", @@ -3185,58 +3235,130 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collator" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08984ed58ac439ebf3e13d2cf26b0c46a60afcd21721c1d14087c0b240344dda" +dependencies = [ + "icu_collator_data", + "icu_collections", + "icu_locale_core", + "icu_locale_fallback", + "icu_normalizer", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "zerovec", +] + +[[package]] +name = "icu_collator_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d7e54efdddeb1208c08dd5d32b53a879ac00d2d3d051b2255fd26821d16368" + [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", ] +[[package]] +name = "icu_locale" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "785f595c61ef57169a467eeed7b4b6936a66f6cacbb116a96ee86da983251bf4" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_locale_data", + "icu_locale_fallback", + "icu_provider", + "tinystr", + "zerovec", +] + [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", "litemap", + "serde", "tinystr", "writeable", "zerovec", ] +[[package]] +name = "icu_locale_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d37d91460e362a5cf58907cd7ce871411775ee6294e49a5cc8e6cec16dfa75ea" + +[[package]] +name = "icu_locale_fallback" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "251af8e57c9400e3eb58242fe5b8b1152b2a64fdf4cf632f923c38ccee6f2fa9" +dependencies = [ + "icu_locale_core", + "icu_locale_fallback_data", + "icu_provider", + "potential_utf", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locale_fallback_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "decf2a22ec8fa68f1a0c1129a3f8583f8f8bc24e8b9ccbe98ead99f62a4dc3a8" + [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", + "utf16_iter", + "utf8_iter", + "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ + "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", @@ -3247,18 +3369,20 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ "displaydoc", "icu_locale_core", + "serde", + "stable_deref_trait", "writeable", "yoke", "zerofrom", @@ -3347,7 +3471,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ - "block-padding", "generic-array", ] @@ -3357,6 +3480,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" +[[package]] +name = "intrusive-collections" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86" +dependencies = [ + "memoffset", +] + [[package]] name = "io-extras" version = "0.18.4" @@ -3373,6 +3505,17 @@ version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" +[[package]] +name = "io-uring" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3bd0ecfbb87805f538bb7b32e5239ca0763890c623e349860ecba69469f2bb" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "libc", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -3407,7 +3550,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3499,6 +3642,55 @@ dependencies = [ "cc", ] +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.117", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + [[package]] name = "jobserver" version = "0.1.34" @@ -3589,7 +3781,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn", + "syn 2.0.117", ] [[package]] @@ -3653,140 +3845,6 @@ dependencies = [ "redox_syscall 0.7.3", ] -[[package]] -name = "libsql" -version = "0.9.29" -dependencies = [ - "anyhow", - "async-stream", - "async-trait", - "base64 0.21.7", - "bincode", - "bitflags 2.11.0", - "bytes", - "chrono", - "crc32fast", - "fallible-iterator 0.3.0", - "futures", - "http 0.2.12", - "hyper 0.14.32", - "hyper-rustls 0.25.0", - "libsql-hrana", - "libsql-sqlite3-parser", - "libsql-sys", - "libsql_replication", - "parking_lot", - "serde", - "serde_json", - "thiserror 1.0.69", - "tokio", - "tokio-stream", - "tokio-util", - "tonic 0.11.0", - "tonic-web", - "tower 0.4.13", - "tower-http 0.4.4", - "tracing", - "uuid", - "zerocopy 0.7.35", -] - -[[package]] -name = "libsql-ffi" -version = "0.9.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cd1c1662822495393327856774f6803be25d85bfdcd5b9d4af35458f5daaf75" -dependencies = [ - "bindgen 0.66.1", - "cc", - "cmake", - "glob", -] - -[[package]] -name = "libsql-hrana" -version = "0.9.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "646d0aa75e412769018422f0da798f72e93bd51964f0b2ddad4317aa779ae444" -dependencies = [ - "base64 0.21.7", - "bytes", - "prost 0.12.6", - "serde", -] - -[[package]] -name = "libsql-rusqlite" -version = "0.9.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a4ce3a78c6e3c2b23b02ab6272df8340e1c53380497979d456882254f348d5f" -dependencies = [ - "bitflags 2.11.0", - "fallible-iterator 0.2.0", - "fallible-streaming-iterator", - "hashlink 0.8.4", - "libsql-ffi", - "smallvec", -] - -[[package]] -name = "libsql-sqlite3-parser" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15a90128c708356af8f7d767c9ac2946692c9112b4f74f07b99a01a60680e413" -dependencies = [ - "bitflags 2.11.0", - "cc", - "fallible-iterator 0.3.0", - "indexmap 2.13.0", - "log", - "memchr", - "phf 0.11.3", - "phf_codegen", - "phf_shared 0.11.3", - "uncased", -] - -[[package]] -name = "libsql-sys" -version = "0.9.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a3c326fcfc36fe7578238d5ee6b58c529f8c76372acd61ec50267529cdaff95" -dependencies = [ - "bytes", - "libsql-ffi", - "libsql-rusqlite", - "once_cell", - "tracing", - "zerocopy 0.7.35", -] - -[[package]] -name = "libsql_replication" -version = "0.9.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9a2e469ac8400659bd31f81a745908bcc5cb6b40be2f2ff8de90b15bec5501" -dependencies = [ - "aes", - "async-stream", - "async-trait", - "bytes", - "cbc", - "libsql-rusqlite", - "libsql-sys", - "parking_lot", - "prost 0.12.6", - "serde", - "thiserror 1.0.69", - "tokio", - "tokio-stream", - "tokio-util", - "tonic 0.11.0", - "tracing", - "uuid", - "zerocopy 0.7.35", -] - [[package]] name = "libsqlite3-sys" version = "0.30.1" @@ -3815,6 +3873,26 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "linkme" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3045e122bd98aef8ec3ad58ce84f0791f64e70163d1a02710af4aa11a4d54cc5" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77060ebe535362c3da75682cd17b0431017b6e7c5661e714fc69a7ad017d1301" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -3877,7 +3955,7 @@ dependencies = [ "quote", "regex-automata", "regex-syntax", - "syn", + "syn 2.0.117", ] [[package]] @@ -3889,6 +3967,19 @@ dependencies = [ "logos-codegen", ] +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + [[package]] name = "lru" version = "0.12.5" @@ -3922,7 +4013,7 @@ dependencies = [ "manyhow-macros", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3945,12 +4036,6 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - [[package]] name = "matchit" version = "0.8.4" @@ -4007,6 +4092,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "miette" version = "7.6.0" @@ -4027,7 +4121,7 @@ checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4102,7 +4196,7 @@ dependencies = [ "ruff_text_size", "serde", "smallvec", - "strum", + "strum 0.27.2", ] [[package]] @@ -4120,10 +4214,10 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe 0.2.1", + "openssl-probe", "openssl-sys", "schannel", - "security-framework 3.7.0", + "security-framework", "security-framework-sys", "tempfile", ] @@ -4320,9 +4414,9 @@ dependencies = [ name = "oncall-reference" version = "0.1.0" dependencies = [ - "axum 0.8.8", + "axum", "criterion", - "hyper 1.8.1", + "hyper", "serde_json", "sqlx", "temper-jit", @@ -4332,7 +4426,7 @@ dependencies = [ "temper-store-postgres", "temper-verify", "tokio", - "tower 0.5.3", + "tower", ] [[package]] @@ -4393,15 +4487,9 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - [[package]] name = "openssl-probe" version = "0.2.1" @@ -4454,9 +4542,9 @@ checksum = "a8863faf2910030d139fb48715ad5ff2f35029fc5f244f6d5f689ddcf4d26253" dependencies = [ "async-trait", "bytes", - "http 1.4.0", + "http", "opentelemetry", - "reqwest", + "reqwest 0.12.28", "tracing", ] @@ -4468,13 +4556,13 @@ checksum = "5bef114c6d41bea83d6dc60eb41720eedd0261a67af57b66dd2b84ac46c01d91" dependencies = [ "async-trait", "futures-core", - "http 1.4.0", + "http", "opentelemetry", "opentelemetry-http", "opentelemetry-proto", "opentelemetry_sdk", "prost 0.13.5", - "reqwest", + "reqwest 0.12.28", "thiserror 2.0.18", "tracing", ] @@ -4488,7 +4576,7 @@ dependencies = [ "opentelemetry", "opentelemetry_sdk", "prost 0.13.5", - "tonic 0.12.3", + "tonic", ] [[package]] @@ -4528,6 +4616,12 @@ dependencies = [ "indexmap 2.13.0", ] +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + [[package]] name = "p256" version = "0.13.2" @@ -4540,6 +4634,15 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "pack1" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b7bb0ecf2e447b1f20ee94ee79ef6eed1e9d4b3c36ce1903b9dea3bf205523" +dependencies = [ + "bytemuck", +] + [[package]] name = "parking" version = "2.2.1" @@ -4591,20 +4694,20 @@ dependencies = [ "regex", "regex-syntax", "structmeta", - "syn", + "syn 2.0.117", ] [[package]] -name = "pathdiff" +name = "pastey" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" [[package]] -name = "peeking_take_while" -version = "0.1.2" +name = "pathdiff" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "pem-rfc7468" @@ -4687,7 +4790,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", - "uncased", ] [[package]] @@ -4722,7 +4824,7 @@ checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4893,6 +4995,8 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ + "serde_core", + "writeable", "zerovec", ] @@ -4933,7 +5037,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.8.42", + "zerocopy", ] [[package]] @@ -4960,7 +5064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", ] [[package]] @@ -5058,7 +5162,7 @@ dependencies = [ "prost 0.12.6", "prost-types 0.12.6", "regex", - "syn", + "syn 2.0.117", "tempfile", ] @@ -5072,7 +5176,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -5085,7 +5189,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -5098,7 +5202,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -5149,7 +5253,7 @@ checksum = "bb9a7d9ed2618f94b6d054aba3eb2768c9b489fe16b4ef8847fbc6ed41b707bb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -5189,7 +5293,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash 2.1.1", - "rustls 0.23.37", + "rustls", "socket2 0.6.3", "thiserror 2.0.18", "tokio", @@ -5203,14 +5307,15 @@ version = "0.11.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" dependencies = [ + "aws-lc-rs", "bytes", "getrandom 0.4.2", "lru-slab", "rand 0.10.1", - "rand_pcg", + "rand_pcg 0.10.2", "ring", "rustc-hash 2.1.1", - "rustls 0.23.37", + "rustls", "rustls-pki-types", "slab", "thiserror 2.0.18", @@ -5261,7 +5366,7 @@ dependencies = [ "proc-macro-utils", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -5276,6 +5381,12 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -5352,6 +5463,15 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "rand_pcg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rand_pcg" version = "0.10.2" @@ -5370,6 +5490,15 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rapidhash" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da7e78a036ce858e8d55b7e7dc8ba3a88b78350fd2155d3591bbd966b58589e" +dependencies = [ + "rustversion", +] + [[package]] name = "rayon" version = "1.11.0" @@ -5481,7 +5610,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -5490,7 +5619,7 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5216b1837de2149f8bc8e6d5f88a9326b63b8c836ed58ce4a0a29ec736a59734" dependencies = [ - "allocator-api2", + "allocator-api2 0.2.21", "bumpalo", "hashbrown 0.15.5", "log", @@ -5539,12 +5668,12 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.4.13", - "http 1.4.0", - "http-body 1.0.1", + "h2", + "http", + "http-body", "http-body-util", - "hyper 1.8.1", - "hyper-rustls 0.27.7", + "hyper", + "hyper-rustls", "hyper-tls", "hyper-util", "js-sys", @@ -5555,25 +5684,68 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.37", + "rustls", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tokio-native-tls", - "tokio-rustls 0.26.4", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams 0.4.2", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "reqwest" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" +dependencies = [ + "base64 0.23.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-rustls", "tokio-util", - "tower 0.5.3", - "tower-http 0.6.8", + "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", + "wasm-streams 0.5.0", "web-sys", - "webpki-roots 1.0.6", ] [[package]] @@ -5600,6 +5772,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "roaring" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18bd8a37d17a58532776dcdf6041ce64929adca78e8489d5cacbafe99229d3e1" +dependencies = [ + "bytemuck", + "byteorder", +] + [[package]] name = "rsa" version = "0.9.10" @@ -5719,6 +5901,16 @@ dependencies = [ "semver", ] +[[package]] +name = "rustc_version_runtime" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dd18cd2bae1820af0b6ad5e54f4a51d0f3fcc53b05f845675074efcc7af071d" +dependencies = [ + "rustc_version", + "semver", +] + [[package]] name = "rustix" version = "0.38.44" @@ -5755,57 +5947,31 @@ dependencies = [ "rustix 1.1.4", ] -[[package]] -name = "rustls" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" -dependencies = [ - "log", - "ring", - "rustls-pki-types", - "rustls-webpki 0.102.8", - "subtle", - "zeroize", -] - [[package]] name = "rustls" version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ + "aws-lc-rs", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.13", + "rustls-webpki", "subtle", "zeroize", ] -[[package]] -name = "rustls-native-certs" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" -dependencies = [ - "openssl-probe 0.1.6", - "rustls-pemfile", - "rustls-pki-types", - "schannel", - "security-framework 2.11.1", -] - [[package]] name = "rustls-native-certs" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe 0.2.1", + "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.7.0", + "security-framework", ] [[package]] @@ -5828,22 +5994,39 @@ dependencies = [ ] [[package]] -name = "rustls-webpki" -version = "0.102.8" +name = "rustls-platform-verifier" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", ] +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -5915,6 +6098,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -5935,19 +6124,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.11.0", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - [[package]] name = "security-framework" version = "3.7.0" @@ -6008,7 +6184,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -6044,7 +6220,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -6096,7 +6272,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -6121,6 +6297,12 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "sha2" version = "0.10.9" @@ -6174,6 +6356,32 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "shuttle" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab17edba38d63047f46780cf7360acf7467fec2c048928689a5c1dd1c2b4e31" +dependencies = [ + "assoc", + "bitvec", + "cfg-if", + "generator", + "hex", + "owo-colors", + "rand 0.8.5", + "rand_core 0.6.4", + "rand_pcg 0.3.1", + "scoped-tls", + "smallvec", + "tracing", +] + [[package]] name = "signal-hook" version = "0.3.18" @@ -6215,6 +6423,31 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simsimd" +version = "6.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fb3bc3cdce07a7d7d4caa4c54f8aa967f6be41690482b54b24100a2253fa70" +dependencies = [ + "cc", +] + [[package]] name = "siphasher" version = "1.0.2" @@ -6266,6 +6499,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "softaes" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e14297decde697ddf377c25752aead0927d5cfc89c2684d2af96901a4ceeea" + [[package]] name = "spin" version = "0.9.8" @@ -6325,7 +6564,7 @@ dependencies = [ "futures-io", "futures-util", "hashbrown 0.15.5", - "hashlink 0.10.0", + "hashlink", "indexmap 2.13.0", "log", "memchr", @@ -6353,7 +6592,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn", + "syn 2.0.117", ] [[package]] @@ -6376,7 +6615,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn", + "syn 2.0.117", "tokio", "url", ] @@ -6579,7 +6818,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta-derive", - "syn", + "syn 2.0.117", ] [[package]] @@ -6590,7 +6829,16 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros 0.26.4", ] [[package]] @@ -6599,7 +6847,20 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros", + "strum_macros 0.27.2", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.117", ] [[package]] @@ -6611,7 +6872,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -6643,6 +6904,12 @@ dependencies = [ "symbolic-common", ] +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "2.0.117" @@ -6655,10 +6922,15 @@ dependencies = [ ] [[package]] -name = "sync_wrapper" -version = "0.1.2" +name = "syn" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] [[package]] name = "sync_wrapper" @@ -6677,7 +6949,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -6717,6 +6989,12 @@ dependencies = [ "winx", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.13.5" @@ -6755,7 +7033,7 @@ dependencies = [ "chrono", "deadpool-postgres", "futures", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "temper-actor-runtime", @@ -6786,7 +7064,7 @@ name = "temper-cli" version = "0.1.0" dependencies = [ "anyhow", - "axum 0.8.8", + "axum", "base64 0.22.1", "chrono", "clap", @@ -6798,7 +7076,7 @@ dependencies = [ "indicatif", "open", "rand 0.8.5", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "sha2 0.10.9", @@ -6874,7 +7152,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -6882,10 +7160,10 @@ name = "temper-mcp" version = "0.1.0" dependencies = [ "anyhow", - "axum 0.8.8", + "axum", "hostname", "monty", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "sha2 0.10.9", @@ -6909,7 +7187,7 @@ dependencies = [ "opentelemetry-appender-tracing", "opentelemetry-otlp", "opentelemetry_sdk", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "sha2 0.10.9", @@ -6927,7 +7205,7 @@ dependencies = [ name = "temper-odata" version = "0.1.0" dependencies = [ - "axum 0.8.8", + "axum", "chrono", "serde", "serde_json", @@ -6968,14 +7246,14 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "axum 0.8.8", + "axum", "base64 0.22.1", "chrono", "futures", - "hyper 1.8.1", + "hyper", "opentelemetry", "p256", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "sha2 0.10.9", @@ -6995,7 +7273,7 @@ dependencies = [ "tokio", "tokio-test", "toml", - "tower 0.5.3", + "tower", "tracing", "uuid", "wiremock", @@ -7023,7 +7301,7 @@ version = "0.1.0" dependencies = [ "anyhow", "monty", - "reqwest", + "reqwest 0.12.28", "serde_json", "sha2 0.10.9", "tokio", @@ -7038,7 +7316,7 @@ dependencies = [ "anyhow", "bytes", "futures-util", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "tokio", @@ -7053,7 +7331,7 @@ dependencies = [ "aes-gcm", "async-stream", "async-trait", - "axum 0.8.8", + "axum", "base64 0.22.1", "bytes", "cedar-policy", @@ -7063,14 +7341,14 @@ dependencies = [ "ed25519-dalek", "futures-util", "hmac 0.12.1", - "hyper 1.8.1", + "hyper", "libc", "lru", "opentelemetry", "opentelemetry_sdk", "p256", "pprof", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "sha1", @@ -7105,8 +7383,8 @@ dependencies = [ "tokio-test", "tokio-tungstenite", "toml", - "tower 0.5.3", - "tower-http 0.6.8", + "tower", + "tower-http", "tracing", "tracing-opentelemetry", "tracing-subscriber", @@ -7176,16 +7454,18 @@ name = "temper-store-turso" version = "0.1.0" dependencies = [ "chrono", - "libsql", "opentelemetry", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "sha2 0.10.9", "temper-runtime", "tempfile", + "thiserror 2.0.18", "tokio", "tracing", + "turso", + "turso_serverless", "uuid", "wiremock", ] @@ -7194,9 +7474,9 @@ dependencies = [ name = "temper-transport" version = "0.1.0" dependencies = [ - "axum 0.8.8", + "axum", "futures-util", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "tokio", @@ -7224,14 +7504,14 @@ version = "0.1.0" dependencies = [ "async-stream", "async-trait", - "axum 0.8.8", + "axum", "base64 0.22.1", "bytes", "chrono", "futures-util", "opentelemetry", "opentelemetry_sdk", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "sha2 0.10.9", @@ -7367,7 +7647,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -7378,7 +7658,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -7455,11 +7735,12 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", + "serde_core", "zerovec", ] @@ -7505,16 +7786,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "tokio-io-timeout" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd86198d9ee903fedd2f9a2e72014287c0d9167e4ae43b5853007205dda1b76" -dependencies = [ - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-macros" version = "2.6.1" @@ -7523,7 +7794,7 @@ checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -7562,24 +7833,13 @@ dependencies = [ "whoami 2.1.1", ] -[[package]] -name = "tokio-rustls" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" -dependencies = [ - "rustls 0.22.4", - "rustls-pki-types", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.37", + "rustls", "tokio", ] @@ -7671,51 +7931,24 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap 2.13.0", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", - "winnow", -] - -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - -[[package]] -name = "tonic" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" -dependencies = [ - "async-stream", - "async-trait", - "axum 0.6.20", - "base64 0.21.7", - "bytes", - "h2 0.3.27", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "hyper-timeout", - "percent-encoding", - "pin-project", - "prost 0.12.6", - "tokio", - "tokio-stream", - "tower 0.4.13", - "tower-layer", - "tower-service", - "tracing", +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap 2.13.0", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tonic" version = "0.12.3" @@ -7725,8 +7958,8 @@ dependencies = [ "async-trait", "base64 0.22.1", "bytes", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "percent-encoding", "pin-project", @@ -7737,46 +7970,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "tonic-web" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc3b0e1cedbf19fdfb78ef3d672cb9928e0a91a9cb4629cc0c916e8cff8aaaa1" -dependencies = [ - "base64 0.21.7", - "bytes", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "pin-project", - "tokio-stream", - "tonic 0.11.0", - "tower-http 0.4.4", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "indexmap 1.9.3", - "pin-project", - "pin-project-lite", - "rand 0.8.5", - "slab", - "tokio", - "tokio-util", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tower" version = "0.5.3" @@ -7786,33 +7979,13 @@ dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tower-layer", "tower-service", "tracing", ] -[[package]] -name = "tower-http" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" -dependencies = [ - "bitflags 2.11.0", - "bytes", - "futures-core", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "http-range-header", - "pin-project-lite", - "tower 0.4.13", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tower-http" version = "0.6.8" @@ -7822,11 +7995,11 @@ dependencies = [ "bitflags 2.11.0", "bytes", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "iri-string", "pin-project-lite", - "tower 0.5.3", + "tower", "tower-layer", "tower-service", "tracing", @@ -7856,6 +8029,19 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" +dependencies = [ + "crossbeam-channel", + "symlink", + "thiserror 2.0.18", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.31" @@ -7864,7 +8050,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -7951,7 +8137,7 @@ checksum = "eadc29d668c91fcc564941132e17b28a7ceb2f3ebf0b9dae3e03fd7a6748eb0d" dependencies = [ "bytes", "data-encoding", - "http 1.4.0", + "http", "httparse", "log", "native-tls", @@ -7961,6 +8147,222 @@ dependencies = [ "utf-8", ] +[[package]] +name = "turso" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9491d7a80312c5abe66a4409e4dce02065503a235453c94b9e4133877e39ffc" +dependencies = [ + "thiserror 2.0.18", + "tracing", + "tracing-subscriber", + "turso_core", + "turso_sdk_kit", + "turso_sync_sdk_kit", +] + +[[package]] +name = "turso_core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a833cc3bf8d4e6c101c504fa470f8ab4270c2202ff2591b61b2e373b4f20d9b" +dependencies = [ + "aegis", + "aes", + "aes-gcm", + "allocator-api2 0.4.0", + "antithesis_sdk", + "arc-swap", + "aristo", + "bigdecimal", + "bitflags 2.11.0", + "branches", + "bumpalo", + "bytemuck", + "cfg_aliases", + "cfg_block", + "chrono", + "crc32c", + "crossbeam-epoch", + "crossbeam-utils", + "either", + "fallible-iterator 0.3.0", + "fastbloom", + "hex", + "icu_collator", + "icu_locale", + "intrusive-collections", + "io-uring", + "libc", + "libloading", + "libm", + "loom", + "miette", + "num-bigint", + "num-traits", + "pack1", + "parking_lot", + "pastey", + "polling", + "rand 0.9.2", + "rapidhash", + "regex", + "regex-syntax", + "roaring", + "rustc-hash 2.1.1", + "rustix 1.1.4", + "ryu", + "serde_json", + "shuttle", + "simsimd", + "smallvec", + "strum 0.26.3", + "strum_macros 0.26.4", + "tempfile", + "thiserror 2.0.18", + "tracing", + "tracing-subscriber", + "turso_ext", + "turso_macros", + "turso_parser", + "twox-hash", + "uncased", + "uuid", + "windows-sys 0.61.2", +] + +[[package]] +name = "turso_ext" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7452fe676450b6840e9eb80e512d14293265221154eba66aba3845dfc0d659f" +dependencies = [ + "chrono", + "getrandom 0.4.2", + "turso_macros", +] + +[[package]] +name = "turso_macros" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2825eaa6b7b893693636947f988e252c1196393fa54d4b85637c70d616d92fa" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "turso_parser" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0e58d029f02e442df4be0b5036ce520b49c553505d9d52ef5a98fc4056e95b7" +dependencies = [ + "bitflags 2.11.0", + "memchr", + "miette", + "strum 0.26.3", + "strum_macros 0.26.4", + "thiserror 2.0.18", + "turso_macros", +] + +[[package]] +name = "turso_sdk_kit" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18c1dc1c0304348c39b97bc6b27cdcb1d7292454ebd0de0f30b5ee3a4c61f9bb" +dependencies = [ + "bindgen 0.69.5", + "env_logger", + "parking_lot", + "tracing", + "tracing-appender", + "tracing-subscriber", + "turso_core", + "turso_ext", + "turso_sdk_kit_macros", +] + +[[package]] +name = "turso_sdk_kit_macros" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4708b5fe678b8730c85964e3d378f75e18c23c4409971360b2209f4e53e4956" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "turso_serverless" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4548bc9f685877497b6a74c4da874a72e03cc8306e77ac812fd6bd53d0ff803e" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures", + "reqwest 0.13.5", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", +] + +[[package]] +name = "turso_sync_engine" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8f03e430240d590d209ac874db52773b7382c627cd604a685affde77622b09" +dependencies = [ + "base64 0.22.1", + "bytes", + "crc32c", + "genawaiter", + "http", + "libc", + "prost 0.14.3", + "roaring", + "serde", + "serde_json", + "thiserror 2.0.18", + "tracing", + "turso_core", + "turso_parser", + "uuid", +] + +[[package]] +name = "turso_sync_sdk_kit" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d48cb47d056c3ec567745761fa6f832358ca30ef9eb0435fdcfb77c558e70089" +dependencies = [ + "bindgen 0.69.5", + "env_logger", + "genawaiter", + "parking_lot", + "tracing", + "tracing-appender", + "tracing-subscriber", + "turso_core", + "turso_sdk_kit", + "turso_sdk_kit_macros", + "turso_sync_engine", +] + +[[package]] +name = "twox-hash" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5283634e518fe9e82c7b20520bb4bc209009fd16c82077c802f8111ecbb0117a" +dependencies = [ + "rand 0.10.1", +] + [[package]] name = "typed-arena" version = "2.0.2" @@ -8130,6 +8532,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -8151,6 +8559,7 @@ dependencies = [ "getrandom 0.4.2", "js-sys", "serde_core", + "sha1_smol", "wasm-bindgen", ] @@ -8300,7 +8709,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -8368,6 +8777,19 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmparser" version = "0.236.1" @@ -8535,7 +8957,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasmtime-internal-component-util", "wasmtime-internal-wit-bindgen", "wit-parser 0.236.1", @@ -8650,7 +9072,7 @@ checksum = "ae71687aa834f9cc9eb5b0f97c85184d7dc70b84d32fd8927af0887998a1ba35" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -8779,12 +9201,12 @@ dependencies = [ ] [[package]] -name = "webpki-roots" -version = "0.26.11" +name = "webpki-root-certs" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" dependencies = [ - "webpki-roots 1.0.6", + "rustls-pki-types", ] [[package]] @@ -8856,7 +9278,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "witx", ] @@ -8868,7 +9290,7 @@ checksum = "5e2f5a33059321aee91888c331b45de1d6e9f9d0f89ab88398ef1fda2d3ee1fb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wiggle-generate", ] @@ -8944,7 +9366,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -8955,7 +9377,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -9253,9 +9675,9 @@ dependencies = [ "base64 0.22.1", "deadpool", "futures", - "http 1.4.0", + "http", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-util", "log", "once_cell", @@ -9296,7 +9718,7 @@ dependencies = [ "heck", "indexmap 2.13.0", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -9312,7 +9734,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -9384,11 +9806,26 @@ dependencies = [ "wast 35.0.2", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] [[package]] name = "xattr" @@ -9402,9 +9839,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -9413,13 +9850,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -9442,17 +9879,7 @@ checksum = "c82b97329d02d87da6802ed9fda083f1b255d822ab13d5b1fb961196b58a69a1" dependencies = [ "bindgen 0.72.1", "pkg-config", - "reqwest", -] - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive 0.7.35", + "reqwest 0.12.28", ] [[package]] @@ -9461,18 +9888,7 @@ version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" dependencies = [ - "zerocopy-derive 0.8.42", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "zerocopy-derive", ] [[package]] @@ -9483,7 +9899,7 @@ checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -9503,7 +9919,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -9515,21 +9931,23 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ "displaydoc", "yoke", "zerofrom", + "zerovec", ] [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ + "serde", "yoke", "zerofrom", "zerovec-derive", @@ -9537,13 +9955,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.5", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 894ad003..fe0f4ab6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,6 @@ [workspace] resolver = "2" exclude = [ - "vendor/libsql", "wasm-modules/http-fetch", "wasm-modules/gepa-replay", "wasm-modules/gepa-score", diff --git a/crates/temper-store-turso/Cargo.toml b/crates/temper-store-turso/Cargo.toml index 2d1ea2bb..3d18c760 100644 --- a/crates/temper-store-turso/Cargo.toml +++ b/crates/temper-store-turso/Cargo.toml @@ -1,18 +1,18 @@ [package] -# Registry publication would replace the patched path with unpatched libsql. -publish = false name = "temper-store-turso" version.workspace = true edition.workspace = true license.workspace = true rust-version.workspace = true -description = "Turso/libSQL storage backend for Temper entity services" +description = "Turso storage backend for Temper entity services" [dependencies] temper-runtime = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -libsql = { path = "../../vendor/libsql", version = "0.9.29" } +turso = { version = "=0.7.2", default-features = false } +turso_serverless = "=0.1.3" +thiserror = { workspace = true } sha2 = { workspace = true } tracing = { workspace = true } tokio = { workspace = true } diff --git a/crates/temper-store-turso/src/driver.rs b/crates/temper-store-turso/src/driver.rs new file mode 100644 index 00000000..dedf9edf --- /dev/null +++ b/crates/temper-store-turso/src/driver.rs @@ -0,0 +1,221 @@ +//! SQL operations shared by the official embedded and serverless Turso drivers. + +use thiserror::Error; + +use turso_serverless::params::{IntoParams, Params}; +pub(crate) use turso_serverless::{Row, Value, params, params_from_iter}; + +/// Driver failures retain the original engine or transport error. +#[derive(Debug, Error)] +pub(crate) enum DriverError { + #[error(transparent)] + Local(#[from] turso::Error), + #[error(transparent)] + Remote(#[from] turso_serverless::Error), +} + +#[derive(Debug)] +pub(crate) enum Database { + Local(turso::Database), + Remote(turso_serverless::Database), +} + +impl Database { + pub(crate) async fn local(path: &str) -> Result { + Ok(Self::Local(turso::Builder::new_local(path).build().await?)) + } + + pub(crate) async fn remote(url: &str, token: &str) -> Result { + Ok(Self::Remote( + turso_serverless::Builder::new_remote(url) + .with_auth_token(token) + .build() + .await?, + )) + } + + pub(crate) fn connect(&self) -> Result { + match self { + Self::Local(db) => Ok(Connection::Local(db.connect()?)), + Self::Remote(db) => Ok(Connection::Remote(db.connect()?)), + } + } +} + +pub(crate) enum Connection { + Local(turso::Connection), + Remote(turso_serverless::Connection), +} + +impl Connection { + pub(crate) async fn query( + &self, + sql: &str, + params: impl IntoParams, + ) -> Result { + let params = params.into_params()?; + match self { + Self::Local(conn) => Rows::local(conn.query(sql, local_params(params)).await?).await, + Self::Remote(conn) => Ok(Rows::Remote(conn.query(sql, params).await?)), + } + } + + pub(crate) async fn execute( + &self, + sql: &str, + params: impl IntoParams, + ) -> Result { + let params = params.into_params()?; + match self { + Self::Local(conn) => Ok(conn.execute(sql, local_params(params)).await?), + Self::Remote(conn) => Ok(conn.execute(sql, params).await?), + } + } + + pub(crate) async fn begin_immediate(&self) -> Result, DriverError> { + // Each store operation owns its connection. The SDK still refuses a nested transaction. + match self { + Self::Local(conn) => Ok(Transaction::Local( + turso::transaction::Transaction::new_unchecked( + conn, + turso::transaction::TransactionBehavior::Immediate, + ) + .await?, + )), + Self::Remote(conn) => Ok(Transaction::Remote( + turso_serverless::Transaction::new_unchecked( + conn, + turso_serverless::TransactionBehavior::Immediate, + ) + .await?, + )), + } + } +} + +pub(crate) enum Transaction<'conn> { + Local(turso::transaction::Transaction<'conn>), + Remote(turso_serverless::Transaction<'conn>), +} + +impl Transaction<'_> { + pub(crate) async fn query( + &self, + sql: &str, + params: impl IntoParams, + ) -> Result { + let params = params.into_params()?; + match self { + Self::Local(tx) => Rows::local(tx.query(sql, local_params(params)).await?).await, + Self::Remote(tx) => Ok(Rows::Remote(tx.query(sql, params).await?)), + } + } + + pub(crate) async fn execute( + &self, + sql: &str, + params: impl IntoParams, + ) -> Result { + let params = params.into_params()?; + match self { + Self::Local(tx) => Ok(tx.execute(sql, local_params(params)).await?), + Self::Remote(tx) => Ok(tx.execute(sql, params).await?), + } + } + + pub(crate) async fn rollback(self) -> Result<(), DriverError> { + match self { + Self::Local(tx) => Ok(tx.rollback().await?), + Self::Remote(tx) => Ok(tx.rollback().await?), + } + } + + pub(crate) async fn commit(self) -> Result<(), DriverError> { + match self { + Self::Local(tx) => Ok(tx.commit().await?), + Self::Remote(tx) => Ok(tx.commit().await?), + } + } +} + +pub(crate) enum Rows { + Local { + rows: turso::Rows, + first: Option, + }, + Remote(turso_serverless::Rows), + Exhausted, +} + +impl Rows { + async fn local(mut rows: turso::Rows) -> Result { + // Start the statement before returning, including PRAGMAs whose rows are discarded. + match rows.next().await? { + Some(row) => Ok(Self::Local { + rows, + first: Some(remote_row(row)?), + }), + None => Ok(Self::Exhausted), + } + } + + pub(crate) async fn next(&mut self) -> Result, DriverError> { + let row = match self { + Self::Remote(rows) => rows.next().await?, + Self::Local { rows, first } => match first.take() { + Some(row) => Some(row), + None => rows.next().await?.map(remote_row).transpose()?, + }, + Self::Exhausted => None, + }; + if row.is_none() { + *self = Self::Exhausted; + } + Ok(row) + } +} + +fn remote_row(row: turso::Row) -> Result { + let values = (0..row.column_count()) + .map(|i| row.get_value(i).map(remote_value)) + .collect::, _>>()?; + Ok(Row::new(values)) +} + +fn local_params(params: Params) -> turso::params::Params { + match params { + Params::None => turso::params::Params::None, + Params::Positional(values) => { + turso::params::Params::Positional(values.into_iter().map(local_value).collect()) + } + Params::Named(values) => turso::params::Params::Named( + values + .into_iter() + .map(|(key, value)| (key, local_value(value))) + .collect(), + ), + } +} + +fn local_value(value: Value) -> turso::Value { + match value { + Value::Null => turso::Value::Null, + Value::Integer(value) => turso::Value::Integer(value), + Value::Real(value) => turso::Value::Real(value), + Value::Text(value) => turso::Value::Text(value), + Value::Blob(value) => turso::Value::Blob(value), + } +} + +fn remote_value(value: turso::Value) -> Value { + match value { + turso::Value::Null => Value::Null, + turso::Value::Integer(value) => Value::Integer(value), + turso::Value::Real(value) => Value::Real(value), + turso::Value::Text(value) => Value::Text(value), + turso::Value::Blob(value) => Value::Blob(value), + } +} + +#[cfg(test)] +mod tests; diff --git a/crates/temper-store-turso/src/driver/tests.rs b/crates/temper-store-turso/src/driver/tests.rs new file mode 100644 index 00000000..41e3447a --- /dev/null +++ b/crates/temper-store-turso/src/driver/tests.rs @@ -0,0 +1,88 @@ +use super::*; + +#[tokio::test] +async fn local_rows_preserve_positional_and_named_values() { + let db = Database::local(":memory:").await.unwrap(); + let conn = db.connect().unwrap(); + let mut rows = conn + .query( + "SELECT ?1, ?2, ?3, ?4, ?5", + params![i64::MAX, -1.25, "Δ state", vec![0_u8, 255, 17], Value::Null,], + ) + .await + .unwrap(); + let row = rows.next().await.unwrap().unwrap(); + assert_eq!(row.get::(0).unwrap(), i64::MAX); + assert_eq!(row.get::(1).unwrap(), -1.25); + assert_eq!(row.get::(2).unwrap(), "Δ state"); + assert_eq!(row.get::>(3).unwrap(), vec![0, 255, 17]); + assert!(matches!(row.get_value(4).unwrap(), Value::Null)); + assert!(rows.next().await.unwrap().is_none()); + let mut rows = conn + .query("SELECT :value", [(":value", "named")]) + .await + .unwrap(); + assert_eq!( + rows.next() + .await + .unwrap() + .unwrap() + .get::(0) + .unwrap(), + "named" + ); +} + +#[tokio::test] +async fn failed_transaction_rolls_back_before_next_connection_use() { + let db = Database::local(":memory:").await.unwrap(); + let conn = db.connect().unwrap(); + conn.execute("CREATE TABLE values_to_commit(id INTEGER PRIMARY KEY)", ()) + .await + .unwrap(); + { + let tx = conn.begin_immediate().await.unwrap(); + tx.execute("INSERT INTO values_to_commit VALUES (1)", ()) + .await + .unwrap(); + assert!( + tx.execute("INSERT INTO values_to_commit VALUES (1)", ()) + .await + .is_err() + ); + } + let mut rows = conn + .query("SELECT COUNT(*) FROM values_to_commit", ()) + .await + .unwrap(); + assert_eq!( + rows.next().await.unwrap().unwrap().get::(0).unwrap(), + 0 + ); + drop(rows); + let tx = conn.begin_immediate().await.unwrap(); + tx.execute("INSERT INTO values_to_commit VALUES (2)", ()) + .await + .unwrap(); + tx.commit().await.unwrap(); + let mut rows = conn + .query("SELECT id FROM values_to_commit", ()) + .await + .unwrap(); + assert_eq!( + rows.next().await.unwrap().unwrap().get::(0).unwrap(), + 2 + ); +} + +#[tokio::test] +async fn query_executes_configuration_even_when_rows_are_discarded() { + let db = Database::local(":memory:").await.unwrap(); + let conn = db.connect().unwrap(); + drop(conn.query("PRAGMA user_version=17", ()).await.unwrap()); + let mut rows = conn.query("PRAGMA user_version", ()).await.unwrap(); + assert_eq!( + rows.next().await.unwrap().unwrap().get::(0).unwrap(), + 17 + ); +} diff --git a/crates/temper-store-turso/src/lib.rs b/crates/temper-store-turso/src/lib.rs index 53a5372c..64dbfdfe 100644 --- a/crates/temper-store-turso/src/lib.rs +++ b/crates/temper-store-turso/src/lib.rs @@ -1,10 +1,11 @@ //! # temper-store-turso //! -//! Turso/libSQL storage backend for the Temper actor framework. +//! Turso storage backend for the Temper actor framework. //! //! This crate implements the [`EventStore`](temper_runtime::persistence::EventStore) -//! trait from `temper-runtime` using libSQL (Turso-compatible). +//! trait using the official embedded Turso engine and remote serverless client. +mod driver; mod metrics; mod retry; pub mod router; diff --git a/crates/temper-store-turso/src/router.rs b/crates/temper-store-turso/src/router.rs index 3c7e1fe0..6c8e96c4 100644 --- a/crates/temper-store-turso/src/router.rs +++ b/crates/temper-store-turso/src/router.rs @@ -181,7 +181,7 @@ impl TenantStoreRouter { let mut rows = conn .query( "SELECT tenant_id, user_id, role FROM tenant_users WHERE user_id = ?1", - libsql::params![user_id], + crate::driver::params![user_id], ) .await .map_err(storage_error)?; @@ -208,7 +208,7 @@ impl TenantStoreRouter { let conn = self.platform.connection().map_err(storage_error)?; conn.execute( "INSERT OR REPLACE INTO tenant_users (tenant_id, user_id, role) VALUES (?1, ?2, ?3)", - libsql::params![tenant_id, user_id, role], + crate::driver::params![tenant_id, user_id, role], ) .await .map_err(storage_error)?; @@ -225,7 +225,7 @@ impl TenantStoreRouter { let mut rows = conn .query( "SELECT tenant_id, user_id, role FROM tenant_users WHERE tenant_id = ?1", - libsql::params![tenant_id], + crate::driver::params![tenant_id], ) .await .map_err(storage_error)?; @@ -252,13 +252,13 @@ impl TenantStoreRouter { // Delete associated users and installed apps first. conn.execute( "DELETE FROM tenant_users WHERE tenant_id = ?1", - libsql::params![tenant_id], + crate::driver::params![tenant_id], ) .await .map_err(storage_error)?; conn.execute( "DELETE FROM tenant_installed_apps WHERE tenant_id = ?1", - libsql::params![tenant_id], + crate::driver::params![tenant_id], ) .await .map_err(storage_error)?; @@ -267,7 +267,7 @@ impl TenantStoreRouter { let result = conn .execute( "DELETE FROM tenant_registry WHERE tenant_id = ?1", - libsql::params![tenant_id], + crate::driver::params![tenant_id], ) .await .map_err(storage_error)?; @@ -292,7 +292,7 @@ impl TenantStoreRouter { let conn = self.platform.connection().map_err(storage_error)?; conn.execute( "DELETE FROM tenant_users WHERE tenant_id = ?1 AND user_id = ?2", - libsql::params![tenant_id, user_id], + crate::driver::params![tenant_id, user_id], ) .await .map_err(storage_error)?; @@ -316,7 +316,7 @@ impl TenantStoreRouter { let mut rows = conn .query( "SELECT turso_db_url, turso_auth_token FROM tenant_registry WHERE tenant_id = ?1", - libsql::params![tenant_id], + crate::driver::params![tenant_id], ) .await .map_err(storage_error)?; @@ -365,7 +365,7 @@ impl TenantStoreRouter { conn.execute( "INSERT INTO tenant_registry (tenant_id, turso_db_url, turso_auth_token) VALUES (?1, ?2, ?3)", - libsql::params![tenant_id, db_url, auth_token], + crate::driver::params![tenant_id, db_url, auth_token], ) .await .map_err(storage_error)?; @@ -835,7 +835,7 @@ impl EventStore for TenantStoreRouter { #[cfg(test)] mod tests { use super::*; - use libsql::params; + use crate::driver::params; #[tokio::test] async fn test_router_local_dev() { diff --git a/crates/temper-store-turso/src/store/authz.rs b/crates/temper-store-turso/src/store/authz.rs index 406dbd9a..3d2ffb93 100644 --- a/crates/temper-store-turso/src/store/authz.rs +++ b/crates/temper-store-turso/src/store/authz.rs @@ -1,6 +1,6 @@ //! Authorization decisions and Cedar policy persistence. -use libsql::params; +use crate::driver::params; use temper_runtime::persistence::{PersistenceError, storage_error}; use tracing::instrument; diff --git a/crates/temper-store-turso/src/store/blobs.rs b/crates/temper-store-turso/src/store/blobs.rs index ecaeac40..182b42d4 100644 --- a/crates/temper-store-turso/src/store/blobs.rs +++ b/crates/temper-store-turso/src/store/blobs.rs @@ -4,7 +4,7 @@ //! This provides persistent local blob storage so the blob_adapter WASM module //! can upload/download via HTTP without requiring external S3/R2. -use libsql::params; +use crate::driver::params; use std::time::Duration; use crate::TursoEventStore; @@ -202,7 +202,7 @@ impl TursoEventStore { .get_value(0) .map_err(|e| format!("blob read failed: {e}")) .and_then(|v| match v { - libsql::Value::Blob(b) => Ok(b), + crate::driver::Value::Blob(b) => Ok(b), _ => Err("blob column is not BLOB type".to_string()), })?; Ok(Some(data)) @@ -262,7 +262,7 @@ impl TursoEventStore { .get_value(1) .map_err(|e| format!("blob data read failed: {e}")) .and_then(|value| match value { - libsql::Value::Blob(bytes) => Ok(bytes), + crate::driver::Value::Blob(bytes) => Ok(bytes), _ => Err("blob data column is not BLOB type".to_string()), })?; out.push(TursoBlobRow { diff --git a/crates/temper-store-turso/src/store/constraints.rs b/crates/temper-store-turso/src/store/constraints.rs index abc8854e..37c422ff 100644 --- a/crates/temper-store-turso/src/store/constraints.rs +++ b/crates/temper-store-turso/src/store/constraints.rs @@ -1,6 +1,6 @@ //! Tenant-level cross-entity constraint persistence. -use libsql::params; +use crate::driver::params; use temper_runtime::persistence::{PersistenceError, storage_error}; use tracing::instrument; diff --git a/crates/temper-store-turso/src/store/entity_listing.rs b/crates/temper-store-turso/src/store/entity_listing.rs index 7ccdf600..4bbc0c1b 100644 --- a/crates/temper-store-turso/src/store/entity_listing.rs +++ b/crates/temper-store-turso/src/store/entity_listing.rs @@ -1,6 +1,6 @@ //! Entity listing helpers for Turso/libSQL-backed runtime recovery. -use libsql::params; +use crate::driver::params; use temper_runtime::persistence::{PersistenceError, storage_error}; use tracing::instrument; diff --git a/crates/temper-store-turso/src/store/event_store.rs b/crates/temper-store-turso/src/store/event_store.rs index 84d23fb0..92162874 100644 --- a/crates/temper-store-turso/src/store/event_store.rs +++ b/crates/temper-store-turso/src/store/event_store.rs @@ -1,6 +1,6 @@ -//! [`EventStore`] trait implementation for Turso/libSQL. +//! [`EventStore`] trait implementation for Turso. -use libsql::{TransactionBehavior, Value, params, params_from_iter}; +use crate::driver::{Value, params, params_from_iter}; use std::time::Duration; use temper_runtime::persistence::{ EntityVectorCandidate, EntityVectorRow, EventMetadata, EventStore, PersistenceAppend, @@ -225,10 +225,7 @@ impl EventStore for TursoEventStore { .acquire_write_permit("turso.backfill_entity_vectors", WritePriority::Low) .await?; let conn = self.configured_connection().await?; - let tx = conn - .transaction_with_behavior(TransactionBehavior::Immediate) - .await - .map_err(storage_error)?; + let tx = conn.begin_immediate().await.map_err(storage_error)?; tx.execute( "DELETE FROM entity_vector_index \ WHERE tenant = ?1 AND entity_type = ?2 AND entity_id = ?3", @@ -484,10 +481,7 @@ impl EventStore for TursoEventStore { .acquire_write_permit("turso.save_snapshot", WritePriority::Low) .await?; let conn = self.configured_connection().await?; - let tx = conn - .transaction_with_behavior(TransactionBehavior::Immediate) - .await - .map_err(storage_error)?; + let tx = conn.begin_immediate().await.map_err(storage_error)?; tx.execute( "INSERT INTO snapshots (tenant, entity_type, entity_id, sequence_nr, snapshot) @@ -825,10 +819,7 @@ impl TursoEventStore { let (tenant, entity_type, entity_id) = parse_persistence_id_parts(persistence_id).map_err(PersistenceError::Storage)?; let conn = self.configured_connection().await?; - let tx = conn - .transaction_with_behavior(TransactionBehavior::Immediate) - .await - .map_err(storage_error)?; + let tx = conn.begin_immediate().await.map_err(storage_error)?; let select_start = std::time::Instant::now(); let rows_result = tx @@ -1002,10 +993,7 @@ impl TursoEventStore { } let conn = self.configured_connection().await?; - let tx = conn - .transaction_with_behavior(TransactionBehavior::Immediate) - .await - .map_err(storage_error)?; + let tx = conn.begin_immediate().await.map_err(storage_error)?; let mut parsed = Vec::with_capacity(appends.len()); for append in appends { diff --git a/crates/temper-store-turso/src/store/evolution.rs b/crates/temper-store-turso/src/store/evolution.rs index 73695561..c939999a 100644 --- a/crates/temper-store-turso/src/store/evolution.rs +++ b/crates/temper-store-turso/src/store/evolution.rs @@ -1,6 +1,6 @@ //! Feature requests, evolution records, and design-time events. -use libsql::params; +use crate::driver::params; use temper_runtime::persistence::{PersistenceError, storage_error}; use tracing::instrument; @@ -269,7 +269,7 @@ impl TursoEventStore { /// Parse an evolution record row. pub(super) fn row_to_evolution_record( - row: &libsql::Row, + row: &crate::driver::Row, ) -> Result { Ok(EvolutionRecordRow { id: row.get::(0).map_err(storage_error)?, diff --git a/crates/temper-store-turso/src/store/field_index.rs b/crates/temper-store-turso/src/store/field_index.rs index e2f69280..43c79e45 100644 --- a/crates/temper-store-turso/src/store/field_index.rs +++ b/crates/temper-store-turso/src/store/field_index.rs @@ -5,7 +5,7 @@ //! clauses. This avoids materializing every actor in a collection query //! just to evaluate filters in memory. -use libsql::{TransactionBehavior, Value, params, params_from_iter}; +use crate::driver::{Value, params, params_from_iter}; use sha2::{Digest, Sha256}; use std::collections::{BTreeMap, BTreeSet}; use temper_runtime::persistence::{PersistenceError, storage_error}; @@ -175,10 +175,7 @@ impl TursoEventStore { return Ok(()); } - let tx = conn - .transaction_with_behavior(TransactionBehavior::Immediate) - .await - .map_err(storage_error)?; + let tx = conn.begin_immediate().await.map_err(storage_error)?; let existing_sequence = { let mut existing_rows = tx @@ -291,10 +288,7 @@ impl TursoEventStore { .acquire_write_permit("turso.upsert_query_projections", WritePriority::Low) .await?; let conn = self.configured_connection().await?; - let tx = conn - .transaction_with_behavior(TransactionBehavior::Immediate) - .await - .map_err(storage_error)?; + let tx = conn.begin_immediate().await.map_err(storage_error)?; let updated_at = sim_now().to_rfc3339(); let mut existing = BTreeMap::new(); let existing_candidates = projections @@ -534,10 +528,7 @@ impl TursoEventStore { .acquire_write_permit("turso.remove_query_projection", WritePriority::Low) .await?; let conn = self.configured_connection().await?; - let tx = conn - .transaction_with_behavior(TransactionBehavior::Immediate) - .await - .map_err(storage_error)?; + let tx = conn.begin_immediate().await.map_err(storage_error)?; tx.execute( "DELETE FROM entity_catalog WHERE tenant = ?1 AND entity_type = ?2 AND entity_id = ?3", params![tenant, entity_type, entity_id], @@ -575,8 +566,8 @@ impl TursoEventStore { /// and `params` are generated by the OData-to-SQL translator /// (`filter_sql::try_translate_filter`). /// - /// Parameters use `String` (not `libsql::Value`) so the interface is - /// usable from crates that don't depend on `libsql` directly. + /// Parameters use `String` (not `crate::driver::Value`) so the interface is + /// usable from crates that don't depend on the database driver directly. #[instrument(skip_all, fields( otel.name = "turso.query_field_index", tenant, entity_type, @@ -596,14 +587,14 @@ impl TursoEventStore { // Prepend the tenant/entity_type params. The translator's params // start at ?3 so there's no collision. - let mut all_params: Vec = vec![ - libsql::Value::from(tenant.to_string()), - libsql::Value::from(entity_type.to_string()), + let mut all_params: Vec = vec![ + crate::driver::Value::from(tenant.to_string()), + crate::driver::Value::from(entity_type.to_string()), ]; - all_params.extend(params.into_iter().map(libsql::Value::from)); + all_params.extend(params.into_iter().map(crate::driver::Value::from)); let mut rows = conn - .query(&sql, libsql::params_from_iter(all_params)) + .query(&sql, crate::driver::params_from_iter(all_params)) .await .map_err(storage_error)?; @@ -660,19 +651,19 @@ impl TursoEventStore { ORDER BY c.entity_id, f.field_name" ); - let mut params: Vec = + let mut params: Vec = Vec::with_capacity(2 + requested_fields.len() + entity_ids.len()); for field_name in &requested_fields { - params.push(libsql::Value::from((*field_name).to_string())); + params.push(crate::driver::Value::from((*field_name).to_string())); } - params.push(libsql::Value::from(tenant.to_string())); - params.push(libsql::Value::from(entity_type.to_string())); + params.push(crate::driver::Value::from(tenant.to_string())); + params.push(crate::driver::Value::from(entity_type.to_string())); for entity_id in entity_ids { - params.push(libsql::Value::from(entity_id.clone())); + params.push(crate::driver::Value::from(entity_id.clone())); } let mut rows = conn - .query(&sql, libsql::params_from_iter(params)) + .query(&sql, crate::driver::params_from_iter(params)) .await .map_err(storage_error)?; @@ -810,11 +801,11 @@ impl TursoEventStore { WHERE tenant = ? AND entity_type = ? AND entity_id IN ({placeholders}) \ ORDER BY entity_id" ); - let mut params: Vec = Vec::with_capacity(entity_ids.len() + 2); - params.push(libsql::Value::Text(tenant.to_string())); - params.push(libsql::Value::Text(entity_type.to_string())); + let mut params: Vec = Vec::with_capacity(entity_ids.len() + 2); + params.push(crate::driver::Value::Text(tenant.to_string())); + params.push(crate::driver::Value::Text(entity_type.to_string())); for id in entity_ids { - params.push(libsql::Value::Text(id.clone())); + params.push(crate::driver::Value::Text(id.clone())); } let mut rows = conn.query(&sql, params).await.map_err(storage_error)?; let mut out = Vec::new(); diff --git a/crates/temper-store-turso/src/store/instrumentation.rs b/crates/temper-store-turso/src/store/instrumentation.rs index aea9351b..55f13549 100644 --- a/crates/temper-store-turso/src/store/instrumentation.rs +++ b/crates/temper-store-turso/src/store/instrumentation.rs @@ -40,19 +40,19 @@ pub(super) fn record_turso_query_duration( /// Connection wrapper that records Turso query/execute latency metrics. pub(crate) struct InstrumentedConnection { - inner: libsql::Connection, + inner: crate::driver::Connection, } impl InstrumentedConnection { - pub(super) fn new(inner: libsql::Connection) -> Self { + pub(super) fn new(inner: crate::driver::Connection) -> Self { Self { inner } } pub(crate) async fn query( &self, sql: &str, - params: impl libsql::params::IntoParams, - ) -> Result { + params: impl turso_serverless::params::IntoParams, + ) -> Result { let start = Instant::now(); let result = self.inner.query(sql, params).await; record_turso_query_duration(start.elapsed(), "query", "connection", result.is_ok()); @@ -62,8 +62,8 @@ impl InstrumentedConnection { pub(crate) async fn execute( &self, sql: &str, - params: impl libsql::params::IntoParams, - ) -> Result { + params: impl turso_serverless::params::IntoParams, + ) -> Result { let start = Instant::now(); let result = self.inner.execute(sql, params).await; record_turso_query_duration(start.elapsed(), "execute", "connection", result.is_ok()); @@ -72,7 +72,7 @@ impl InstrumentedConnection { } impl Deref for InstrumentedConnection { - type Target = libsql::Connection; + type Target = crate::driver::Connection; fn deref(&self) -> &Self::Target { &self.inner diff --git a/crates/temper-store-turso/src/store/mod.rs b/crates/temper-store-turso/src/store/mod.rs index fce01e29..e8df02bb 100644 --- a/crates/temper-store-turso/src/store/mod.rs +++ b/crates/temper-store-turso/src/store/mod.rs @@ -9,7 +9,7 @@ //! - [`constraints`]: Tenant-level cross-entity constraints //! - [`event_store`]: [`EventStore`] trait implementation -use libsql::{Builder, Database}; +use crate::driver::Database; use std::sync::Arc; use std::sync::atomic::AtomicUsize; use temper_runtime::persistence::{PersistenceError, storage_error}; @@ -68,16 +68,10 @@ impl TursoEventStore { tracing::error!("auth token is required for libsql:// URLs"); PersistenceError::Storage("auth token is required for libsql:// URLs".to_string()) })?; - Builder::new_remote(url.to_string(), token.to_string()) - .build() - .await - .map_err(storage_error)? + Database::remote(url, token).await.map_err(storage_error)? } else { let local_path = url.strip_prefix("file:").unwrap_or(url); - Builder::new_local(local_path) - .build() - .await - .map_err(storage_error)? + Database::local(local_path).await.map_err(storage_error)? }; let is_remote = url.starts_with("libsql://"); @@ -494,14 +488,8 @@ impl TursoEventStore { /// Obtain a connection handle to the underlying database. /// - /// `Database::connect()` returns a lightweight handle, **not** a fresh TCP - /// connection each time: - /// - **Local SQLite** (`file:` URLs): a handle to the same underlying - /// database file — no network overhead. - /// - **Remote Turso** (`libsql://` URLs): a handle drawn from an internal - /// HTTP/gRPC connection pool managed by the `libsql` crate. - /// - /// It is safe (and cheap) to call this at the start of every method. + /// Local connections share the embedded Turso database. Remote connections + /// use the official serverless client's HTTP transport. pub(crate) fn connection(&self) -> Result { Ok(InstrumentedConnection::new( self.db.connect().map_err(storage_error)?, diff --git a/crates/temper-store-turso/src/store/ots.rs b/crates/temper-store-turso/src/store/ots.rs index 602f05f1..f9c50a33 100644 --- a/crates/temper-store-turso/src/store/ots.rs +++ b/crates/temper-store-turso/src/store/ots.rs @@ -1,6 +1,6 @@ //! OTS trajectory persistence methods. -use libsql::params; +use crate::driver::params; use temper_runtime::persistence::{PersistenceError, storage_error}; use tracing::instrument; @@ -248,7 +248,7 @@ impl TursoEventStore { } sql.push_str(&format!(" ORDER BY created_at DESC LIMIT {limit}")); - let mut values: Vec = vec![tenant.to_string().into()]; + let mut values: Vec = vec![tenant.to_string().into()]; if let Some(aid) = agent_id { values.push(aid.to_string().into()); } @@ -257,7 +257,7 @@ impl TursoEventStore { } let mut rows = conn - .query(&sql, libsql::params_from_iter(values)) + .query(&sql, crate::driver::params_from_iter(values)) .await .map_err(storage_error)?; diff --git a/crates/temper-store-turso/src/store/ots_test.rs b/crates/temper-store-turso/src/store/ots_test.rs index 666a4a83..5b46f29c 100644 --- a/crates/temper-store-turso/src/store/ots_test.rs +++ b/crates/temper-store-turso/src/store/ots_test.rs @@ -184,7 +184,7 @@ async fn a_globally_keyed_table_is_rekeyed_by_tenant_on_open() { let db_url = format!("file:{}", db_path.display()); { - let legacy = libsql::Builder::new_local(&db_path) + let legacy = turso::Builder::new_local(db_path.to_str().unwrap()) .build() .await .expect("open legacy db"); diff --git a/crates/temper-store-turso/src/store/policy.rs b/crates/temper-store-turso/src/store/policy.rs index a72d25da..9ce273e1 100644 --- a/crates/temper-store-turso/src/store/policy.rs +++ b/crates/temper-store-turso/src/store/policy.rs @@ -5,7 +5,7 @@ //! per tenant), this table supports multiple entries per tenant, each with its own //! `policy_id`, content hash, enabled flag, and audit fields. -use libsql::params; +use crate::driver::params; use sha2::{Digest, Sha256}; use temper_runtime::persistence::{PersistenceError, storage_error}; use tracing::instrument; diff --git a/crates/temper-store-turso/src/store/policy_approval.rs b/crates/temper-store-turso/src/store/policy_approval.rs index 3e6d1188..b0844068 100644 --- a/crates/temper-store-turso/src/store/policy_approval.rs +++ b/crates/temper-store-turso/src/store/policy_approval.rs @@ -1,6 +1,6 @@ //! Transactional persistence for Cedar policy approvals. -use libsql::{TransactionBehavior, params}; +use crate::driver::params; use temper_runtime::persistence::{PersistenceError, storage_error}; use super::TursoEventStore; @@ -23,10 +23,7 @@ impl TursoEventStore { } = commit; let policy_hash = compute_policy_hash(cedar_text); let connection = self.configured_connection().await?; - let transaction = connection - .transaction_with_behavior(TransactionBehavior::Immediate) - .await - .map_err(storage_error)?; + let transaction = connection.begin_immediate().await.map_err(storage_error)?; let policy_rows = transaction .execute( @@ -71,10 +68,7 @@ impl TursoEventStore { policy_id: &str, ) -> Result<(), PersistenceError> { let connection = self.configured_connection().await?; - let transaction = connection - .transaction_with_behavior(TransactionBehavior::Immediate) - .await - .map_err(storage_error)?; + let transaction = connection.begin_immediate().await.map_err(storage_error)?; transaction .execute( diff --git a/crates/temper-store-turso/src/store/published_artifacts.rs b/crates/temper-store-turso/src/store/published_artifacts.rs index 5d974269..ad4fcaf0 100644 --- a/crates/temper-store-turso/src/store/published_artifacts.rs +++ b/crates/temper-store-turso/src/store/published_artifacts.rs @@ -1,4 +1,4 @@ -use libsql::params; +use crate::driver::params; use temper_runtime::persistence::{PersistenceError, storage_error}; use tracing::instrument; diff --git a/crates/temper-store-turso/src/store/query_page.rs b/crates/temper-store-turso/src/store/query_page.rs index 55165566..a51ab340 100644 --- a/crates/temper-store-turso/src/store/query_page.rs +++ b/crates/temper-store-turso/src/store/query_page.rs @@ -46,16 +46,18 @@ impl TursoEventStore { include_count, ); - let mut all_params: Vec = vec![ - libsql::Value::from(tenant.to_string()), - libsql::Value::from(entity_type.to_string()), + let mut all_params: Vec = vec![ + crate::driver::Value::from(tenant.to_string()), + crate::driver::Value::from(entity_type.to_string()), ]; - all_params.extend(params.iter().cloned().map(libsql::Value::from)); - all_params.push(libsql::Value::from(top.min(i64::MAX as usize) as i64)); - all_params.push(libsql::Value::from(skip.min(i64::MAX as usize) as i64)); + all_params.extend(params.iter().cloned().map(crate::driver::Value::from)); + all_params.push(crate::driver::Value::from(top.min(i64::MAX as usize) as i64)); + all_params.push(crate::driver::Value::from( + skip.min(i64::MAX as usize) as i64 + )); let mut rows = conn - .query(&sql, libsql::params_from_iter(all_params)) + .query(&sql, crate::driver::params_from_iter(all_params)) .await .map_err(storage_error)?; @@ -93,13 +95,13 @@ impl TursoEventStore { FROM entity_catalog \ WHERE tenant = ?1 AND entity_type = ?2 AND ({where_clause})" ); - let mut all_params: Vec = vec![ - libsql::Value::from(tenant.to_string()), - libsql::Value::from(entity_type.to_string()), + let mut all_params: Vec = vec![ + crate::driver::Value::from(tenant.to_string()), + crate::driver::Value::from(entity_type.to_string()), ]; - all_params.extend(params.into_iter().map(libsql::Value::from)); + all_params.extend(params.into_iter().map(crate::driver::Value::from)); let count: i64 = conn - .query(&sql, libsql::params_from_iter(all_params)) + .query(&sql, crate::driver::params_from_iter(all_params)) .await .map_err(storage_error)? .next() diff --git a/crates/temper-store-turso/src/store/secrets.rs b/crates/temper-store-turso/src/store/secrets.rs index 53a3ab6b..d4e40b03 100644 --- a/crates/temper-store-turso/src/store/secrets.rs +++ b/crates/temper-store-turso/src/store/secrets.rs @@ -3,7 +3,7 @@ //! CRUD operations on the `tenant_secrets` table, storing AES-256-GCM //! encrypted ciphertext and nonce as BLOBs. -use libsql::params; +use crate::driver::params; use temper_runtime::persistence::{PersistenceError, storage_error}; use tracing::instrument; diff --git a/crates/temper-store-turso/src/store/specs.rs b/crates/temper-store-turso/src/store/specs.rs index 5edb0ab4..21ad4f5e 100644 --- a/crates/temper-store-turso/src/store/specs.rs +++ b/crates/temper-store-turso/src/store/specs.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; -use libsql::{TransactionBehavior, params}; +use crate::driver::params; use temper_runtime::persistence::{PersistenceError, storage_error}; use tracing::instrument; @@ -85,7 +85,7 @@ impl TursoEventStore { /// Atomically upsert multiple specs, record the app installation, optionally /// write a Cedar policy, and mark all tenant specs as committed — all within - /// a single libsql transaction. + /// a single database transaction. /// /// This eliminates the crash-vulnerability window where individual upserts /// leave specs with `committed=0` that get garbage-collected on restart. @@ -112,10 +112,7 @@ impl TursoEventStore { let _write_permit = self .acquire_write_permit("turso.upsert_specs_and_commit", WritePriority::High) .await?; - let tx = conn - .transaction_with_behavior(TransactionBehavior::Immediate) - .await - .map_err(storage_error)?; + let tx = conn.begin_immediate().await.map_err(storage_error)?; for index in spec_indices { let (entity_type, ioa_source, csdl_xml, content_hash) = specs[index]; @@ -186,7 +183,7 @@ impl TursoEventStore { async fn spec_indices_requiring_upsert( &self, - conn: &libsql::Connection, + conn: &crate::driver::Connection, tenant: &str, specs: &[(&str, &str, &str, &str)], ) -> Result, PersistenceError> { @@ -216,7 +213,7 @@ impl TursoEventStore { } async fn load_spec_fingerprints( - conn: &libsql::Connection, + conn: &crate::driver::Connection, tenant: &str, entity_types: &[&str], ) -> Result, PersistenceError> { @@ -233,7 +230,7 @@ impl TursoEventStore { FROM specs \ WHERE tenant = ?1 AND entity_type IN ({placeholders})" ); - let mut values: Vec = vec![tenant.to_string().into()]; + let mut values: Vec = vec![tenant.to_string().into()]; values.extend( entity_types .iter() @@ -241,7 +238,7 @@ impl TursoEventStore { ); let mut rows = conn - .query(&sql, libsql::params_from_iter(values)) + .query(&sql, crate::driver::params_from_iter(values)) .await .map_err(storage_error)?; let mut existing = BTreeMap::new(); @@ -267,7 +264,7 @@ impl TursoEventStore { } async fn tenant_policy_needs_write( - conn: &libsql::Connection, + conn: &crate::driver::Connection, tenant: &str, policy: Option<&str>, ) -> Result { @@ -290,7 +287,7 @@ impl TursoEventStore { } async fn installed_app_needs_write( - conn: &libsql::Connection, + conn: &crate::driver::Connection, tenant: &str, app_name: &str, ) -> Result { diff --git a/crates/temper-store-turso/src/store/tests/evolution_tenant.rs b/crates/temper-store-turso/src/store/tests/evolution_tenant.rs index 8990cd81..bea0b369 100644 --- a/crates/temper-store-turso/src/store/tests/evolution_tenant.rs +++ b/crates/temper-store-turso/src/store/tests/evolution_tenant.rs @@ -6,7 +6,7 @@ use super::{make_store, sqlite_test_url}; async fn legacy_evolution_tables_gain_default_tenant_ownership() { let url = sqlite_test_url("evolution-legacy-migration"); let path = url.strip_prefix("file:").unwrap(); - let database = libsql::Builder::new_local(path).build().await.unwrap(); + let database = turso::Builder::new_local(path).build().await.unwrap(); let connection = database.connect().unwrap(); connection .execute_batch( diff --git a/crates/temper-store-turso/src/store/tests/mod.rs b/crates/temper-store-turso/src/store/tests/mod.rs index 530d17e8..2a4121a9 100644 --- a/crates/temper-store-turso/src/store/tests/mod.rs +++ b/crates/temper-store-turso/src/store/tests/mod.rs @@ -1,6 +1,6 @@ //! Integration tests for the Turso event store. -use libsql::params; +use crate::driver::params; use temper_runtime::persistence::{ EntityVectorRow, EventMetadata, EventStore, PersistenceAppend, PersistenceEnvelope, PersistenceError, diff --git a/crates/temper-store-turso/src/store/trajectory.rs b/crates/temper-store-turso/src/store/trajectory.rs index 961a6204..331c3296 100644 --- a/crates/temper-store-turso/src/store/trajectory.rs +++ b/crates/temper-store-turso/src/store/trajectory.rs @@ -2,7 +2,7 @@ //! //! The read path lives in the sibling `trajectory_queries` module. -use libsql::params; +use crate::driver::params; use std::time::Duration; use temper_runtime::persistence::{PersistenceError, storage_error}; use tracing::instrument; diff --git a/crates/temper-store-turso/src/store/trajectory_queries.rs b/crates/temper-store-turso/src/store/trajectory_queries.rs index 35ecf619..08436dc8 100644 --- a/crates/temper-store-turso/src/store/trajectory_queries.rs +++ b/crates/temper-store-turso/src/store/trajectory_queries.rs @@ -5,7 +5,7 @@ //! intents, aggregate stats, and the agent- and session-scoped replays — //! lives here. -use libsql::params; +use crate::driver::params; use temper_runtime::persistence::{PersistenceError, storage_error}; use tracing::instrument; @@ -165,8 +165,8 @@ impl TursoEventStore { Ok(out) } - /// Parse a trajectory row from a libsql Row (16 columns). - fn row_to_trajectory(row: &libsql::Row) -> Result { + /// Parse a trajectory row from a database row (16 columns). + fn row_to_trajectory(row: &crate::driver::Row) -> Result { Ok(TursoTrajectoryRow { tenant: row.get::(0).map_err(storage_error)?, entity_type: row.get::(1).map_err(storage_error)?, diff --git a/crates/temper-store-turso/src/store/wasm.rs b/crates/temper-store-turso/src/store/wasm.rs index 6b84bf52..e78e3f93 100644 --- a/crates/temper-store-turso/src/store/wasm.rs +++ b/crates/temper-store-turso/src/store/wasm.rs @@ -1,6 +1,6 @@ //! WASM module storage and invocation log persistence. -use libsql::params; +use crate::driver::params; use temper_runtime::persistence::{PersistenceError, storage_error}; use tracing::instrument; @@ -294,8 +294,10 @@ impl TursoEventStore { Ok(affected > 0) } - /// Parse a WASM module row from a libsql Row (8 columns). - fn row_to_wasm_module(row: &libsql::Row) -> Result { + /// Parse a WASM module row from a database row (8 columns). + fn row_to_wasm_module( + row: &crate::driver::Row, + ) -> Result { Ok(TursoWasmModuleRow { tenant: row.get::(0).map_err(storage_error)?, module_name: row.get::(1).map_err(storage_error)?, @@ -310,9 +312,9 @@ impl TursoEventStore { }) } - /// Parse a WASM module metadata row from a libsql Row (5 columns). + /// Parse a WASM module metadata row from a database row (5 columns). fn row_to_wasm_module_metadata( - row: &libsql::Row, + row: &crate::driver::Row, ) -> Result { Ok(TursoWasmModuleMetadataRow { tenant: row.get::(0).map_err(storage_error)?, diff --git a/crates/temper-store-turso/tests/blob_ttl_e2e.rs b/crates/temper-store-turso/tests/blob_ttl_e2e.rs index bce466cc..84c51c42 100644 --- a/crates/temper-store-turso/tests/blob_ttl_e2e.rs +++ b/crates/temper-store-turso/tests/blob_ttl_e2e.rs @@ -1,5 +1,5 @@ //! End-to-end verification of the Phase 4 blob TTL + sweep primitives against -//! a real local libsql/Turso database (file-backed). +//! a real local Turso database (file-backed). //! //! Covers ADR-0047: //! - `put_blob` writes with NULL expires_at (permanent). diff --git a/crates/temper-store-turso/tests/connection_lifetime.rs b/crates/temper-store-turso/tests/connection_lifetime.rs index 02f5a3a0..e76d6970 100644 --- a/crates/temper-store-turso/tests/connection_lifetime.rs +++ b/crates/temper-store-turso/tests/connection_lifetime.rs @@ -14,18 +14,16 @@ fn concurrent_connection_drops_do_not_close_reused_handles() { .build() .unwrap(); runtime.block_on(async { - let database = libsql::Builder::new_local(":memory:") - .build() - .await - .unwrap(); + let database = turso::Builder::new_local(":memory:").build().await.unwrap(); barrier.wait(); for cycle in 0..CYCLES { - let connection = database.connect().unwrap(); + let mut connection = database.connect().unwrap(); connection - .execute("CREATE TABLE proof (value INTEGER)", ()) + .execute("CREATE TABLE IF NOT EXISTS proof (value INTEGER)", ()) .await .unwrap(); let transaction = connection.transaction().await.unwrap(); + transaction.execute("DELETE FROM proof", ()).await.unwrap(); let value = (worker * CYCLES + cycle) as i64; transaction .execute("INSERT INTO proof VALUES (?1)", [value]) diff --git a/docs/adrs/0175-libsql-connection-lifetime.md b/docs/adrs/0175-libsql-connection-lifetime.md index accf7eed..79020001 100644 --- a/docs/adrs/0175-libsql-connection-lifetime.md +++ b/docs/adrs/0175-libsql-connection-lifetime.md @@ -1,6 +1,6 @@ # ADR-0175: One owner of the libSQL connection close -- Status: Accepted +- Status: Superseded by ADR-0176 - Date: 2026-09-08 - Deciders: ARN-467 implementer - Related: crates/temper-store-turso; docs/efforts/ARN-467/decisions.md (D27) @@ -18,3 +18,6 @@ A kernel regression exercises 16 concurrent workers and 10,000 connection lifecy ## Consequences Public builds need no private dependency credential. The storage contract and database format are unchanged. Temper owns maintenance of the vendored patch; dependency updates must preserve the lifetime regression and review the new upstream source against this correction. + + +ADR-0176 replaces the maintained libSQL patch with official Turso packages. This decision remains as the history of the removed patch; no library fork or vendored implementation is retained by that migration. diff --git a/docs/adrs/0176-official-turso-engine.md b/docs/adrs/0176-official-turso-engine.md index ac5d94b6..5769af93 100644 --- a/docs/adrs/0176-official-turso-engine.md +++ b/docs/adrs/0176-official-turso-engine.md @@ -1,6 +1,6 @@ # ADR-0176: Use the official Turso engine without a maintained database fork -- Status: Proposed +- Status: Accepted - Date: 2026-09-09 - Decider: Rita Agafonova - Related: ADR-0175, ARN-467 @@ -11,7 +11,7 @@ ARN-467 copied libSQL into the kernel repository to repair a connection teardown ## Decision -Use published stable Turso packages, starting qualification with turso 0.7.2 and turso_serverless 0.1.3. Preserve the existing storage contract: durable event append and replay, atomic writes, tenant isolation, schema migrations, local file data, and remote Turso connections. Remove vendor/libsql and its dedicated patch checks when the replacement passes qualification. Do not introduce another database fork, patch upstream engine internals, silently drop remote support, or change product semantics to fit an incompatible engine. +Use the published packages turso 0.7.2 and turso_serverless 0.1.3. A private driver adapts their SQL values, rows and transactions to the existing store. Disable the embedded package default features because Temper owns its allocator and does not use full-text search. Preserve the existing storage contract: durable event append and replay, atomic writes, tenant isolation, schema migrations, local file data, and remote Turso connections. Remove vendor/libsql and its dedicated patch checks when the replacement passes qualification. Do not introduce another database fork, patch upstream engine internals, silently drop remote support, or change product semantics to fit an incompatible engine. ## Readiness gates diff --git a/docs/efforts/ARN-467/decisions.md b/docs/efforts/ARN-467/decisions.md index f29bacb8..555f2119 100644 --- a/docs/efforts/ARN-467/decisions.md +++ b/docs/efforts/ARN-467/decisions.md @@ -567,3 +567,28 @@ D30 caller review: Generic stream uploads also need to materialize their authori **Chose current Turso over a maintained libSQL patch because:** It keeps database implementation maintenance upstream. Qualification must preserve existing data and local/remote behavior; an engine incompatibility will be reported without starting another upstream repair project. **Where:** ADR-0176; crates/temper-store-turso; codex/arn467-turso-engine. + + +## D45 — Keep local and remote Turso behind the storage adapter + +**Decision:** Use the official embedded engine for local files and the official serverless client for remote URLs, with one private adapter for the SQL operations the store uses. + +**Came up because:** The two official packages expose separate Rust connection and row types; replacing only the local package would drop working remote support or preserve libSQL. + +**Options:** Keep libSQL for remote connections; duplicate all storage queries; change remote writes into local-first sync; adapt the two official packages at the existing store boundary. + +**Chose the private adapter because:** It preserves one set of storage queries and direct remote-write semantics without database implementation code in Temper. The embedded package's default allocator and full-text-search features are disabled because Temper controls its allocator and does not use those features. + +**Where:** crates/temper-store-turso/src/driver.rs; ADR-0176; PR457. + +## D46 — Start local queries before returning rows + +**Decision:** Prime local query results in the private driver adapter and retain the first row until the caller reads it. + +**Came up because:** The new engine defers execution until `Rows::next`; the previous driver started the statement inside `query`. The regression test `query_executes_configuration_even_when_rows_are_discarded` failed with user_version 0 instead of 17. Temper discards results from configuration queries. + +**Options:** Change individual configuration callers; patch the upstream engine; preserve query execution semantics in the existing private adapter. + +**Chose the adapter over caller changes or an engine patch because:** It preserves the same contract for every store query without a fork or scattered exceptions. The adapter buffers one row and marks exhausted results so subsequent reads cannot restart a completed statement. + +**Where:** `crates/temper-store-turso/src/driver.rs`; `crates/temper-store-turso/src/driver/tests.rs`; PR https://github.com/nerdsane/temper/pull/457. diff --git a/scripts/check-vendored-libsql.py b/scripts/check-vendored-libsql.py deleted file mode 100755 index a8797425..00000000 --- a/scripts/check-vendored-libsql.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python3 -"""Verify the reviewed libSQL source and reject silent vendor drift.""" - -from __future__ import annotations - -import hashlib -import sys -from pathlib import Path - - -def verify(root: Path) -> None: - """Raise ValueError if the vendored tree differs from its reviewed manifest.""" - manifest = root / "TEMPER-SHA256SUMS" - expected: dict[str, str] = {} - for line in manifest.read_text().splitlines(): - digest, name = line.split(" ", 1) - if len(digest) != 64 or any(char not in "0123456789abcdef" for char in digest): - raise ValueError("invalid vendor checksum") - path = Path(name) - if path.is_absolute() or ".." in path.parts or name in expected: - raise ValueError("invalid vendor manifest path") - expected[name] = digest - actual = { - path.relative_to(root).as_posix(): path - for path in root.rglob("*") - if path.is_file() and path != manifest - } - if actual.keys() != expected.keys(): - raise ValueError("vendored libSQL file set differs from its reviewed manifest") - for name, path in actual.items(): - if ( - path.is_symlink() - or hashlib.sha256(path.read_bytes()).hexdigest() != expected[name] - ): - raise ValueError(f"vendored libSQL content differs: {name}") - - -if __name__ == "__main__": - vendor = ( - Path(sys.argv[1]) - if len(sys.argv) == 2 - else Path(__file__).resolve().parents[1] / "vendor/libsql" - ) - try: - verify(vendor) - except (OSError, ValueError) as error: - sys.exit(str(error)) diff --git a/vendor/libsql/.cargo_vcs_info.json b/vendor/libsql/.cargo_vcs_info.json deleted file mode 100644 index a425bb13..00000000 --- a/vendor/libsql/.cargo_vcs_info.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "git": { - "sha1": "6702c3da3ea5c8a348217b3db03acea73a5081b6" - }, - "path_in_vcs": "libsql" -} \ No newline at end of file diff --git a/vendor/libsql/Cargo.lock b/vendor/libsql/Cargo.lock deleted file mode 100644 index 3ed777bb..00000000 --- a/vendor/libsql/Cargo.lock +++ /dev/null @@ -1,2889 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "addr2line" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "getrandom", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "aligned-vec" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e0966165eaf052580bd70eb1b32cb3d6245774c0104d1b2793e9650bf83b52a" -dependencies = [ - "equator", -] - -[[package]] -name = "allocator-api2" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anes" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" - -[[package]] -name = "anstyle" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" - -[[package]] -name = "anyhow" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" - -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - -[[package]] -name = "async-stream" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async-trait" -version = "0.1.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "autocfg" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" - -[[package]] -name = "axum" -version = "0.6.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" -dependencies = [ - "async-trait", - "axum-core", - "bitflags 1.3.2", - "bytes", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "hyper", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "sync_wrapper", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "axum-core" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "mime", - "rustversion", - "tower-layer", - "tower-service", -] - -[[package]] -name = "backtrace" -version = "0.3.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bindgen" -version = "0.66.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" -dependencies = [ - "bitflags 2.6.0", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "log", - "peeking_take_while", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn", - "which", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "block-padding" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "bytemuck" -version = "1.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" -dependencies = [ - "serde", -] - -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - -[[package]] -name = "cbc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" -dependencies = [ - "cipher", -] - -[[package]] -name = "cc" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaff6f8ce506b9773fa786672d63fc7a191ffea1be33f72bbd4aeacefca9ffc8" - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-targets 0.52.6", -] - -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "clap" -version = "4.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" -dependencies = [ - "clap_builder", -] - -[[package]] -name = "clap_builder" -version = "4.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" -dependencies = [ - "anstyle", - "clap_lex", -] - -[[package]] -name = "clap_lex" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" - -[[package]] -name = "cmake" -version = "0.1.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" -dependencies = [ - "cc", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" - -[[package]] -name = "cpp_demangle" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "cpufeatures" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "criterion" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" -dependencies = [ - "anes", - "cast", - "ciborium", - "clap", - "criterion-plot", - "futures", - "is-terminal", - "itertools 0.10.5", - "num-traits", - "once_cell", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_derive", - "serde_json", - "tinytemplate", - "tokio", - "walkdir", -] - -[[package]] -name = "criterion-plot" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" -dependencies = [ - "cast", - "itertools 0.10.5", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "debugid" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" -dependencies = [ - "uuid", -] - -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - -[[package]] -name = "equator" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c35da53b5a021d2484a7cc49b2ac7f2d840f8236a286f84202369bd338d761ea" -dependencies = [ - "equator-macro", -] - -[[package]] -name = "equator-macro" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bf679796c0322556351f287a51b49e48f7c4986e727b5dd78c972d30e2e16cc" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - -[[package]] -name = "fastrand" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" - -[[package]] -name = "findshlibs" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" -dependencies = [ - "cc", - "lazy_static", - "libc", - "winapi", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" - -[[package]] -name = "futures-executor" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" - -[[package]] -name = "futures-macro" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" - -[[package]] -name = "futures-task" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" - -[[package]] -name = "futures-util" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "gimli" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.2.6", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "half" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" -dependencies = [ - "cfg-if", - "crunchy", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "hashlink" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" -dependencies = [ - "hashbrown 0.14.5", -] - -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - -[[package]] -name = "http-body" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" -dependencies = [ - "bytes", - "http 1.1.0", -] - -[[package]] -name = "http-range-header" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" - -[[package]] -name = "httparse" -version = "1.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "0.14.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "399c78f9338483cb7e630c8474b07268983c6bd5acee012e4211f9f7bb21b070" -dependencies = [ - "futures-util", - "http 0.2.12", - "hyper", - "log", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "webpki-roots", -] - -[[package]] -name = "hyper-timeout" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" -dependencies = [ - "hyper", - "pin-project-lite", - "tokio", - "tokio-io-timeout", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - -[[package]] -name = "indexmap" -version = "2.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" -dependencies = [ - "equivalent", - "hashbrown 0.14.5", -] - -[[package]] -name = "inferno" -version = "0.11.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321f0f839cd44a4686e9504b0a62b4d69a50b62072144c71c68f5873c167b8d9" -dependencies = [ - "ahash", - "indexmap 2.2.6", - "is-terminal", - "itoa", - "log", - "num-format", - "once_cell", - "quick-xml", - "rgb", - "str_stack", -] - -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "block-padding", - "generic-array", -] - -[[package]] -name = "is-terminal" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "js-sys" -version = "0.3.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "libc" -version = "0.2.155" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" - -[[package]] -name = "libloading" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" -dependencies = [ - "cfg-if", - "windows-targets 0.52.6", -] - -[[package]] -name = "libsql" -version = "0.9.29" -dependencies = [ - "anyhow", - "async-stream", - "async-trait", - "base64 0.21.7", - "bincode", - "bitflags 2.6.0", - "bytes", - "chrono", - "crc32fast", - "criterion", - "fallible-iterator 0.3.0", - "futures", - "http 0.2.12", - "hyper", - "hyper-rustls", - "libsql-hrana", - "libsql-sqlite3-parser", - "libsql-sys", - "libsql_replication", - "parking_lot", - "pprof", - "rand", - "serde", - "serde_json", - "tempfile", - "thiserror", - "tokio", - "tokio-stream", - "tokio-test", - "tokio-util", - "tonic", - "tonic-web", - "tower", - "tower-http", - "tracing", - "tracing-subscriber", - "uuid", - "worker", - "zerocopy", -] - -[[package]] -name = "libsql-ffi" -version = "0.9.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cd1c1662822495393327856774f6803be25d85bfdcd5b9d4af35458f5daaf75" -dependencies = [ - "bindgen", - "cc", - "cmake", - "glob", -] - -[[package]] -name = "libsql-hrana" -version = "0.9.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "646d0aa75e412769018422f0da798f72e93bd51964f0b2ddad4317aa779ae444" -dependencies = [ - "base64 0.21.7", - "bytes", - "prost", - "serde", -] - -[[package]] -name = "libsql-rusqlite" -version = "0.9.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a4ce3a78c6e3c2b23b02ab6272df8340e1c53380497979d456882254f348d5f" -dependencies = [ - "bitflags 2.6.0", - "fallible-iterator 0.2.0", - "fallible-streaming-iterator", - "hashlink", - "libsql-ffi", - "smallvec", -] - -[[package]] -name = "libsql-sqlite3-parser" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15a90128c708356af8f7d767c9ac2946692c9112b4f74f07b99a01a60680e413" -dependencies = [ - "bitflags 2.6.0", - "cc", - "fallible-iterator 0.3.0", - "indexmap 2.2.6", - "log", - "memchr", - "phf", - "phf_codegen", - "phf_shared", - "uncased", -] - -[[package]] -name = "libsql-sys" -version = "0.9.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a3c326fcfc36fe7578238d5ee6b58c529f8c76372acd61ec50267529cdaff95" -dependencies = [ - "bytes", - "libsql-ffi", - "libsql-rusqlite", - "once_cell", - "tracing", - "zerocopy", -] - -[[package]] -name = "libsql_replication" -version = "0.9.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9a2e469ac8400659bd31f81a745908bcc5cb6b40be2f2ff8de90b15bec5501" -dependencies = [ - "aes", - "async-stream", - "async-trait", - "bytes", - "cbc", - "libsql-rusqlite", - "libsql-sys", - "parking_lot", - "prost", - "serde", - "thiserror", - "tokio", - "tokio-stream", - "tokio-util", - "tonic", - "tracing", - "uuid", - "zerocopy", -] - -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "memmap2" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" -dependencies = [ - "libc", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.48.0", -] - -[[package]] -name = "nix" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - -[[package]] -name = "num-format" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" -dependencies = [ - "arrayvec", - "itoa", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "object" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "oorandom" -version = "11.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "phf" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" -dependencies = [ - "phf_shared", - "rand", -] - -[[package]] -name = "phf_shared" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" -dependencies = [ - "siphasher", - "uncased", -] - -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "plotters" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" - -[[package]] -name = "plotters-svg" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" -dependencies = [ - "plotters-backend", -] - -[[package]] -name = "pprof" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebbe2f8898beba44815fdc9e5a4ae9c929e21c5dc29b0c774a15555f7f58d6d0" -dependencies = [ - "aligned-vec", - "backtrace", - "cfg-if", - "criterion", - "findshlibs", - "inferno", - "libc", - "log", - "nix", - "once_cell", - "parking_lot", - "smallvec", - "symbolic-demangle", - "tempfile", - "thiserror", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "prettyplease" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" -dependencies = [ - "proc-macro2", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prost" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-derive" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" -dependencies = [ - "anyhow", - "itertools 0.12.1", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "quick-xml" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" -dependencies = [ - "memchr", -] - -[[package]] -name = "quote" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" -dependencies = [ - "bitflags 2.6.0", -] - -[[package]] -name = "regex" -version = "1.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" - -[[package]] -name = "rgb" -version = "0.8.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aee83dc281d5a3200d37b299acd13b81066ea126a7f16f0eae70fc9aed241d9" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "ring" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" -dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustix" -version = "0.38.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" -dependencies = [ - "bitflags 2.6.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" -dependencies = [ - "log", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88d6d420651b496bdd98684116959239430022a115c1240e6c3993be0b15fba" -dependencies = [ - "openssl-probe", - "rustls-pemfile", - "rustls-pki-types", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pemfile" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" -dependencies = [ - "base64 0.22.1", - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" - -[[package]] -name = "rustls-webpki" -version = "0.102.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "security-framework" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" -dependencies = [ - "bitflags 2.6.0", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "serde" -version = "1.0.204" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde-wasm-bindgen" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - -[[package]] -name = "serde-wasm-bindgen" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - -[[package]] -name = "serde_derive" -version = "1.0.204" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.120" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "socket2" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "str_stack" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "symbolic-common" -version = "12.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71297dc3e250f7dbdf8adb99e235da783d690f5819fdeb4cce39d9cfb0aca9f1" -dependencies = [ - "debugid", - "memmap2", - "stable_deref_trait", - "uuid", -] - -[[package]] -name = "symbolic-demangle" -version = "12.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "424fa2c9bf2c862891b9cfd354a752751a6730fd838a4691e7f6c2c7957b9daf" -dependencies = [ - "cpp_demangle", - "rustc-demangle", - "symbolic-common", -] - -[[package]] -name = "syn" -version = "2.0.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - -[[package]] -name = "tempfile" -version = "3.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" -dependencies = [ - "cfg-if", - "fastrand", - "rustix", - "windows-sys 0.52.0", -] - -[[package]] -name = "thiserror" -version = "1.0.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "tinyvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "num_cpus", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.48.0", -] - -[[package]] -name = "tokio-io-timeout" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" -dependencies = [ - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-macros" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-rustls" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" -dependencies = [ - "rustls", - "rustls-pki-types", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-test" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" -dependencies = [ - "async-stream", - "bytes", - "futures-core", - "tokio", - "tokio-stream", -] - -[[package]] -name = "tokio-util" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tonic" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" -dependencies = [ - "async-stream", - "async-trait", - "axum", - "base64 0.21.7", - "bytes", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "hyper", - "hyper-timeout", - "percent-encoding", - "pin-project", - "prost", - "tokio", - "tokio-stream", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tonic-web" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc3b0e1cedbf19fdfb78ef3d672cb9928e0a91a9cb4629cc0c916e8cff8aaaa1" -dependencies = [ - "base64 0.21.7", - "bytes", - "http 0.2.12", - "http-body 0.4.6", - "hyper", - "pin-project", - "tokio-stream", - "tonic", - "tower-http", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "indexmap 1.9.3", - "pin-project", - "pin-project-lite", - "rand", - "slab", - "tokio", - "tokio-util", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-http" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" -dependencies = [ - "bitflags 2.6.0", - "bytes", - "futures-core", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "http-range-header", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - -[[package]] -name = "tracing" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" -dependencies = [ - "nu-ansi-term", - "sharded-slab", - "smallvec", - "thread_local", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "uncased" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "uuid" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" -dependencies = [ - "getrandom", - "serde", -] - -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-streams" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "web-sys" -version = "0.3.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "worker" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ed0536ade2f3c10aba6a145dfb86d573f33eed53d31e5eb6a5261dc3b4ad0d" -dependencies = [ - "async-trait", - "bytes", - "chrono", - "futures-channel", - "futures-util", - "http 1.1.0", - "http-body 1.0.0", - "js-sys", - "matchit", - "pin-project", - "serde", - "serde-wasm-bindgen 0.6.5", - "serde_json", - "serde_urlencoded", - "tokio", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "worker-kv", - "worker-macros", - "worker-sys", -] - -[[package]] -name = "worker-kv" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f06d4d1416a9f8346ee9123b0d9a11b3cfa38e6cfb5a139698017d1597c4d41" -dependencies = [ - "js-sys", - "serde", - "serde-wasm-bindgen 0.5.0", - "serde_json", - "thiserror", - "wasm-bindgen", - "wasm-bindgen-futures", -] - -[[package]] -name = "worker-macros" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd906c1826d2e6b782549ae0fab4c3835473cd8e0dc86467333a3046764e04d7" -dependencies = [ - "async-trait", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-bindgen-macro-support", - "worker-sys", -] - -[[package]] -name = "worker-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ebe5b036881fbb97ea517e10720ce1704d331a0afc1dfcea7d261103af004f" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/vendor/libsql/Cargo.toml b/vendor/libsql/Cargo.toml deleted file mode 100644 index 8a7c09b1..00000000 --- a/vendor/libsql/Cargo.toml +++ /dev/null @@ -1,394 +0,0 @@ -# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO -# -# When uploading crates to the registry Cargo will automatically -# "normalize" Cargo.toml files for maximal compatibility -# with all versions of Cargo and also rewrite `path` dependencies -# to registry (e.g., crates.io) dependencies. -# -# If you are reading this file be aware that the original Cargo.toml -# will likely look very different (and much more reasonable). -# See Cargo.toml.orig for the original contents. - -[package] -edition = "2021" -name = "libsql" -version = "0.9.29" -authors = ["the libSQL authors"] -build = false -autolib = false -autobins = false -autoexamples = false -autotests = false -autobenches = false -description = "The libSQL database library" -readme = "README.md" -license = "MIT" -repository = "https://github.com/tursodatabase/libsql" - -[package.metadata.cargo-udeps.ignore] -normal = ["hyper-rustls"] - -[package.metadata.docs.rs] -rustdoc-args = [ - "--cfg", - "docsrs", -] - -[features] -cloudflare = [ - "wasm", - "dep:worker", -] -core = [ - "libsql-sys", - "dep:bitflags", - "dep:bytes", - "dep:futures", - "dep:parking_lot", -] -default = [ - "core", - "replication", - "remote", - "sync", - "tls", -] -encryption = [ - "core", - "libsql-sys/encryption", - "dep:bytes", -] -hrana = [ - "parser", - "serde", - "stream", - "dep:base64", - "dep:serde_json", - "dep:futures", - "dep:tokio", - "dep:tokio-util", - "dep:bytes", - "dep:libsql-hrana", -] -parser = [ - "dep:sqlite3-parser", - "dep:fallible-iterator", -] -remote = [ - "hrana", - "dep:base64", - "dep:tower", - "dep:hyper", - "dep:hyper", - "dep:http", - "dep:tokio", - "dep:futures", - "dep:bitflags", -] -replication = [ - "core", - "parser", - "serde", - "stream", - "dep:tower", - "dep:hyper", - "dep:http", - "dep:tokio", - "dep:anyhow", - "dep:bincode", - "dep:zerocopy", - "dep:bytes", - "dep:uuid", - "dep:tokio-stream", - "dep:parking_lot", - "dep:tokio", - "dep:tonic", - "dep:tonic-web", - "dep:tower-http", - "dep:futures", - "dep:libsql_replication", -] -serde = ["dep:serde"] -stream = [ - "dep:futures", - "dep:async-stream", -] -sync = [ - "core", - "parser", - "serde", - "stream", - "remote", - "replication", - "dep:base64", - "dep:tower", - "dep:hyper", - "dep:http", - "dep:tokio", - "dep:zerocopy", - "dep:bytes", - "dep:tokio", - "dep:futures", - "dep:serde_json", - "dep:crc32fast", - "dep:chrono", - "dep:uuid", - "tokio/fs", -] -tls = ["dep:hyper-rustls"] -wasm = ["hrana"] - -[lib] -name = "libsql" -path = "src/lib.rs" - -[[example]] -name = "deserialization" -path = "examples/deserialization.rs" - -[[example]] -name = "encryption_local" -path = "examples/encryption_local.rs" - -[[example]] -name = "encryption_sync" -path = "examples/encryption_sync.rs" - -[[example]] -name = "example" -path = "examples/example.rs" - -[[example]] -name = "example_v2" -path = "examples/example_v2.rs" - -[[example]] -name = "flutter" -path = "examples/flutter.rs" - -[[example]] -name = "local_sync" -path = "examples/local_sync.rs" - -[[example]] -name = "offline_writes" -path = "examples/offline_writes.rs" - -[[example]] -name = "remote_sync" -path = "examples/remote_sync.rs" - -[[example]] -name = "replica" -path = "examples/replica.rs" - -[[example]] -name = "transaction" -path = "examples/transaction.rs" - -[[test]] -name = "encryption" -path = "tests/encryption.rs" - -[[test]] -name = "integration_tests" -path = "tests/integration_tests.rs" - -[[test]] -name = "replication" -path = "tests/replication.rs" - -[[bench]] -name = "benchmark" -path = "benches/benchmark.rs" -harness = false - -[dependencies.anyhow] -version = "1.0.71" -optional = true - -[dependencies.async-stream] -version = "0.3.5" -optional = true - -[dependencies.async-trait] -version = "0.1" - -[dependencies.base64] -version = "0.21" -optional = true - -[dependencies.bincode] -version = "1" -optional = true - -[dependencies.bitflags] -version = "2.4.0" -optional = true - -[dependencies.bytes] -version = "1.4.0" -features = ["serde"] -optional = true - -[dependencies.chrono] -version = "0.4" -optional = true - -[dependencies.crc32fast] -version = "1" -optional = true - -[dependencies.fallible-iterator] -version = "0.3" -optional = true - -[dependencies.futures] -version = "0.3.28" -optional = true - -[dependencies.http] -version = "0.2" -optional = true - -[dependencies.hyper] -version = "0.14" -features = [ - "client", - "http1", - "http2", - "stream", - "runtime", -] -optional = true - -[dependencies.hyper-rustls] -version = "0.25" -features = ["webpki-roots"] -optional = true - -[dependencies.libsql-hrana] -version = "0.9.29" -optional = true - -[dependencies.libsql-sys] -version = "0.9.29" -optional = true -default-features = true - -[dependencies.libsql_replication] -version = "0.9.29" -optional = true - -[dependencies.parking_lot] -version = "0.12.1" -optional = true - -[dependencies.serde] -version = "1" -features = ["derive"] -optional = true - -[dependencies.serde_json] -version = "1" -features = ["float_roundtrip"] -optional = true - -[dependencies.sqlite3-parser] -version = "0.13" -optional = true -package = "libsql-sqlite3-parser" - -[dependencies.thiserror] -version = "1.0.40" - -[dependencies.tokio] -version = "1.29.1" -features = ["sync"] -optional = true - -[dependencies.tokio-stream] -version = "0.1.14" -optional = true - -[dependencies.tokio-util] -version = "0.7" -features = [ - "io-util", - "codec", -] -optional = true - -[dependencies.tonic] -version = "0.11" -optional = true - -[dependencies.tonic-web] -version = "0.11" -optional = true - -[dependencies.tower] -version = "0.4.13" -features = ["util"] -optional = true - -[dependencies.tower-http] -version = "0.4.4" -features = [ - "trace", - "set-header", - "util", -] -optional = true - -[dependencies.tracing] -version = "0.1.37" -default-features = false - -[dependencies.uuid] -version = "1.4.0" -features = [ - "v4", - "serde", -] -optional = true - -[dependencies.worker] -version = "0.4.1" -optional = true - -[dependencies.zerocopy] -version = "0.7.28" -optional = true - -[dev-dependencies.criterion] -version = "0.5" -features = [ - "html_reports", - "async", - "async_futures", - "async_tokio", -] - -[dev-dependencies.pprof] -version = "0.14.0" -features = [ - "criterion", - "flamegraph", -] - -[dev-dependencies.rand] -version = "0.8.5" - -[dev-dependencies.tempfile] -version = "3.7.0" - -[dev-dependencies.tokio] -version = "1.29.1" -features = ["full"] - -[dev-dependencies.tokio-test] -version = "0.4" - -[dev-dependencies.tracing-subscriber] -version = "0.3" diff --git a/vendor/libsql/Cargo.toml.orig b/vendor/libsql/Cargo.toml.orig deleted file mode 100644 index 2fce342a..00000000 --- a/vendor/libsql/Cargo.toml.orig +++ /dev/null @@ -1,161 +0,0 @@ -[package] -name = "libsql" -version.workspace = true -authors.workspace = true -edition.workspace = true -license.workspace = true -repository.workspace = true -description = "The libSQL database library" - -[dependencies] -tracing = { version = "0.1.37", default-features = false } -thiserror = "1.0.40" - -futures = { version = "0.3.28", optional = true } -libsql-sys = { workspace = true, optional = true, default-features = true } -libsql-hrana = { workspace = true, optional = true } -tokio = { version = "1.29.1", features = ["sync"], optional = true } -tokio-util = { version = "0.7", features = ["io-util", "codec"], optional = true } -parking_lot = { version = "0.12.1", optional = true } -hyper = { version = "0.14", features = ["client", "http1", "http2", "stream", "runtime"], optional = true } -hyper-rustls = { version = "0.25", features = ["webpki-roots"], optional = true } -base64 = { version = "0.21", optional = true } -serde = { version = "1", features = ["derive"], optional = true } -serde_json = { version = "1", features = ["float_roundtrip"], optional = true } -async-trait = "0.1" -bitflags = { version = "2.4.0", optional = true } -tower = { workspace = true, features = ["util"], optional = true } -worker = { version = "0.4.1", optional = true } - -bincode = { version = "1", optional = true } -anyhow = { version = "1.0.71", optional = true } -bytes = { version = "1.4.0", features = ["serde"], optional = true } -uuid = { version = "1.4.0", features = ["v4", "serde"], optional = true } -tokio-stream = { version = "0.1.14", optional = true } -tonic = { version = "0.11", optional = true} -tonic-web = { version = "0.11", optional = true } -tower-http = { version = "0.4.4", features = ["trace", "set-header", "util"], optional = true } -http = { version = "0.2", optional = true } -zerocopy = { version = "0.7.28", optional = true } - -sqlite3-parser = { package = "libsql-sqlite3-parser", path = "../vendored/sqlite3-parser", version = "0.13", optional = true } -fallible-iterator = { version = "0.3", optional = true } - -libsql_replication = { workspace = true, optional = true } -async-stream = { version = "0.3.5", optional = true } - -crc32fast = { version = "1", optional = true } -chrono = { version = "0.4", optional = true } - -[dev-dependencies] -criterion = { version = "0.5", features = ["html_reports", "async", "async_futures", "async_tokio"] } -pprof = { version = "0.14.0", features = ["criterion", "flamegraph"] } -tokio = { version = "1.29.1", features = ["full"] } -tokio-test = "0.4" -tracing-subscriber = "0.3" -tempfile = { version = "3.7.0" } -rand = "0.8.5" - -[features] -default = ["core", "replication", "remote", "sync", "tls"] -core = [ - "libsql-sys", - "dep:bitflags", - "dep:bytes", - "dep:futures", - "dep:parking_lot", -] -stream = [ - "dep:futures", - "dep:async-stream" -] -parser = [ - "dep:sqlite3-parser", - "dep:fallible-iterator" -] -replication = [ - "core", - "parser", - "serde", - "stream", - "dep:tower", - "dep:hyper", - "dep:http", - "dep:tokio", - "dep:anyhow", - "dep:bincode", - "dep:zerocopy", - "dep:bytes", - "dep:uuid", - "dep:tokio-stream", - "dep:parking_lot", - "dep:tokio", - "dep:tonic", - "dep:tonic-web", - "dep:tower-http", - "dep:futures", - "dep:libsql_replication", -] -sync = [ - "core", - "parser", - "serde", - "stream", - "remote", - "replication", - "dep:base64", - "dep:tower", - "dep:hyper", - "dep:http", - "dep:tokio", - "dep:zerocopy", - "dep:bytes", - "dep:tokio", - "dep:futures", - "dep:serde_json", - "dep:crc32fast", - "dep:chrono", - "dep:uuid", - "tokio/fs" -] -hrana = [ - "parser", - "serde", - "stream", - "dep:base64", - "dep:serde_json", - "dep:futures", - "dep:tokio", - "dep:tokio-util", - "dep:bytes", - "dep:libsql-hrana", -] -serde = ["dep:serde"] -remote = [ - "hrana", - "dep:base64", - "dep:tower", - "dep:hyper", - "dep:hyper", - "dep:http", - "dep:tokio", - "dep:futures", - "dep:bitflags", -] -wasm = ["hrana"] -cloudflare = [ - "wasm", - "dep:worker" -] -encryption = ["core", "libsql-sys/encryption", "dep:bytes"] -tls = ["dep:hyper-rustls"] - -[[bench]] -name = "benchmark" -harness = false - -[package.metadata.docs.rs] -rustdoc-args = ["--cfg", "docsrs"] - -[package.metadata.cargo-udeps.ignore] -normal = ["hyper-rustls"] diff --git a/vendor/libsql/DEVELOPING.md b/vendor/libsql/DEVELOPING.md deleted file mode 100644 index dccc2e81..00000000 --- a/vendor/libsql/DEVELOPING.md +++ /dev/null @@ -1,32 +0,0 @@ -# Developing libSQL API for Rust - -Setting up the environment: - -```sh -export LIBSQL_STATIC_LIB_DIR=$(pwd)/../../.libs -``` - -Building the APIs: - -```sh -cargo build -``` - -Running the tests: - -```sh -cargo test -``` - -Running the benchmarks: - -```sh -cargo bench -``` - -Run benchmarks and generate flamegraphs: - -```console -echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid -cargo bench --bench benchmark -- --profile-time=5 -``` diff --git a/vendor/libsql/LICENSE.md b/vendor/libsql/LICENSE.md deleted file mode 100644 index fd1a31ee..00000000 --- a/vendor/libsql/LICENSE.md +++ /dev/null @@ -1,20 +0,0 @@ -MIT License - -Copyright 2023 the sqld authors - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/libsql/README.md b/vendor/libsql/README.md deleted file mode 100644 index 76947e03..00000000 --- a/vendor/libsql/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# libSQL API for Rust - -[![Crates.io][crates-badge]][crates-url] -[![MIT licensed][mit-badge]][mit-url] - -[crates-badge]: https://img.shields.io/crates/v/libsql.svg -[crates-url]: https://crates.io/crates/libsql -[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg -[mit-url]: https://github.com/libsql/libsql/blob/main/LICENSE.md - -This repository contains the libSQL API for Rust. - -## Developing - -See [DEVELOPING.md](DEVELOPING.md) for more information. - -## License - -This project is licensed under the [MIT license]. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in libSQL by you, shall be licensed as MIT, without any additional -terms or conditions. - -[MIT license]: https://github.com/libsql/libsql/blob/main/LICENSE.md diff --git a/vendor/libsql/TEMPER-PATCH.md b/vendor/libsql/TEMPER-PATCH.md deleted file mode 100644 index 37a139d7..00000000 --- a/vendor/libsql/TEMPER-PATCH.md +++ /dev/null @@ -1,22 +0,0 @@ -# libSQL 0.9.29 connection lifetime - -Source: https://crates.io/crates/libsql/0.9.29 - -Published archive SHA-256: 2329faffc510cc3c6b4f00169a39177cc7099d3ed7647fc92f7cf26e53a8d976. - -The source is the published crate. The only Rust source change removes the redundant LibsqlConnection Drop implementation in src/local/impls.rs. Its inner Connection already closes the native handle. The registry's .cargo-ok cache marker is omitted. LICENSE.md is copied from the upstream release commit. - -Regression: crates/temper-store-turso/tests/connection_lifetime.rs in the Temper workspace. Rationale: docs/adrs/0175-libsql-connection-lifetime.md. - -The storage crate uses a direct path dependency so consumers of Temper also use this fixed source; no root-only Cargo patch is required. - -This provenance document and TEMPER-SHA256SUMS are additional local files. The -checksum manifest covers every retained upstream file, the license and this -document. scripts/check-vendored-libsql.py rejects changed, missing or additional -files in CI. Intentional vendor updates must refresh the manifest and review the -source difference against the published archive above. - -The fixed source must remain with every distributed Temper checkout. The storage -crate refuses registry publication: Cargo would otherwise replace its path with -the unpatched upstream release. Registry distribution requires a published fixed -dependency before that restriction can be removed. diff --git a/vendor/libsql/TEMPER-SHA256SUMS b/vendor/libsql/TEMPER-SHA256SUMS deleted file mode 100644 index 503704dd..00000000 --- a/vendor/libsql/TEMPER-SHA256SUMS +++ /dev/null @@ -1,71 +0,0 @@ -1463bf3f983a19433e6b37f9cdc9624e7f67fbcba1300f740a80b47f8cb3482b .cargo_vcs_info.json -c254c93bd7caf0328bc8770cc8b2ecbfb2e4df79f92ac27791ade4ef4653e52c Cargo.lock -56ea11ea9602652bb3ddf45fc3d69e463633e70b9a5dadc8cb6a74b259a4a25f Cargo.toml -304014d71ec0984cf6db9a92d83b1f78b7ab86643156dcbb611ca405a9f6f9eb Cargo.toml.orig -c2254784d244e3def241668dd2a0423d552d6e14cad5b8ff3a89926f6aa4b1fc DEVELOPING.md -cecf589818a56c2da8a0a6cc82c8a215c9770988c033b6016c2b74bf79f38152 LICENSE.md -e5ca11f6504282ed3473a6de6a85e02556390d1f27dda427a2279791b7752a71 README.md -cbd917784ac2f2ea68b49b3390272ad498985b702b458bce7e1127c296cf79d8 TEMPER-PATCH.md -38a7a4f42b2da722093324657491246e04946ea24635a37483a1543561f392a7 assets/test/snapshot.snap -985e2a4e3693910adfeb205e3c0844f7b53b0053d0c38103934805dec7b9df75 benches/benchmark.rs -2e8e417a285e470b6f51e6e99086c2cf19d22892df0cba6dd3dd189f56a9cc7c examples/deserialization.rs -803f7ffdfe1abe86b86cabd363e78d9b4c585aac4ca5cdc9c5e21e98b85a7f60 examples/encryption_local.rs -66b6f68f4b443bd6d03ea9326e7900203f7b985b93fc9af35141a9df2c78a6c2 examples/encryption_sync.rs -c8de90e50bf4600581066edd38a9f4ea065db72f1bff8b799089177ea5a55f08 examples/example.rs -a04eb0f5cf384f02d63f80de47c828d8e9a042cfc5f77eb3bb3085c645c625f6 examples/example_v2.rs -71bd46ae7ee58bac6bd39ab0e501ced21c47d9a1abec2c89a14359e182d77e41 examples/flutter.rs -605d20d6d83f246316fdab781ed9099e6d40a2402c0697717973cca153b0480f examples/local_sync.rs -9424ebade6a39ee06e0d745050cfd26f8015c0c5ca97e9f211ca8fd496cbc7ff examples/offline_writes.rs -fafee133325653d2881607ecd2ac5d50c665d6b7d4206f305d76957be33faa02 examples/remote_sync.rs -73b31e67424b41b9c0b6a7801890d5fa4edb2b8d280cc3007054aab4c4a4c795 examples/replica.rs -0a7935c05dcba154bfd7d46dbb6ae6e4d9af3e47323243770199a13266044cf3 examples/transaction.rs -026f437413552ae2f2d6bd696f9e6a918330858d83a159398b544387e220f993 src/auth.rs -c0d2521854ebb97c216871fd07d4faddc71175a5403ecfd84acdd8aa6eda8ec8 src/connection.rs -016bef9b25c66bafbc51c28751d402f2e477d224a8fb34a2ea68ac1cc2e8b899 src/database/builder.rs -4491e0e52b1d0e88662d633075d9a60f7144c7a2cb52c5b4f3df10a6d488a26b src/database.rs -3d00cc32cf9f628ada837692e7e48db16ad46eae35dcf9514c06039e5de48105 src/de.rs -28c6eb8dbadffee82de46951968e149b96e027e4c69da4f07b2b7ecc10463734 src/errors.rs -db953e57433cc884751eb323172c83fc8f64b650cbc0c91610d53a789fa8b148 src/hrana/connection.rs -29d98cd8453145320aacad732654e44f7befedafc922285b0a83f354ac9c7080 src/hrana/cursor.rs -ab62495e9458fe3d44cb23424fc2fae7b5dcc6ccc7d2e20e6c1625ae2a45f560 src/hrana/hyper.rs -8d699af8a1c153ad7c38c5aff81b8b7dc182d3b5a1b85db3084f60c1546ee761 src/hrana/mod.rs -7a72ccb386c752a7278963d3adc872aaabbf24d9bd9151692ef88e66d5aad461 src/hrana/stream.rs -f60f6de73492192cafd68955cd22d9da9616f76692af92871d73c9c1ddc8dedb src/hrana/transaction.rs -85acb61fc42aa9860fae2109f17c23595b89e8ae0fcfda39b41aea853eb5f3c5 src/lib.rs -c3a3f4d1256d439480a19743e17a55f841462da7766c7cfceb0782304738ad3a src/load_extension_guard.rs -4b355fd4cd916e15cc0ffef028034eb22d041c63140acb9f4191ba2cd034f401 src/local/connection.rs -ee25b813ad6f4ec3b155733f1ad77fd17d4ca243231bc08318f37af64493d381 src/local/database.rs -1da002bf0a161c016eb02f50487eb41dc215aa3ee7400d43292b4e10733faacd src/local/impls.rs -58fa1c2defca31e2fee4fea0dcd39ad449b175c77d1d82a54f7857386505c90f src/local/mod.rs -bd66fc7fc496a23f9be1d66c99a30a413fbcfe23cf3f201c455238088c38db06 src/local/rows.rs -8ae4311682d291869310f5d273662b4a368c55ee0fa57241882b5eb6ed38059e src/local/statement.rs -f28a07aac6d0e1e1b8da097cb00e1fbb4afbb76376705a97e6ea28ee26168f75 src/local/transaction.rs -c3c1ee580aa276e95c22c43272f6e0032721d146b1dea912898d60209b22fdb1 src/macros.rs -f1ff5ead2894daf6cc0687f0400902003cb1d9f6abb7a58f62348807f3eeb3c3 src/params.rs -5997dfbe0bd72f23b116ac946909ddef072da8e082660871d129c88a7266e1fe src/parser.rs -f4465e648652dd2a944911b62ee2b133a61f8859451d7c2a5f7f5dc2ecae2d9a src/replication/client.rs -31ea981759195b485e839935fae3b77ca7f614f8844147f709eac5f9826711ec src/replication/connection.rs -2d8049d90080e7c4b8b530ed26d66ffaa4bb8ff69269a2d90aefd4badd526538 src/replication/local_client.rs -3584e827dafe507e0e0378ff71b40fbc0ca9c3731d1708f8861c12eb63b3a892 src/replication/mod.rs -9a3856aba43bb322d8089f4565ecd640505d4d2e2305ee358de16198215405bb src/replication/remote_client.rs -e2ad56df832e758a5b33038bbadf841a64c39e08474e8ffd266bed09d068ab2b src/rows.rs -5d5e5cabf6e3e1f6cfabbc6c7c302513f0f54120b2e6b3f78abcb38c3c54db51 src/statement.rs -2546875e4b3bba225c90213ac788ed2a7758d3cd3fbd1a76e0bb7d5e3150a050 src/sync/connection.rs -38a4a06add41e15fb14639186152e6da2124cbf240dbffe2cf18d3b729d58bba src/sync/statement.rs -82383737f91fe6e3492476625217aa028b9097fe0a9a6b56d993b92a932e28d7 src/sync/test.rs -d34544e189dfc826b92b1866d7ecab7f09083dcb27be018da9c09b06ca565127 src/sync/transaction.rs -539bec9e312891a68baf2638f0a3c8cf6a0cb7ce64180c2e0511d31b0b2a4e23 src/sync.rs -0c3ca190ab41e55e84a7f7b575d9bcc03d9ad39e0c04ac65e8765394728e27c3 src/transaction.rs -1c87d5e3829ed5141e9fe9a397aca5d28a797a03fda82130f4424a6b01ff3330 src/util/box_clone_service.rs -5861d0a8d21e44e186a6d68e39dbc641130a99f908a626f995e306aae278a42f src/util/http.rs -178d58915350eac757e2f4c1d27a152c273beba717794ca79d00ca73c7333bfa src/util/mod.rs -543fd48e33287eb87592b7b70c829dbdb9acbc9ebd65e9522458734cfa07c1cd src/value.rs -00a43bb5dfe6f08a5d83f33fe05018c79085ea599dee34baaf828aceaee12f18 src/wasm/cloudflare.rs -1dc8b00c266dfe0737fa2e132741693e36d25d17ca74d8ea6e3a742784f3211b src/wasm/mod.rs -234022b2c669048701caa22252c9fecf792a78c612f7217e64ea54ceadd73987 src/wasm/rows.rs -d9a0727ba117580b4bcbe794ba7281432249dc9c9595b42176c202b40eff9c49 tests/encryption.rs -07760cd9a9298114c28fce5129c71edf1f9d3a6724b4f1361deb4976ec886c06 tests/integration_tests.rs -3476776af2d4e02d69101e35324205f864fdc0793912371df62b6c801e108ffd tests/replication.rs -00d4b935fa63507872b5f7973e423ca830effe2bddcd350555b53937135920b8 tests/template.db -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 tests/template.db-wal -286f27ba3ba35005c54d44747cb79f9cb9d19956d51479a01641190e5619b6e8 tests/test.db diff --git a/vendor/libsql/assets/test/snapshot.snap b/vendor/libsql/assets/test/snapshot.snap deleted file mode 100644 index fb3b2876081a2a9672b6fd738008961cf475b286..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8288 zcmbQOk+Ypi-*mc=XT9pHf1IrhP{0VKnb2sMxWqRb@hOTE9l(YUKw$#X!UAGI0n7*(9~;fYpjWg2H1@#Ae~AHz zM!{$ZjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mk098UjhmlQORFu)SBrz!` hwIsE;1caHKgIpa$TopnboqSvsKq3knc{&PZngH~0BSQcH diff --git a/vendor/libsql/benches/benchmark.rs b/vendor/libsql/benches/benchmark.rs deleted file mode 100644 index 69905c9d..00000000 --- a/vendor/libsql/benches/benchmark.rs +++ /dev/null @@ -1,224 +0,0 @@ -#![allow(deprecated)] - -use criterion::{criterion_group, criterion_main, BatchSize, Criterion, Throughput}; -use libsql::Database; -use pprof::criterion::{Output, PProfProfiler}; -use tokio::runtime; - -fn open_in_memory() -> Database { - Database::open(":memory:").unwrap() -} - -async fn open_local_replica() -> Option { - let db_path = match std::env::var("DB_PATH") { - Ok(db_path) => db_path, - Err(_) => { - println!( - "The DB_PATH environment variable is not set, skipping local replica benchmarks." - ); - return None; - } - }; - let url = match std::env::var("URL") { - Ok(url) => url, - Err(_) => { - println!("The URL environment variable is not set, skipping local replica benchmarks."); - return None; - } - }; - let auth_token = match std::env::var("AUTH_TOKEN") { - Ok(auth_token) => auth_token, - Err(_) => { - println!("The AUTH_TOKEN environment variable is not set, skipping local replica benchmarks."); - return None; - } - }; - Some( - Database::open_with_remote_sync(db_path, url, auth_token, None) - .await - .unwrap(), - ) -} - -fn bench(c: &mut Criterion) { - let rt = runtime::Builder::new_current_thread() - .enable_time() - .enable_io() - .build() - .unwrap(); - - let mut group = c.benchmark_group("libsql"); - group.throughput(Throughput::Elements(1)); - - let db = open_in_memory(); - let conn = db.connect().unwrap(); - - group.bench_function("in-memory-select-1-unprepared", |b| { - b.to_async(&rt).iter(|| async { - let mut rows = conn.query("SELECT 1", ()).await.unwrap(); - let row = rows.next().await.unwrap().unwrap(); - assert_eq!(row.get::(0).unwrap(), 1); - }); - }); - - // Extremely hacky block_on - // - // Why do we need it? - // - // criterion's async bencher enters the runtime for the setup - // but does not allow us to actually execute anything on that - // runtime because the setup Fn doesn't get a future as the - // return value. So one might say, why not use `rt.block_on` - // well, tokio stops you from embedded runtimes within a runtime - // because this can lead to bad things (deadlocks!). So that means - // we need to find a way to run the prepare future without embedding - // the tokio runtime. - // - // The solution is to be hacky! From the code when using the libsql - // version of the api we know that there isn't actually any async work - // done and that the future always returns right away. Using this we can - // mock poll the future via `tokio_test::task::spawn` and extract the return - // value without actually creating any runtime. This works for now but may - // break in the future in weird ways. - fn block_on, R>(f: F) -> R { - let mut task = tokio_test::task::spawn(f); - - if let std::task::Poll::Ready(r) = task.poll() { - r - } else { - panic!() - } - } - - group.bench_function("in-memory-select-1-prepared", |b| { - b.to_async(&rt).iter_batched( - || block_on(conn.prepare("SELECT 1")).unwrap(), - |stmt| async move { - let mut rows = stmt.query(()).await.unwrap(); - let row = rows.next().await.unwrap().unwrap(); - assert_eq!(row.get::(0).unwrap(), 1); - stmt.reset(); - }, - BatchSize::SmallInput, - ); - }); - - rt.block_on(conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)", ())) - .unwrap(); - for _ in 0..1000 { - rt.block_on(conn.execute("INSERT INTO users (name) VALUES ('FOO')", ())) - .unwrap(); - } - - group.bench_function("in-memory-select-star-from-users-limit-1-unprepared", |b| { - b.to_async(&rt).iter_batched( - || block_on(conn.prepare("SELECT * FROM users LIMIT 1")).unwrap(), - |stmt| async move { - let mut rows = stmt.query(()).await.unwrap(); - let row = rows.next().await.unwrap().unwrap(); - assert_eq!(row.get::(0).unwrap(), 1); - stmt.reset(); - }, - BatchSize::SmallInput, - ); - }); - - group.bench_function( - "in-memory-select-star-from-users-limit-100-unprepared", - |b| { - b.to_async(&rt).iter_batched( - || block_on(conn.prepare("SELECT * FROM users LIMIT 100")).unwrap(), - |stmt| async move { - let mut rows = stmt.query(()).await.unwrap(); - let row = rows.next().await.unwrap().unwrap(); - assert_eq!(row.get::(0).unwrap(), 1); - stmt.reset(); - }, - BatchSize::SmallInput, - ); - }, - ); - - let db = match rt.block_on(open_local_replica()) { - Some(db) => db, - None => return, - }; - let conn = db.connect().unwrap(); - - group.bench_function("local-replica-select-1-unprepared", |b| { - b.to_async(&rt).iter(|| async { - let mut rows = conn.query("SELECT 1", ()).await.unwrap(); - let row = rows.next().await.unwrap().unwrap(); - assert_eq!(row.get::(0).unwrap(), 1); - }); - }); - - group.bench_function("local-replica-select-1-prepared", |b| { - b.to_async(&rt).iter_batched( - || block_on(conn.prepare("SELECT 1")).unwrap(), - |stmt| async move { - let mut rows = stmt.query(()).await.unwrap(); - let row = rows.next().await.unwrap().unwrap(); - assert_eq!(row.get::(0).unwrap(), 1); - stmt.reset(); - }, - BatchSize::SmallInput, - ); - }); - - rt.block_on(conn.execute("DROP TABLE IF EXISTS users", ())) - .unwrap(); - - rt.block_on(db.sync()).unwrap(); - - rt.block_on(conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)", ())) - .unwrap(); - - rt.block_on(db.sync()).unwrap(); - - for _ in 0..1000 { - rt.block_on(conn.execute("INSERT INTO users (name) VALUES ('FOO')", ())) - .unwrap(); - } - - rt.block_on(db.sync()).unwrap(); - - group.bench_function( - "local-replica-select-star-from-users-limit-1-unprepared", - |b| { - b.to_async(&rt).iter_batched( - || block_on(conn.prepare("SELECT * FROM users LIMIT 1")).unwrap(), - |stmt| async move { - let mut rows = stmt.query(()).await.unwrap(); - let row = rows.next().await.unwrap().unwrap(); - assert_eq!(row.get::(0).unwrap(), 1); - stmt.reset(); - }, - BatchSize::SmallInput, - ); - }, - ); - - group.bench_function( - "local-replica-select-star-from-users-limit-100-unprepared", - |b| { - b.to_async(&rt).iter_batched( - || block_on(conn.prepare("SELECT * FROM users LIMIT 100")).unwrap(), - |stmt| async move { - let mut rows = stmt.query(()).await.unwrap(); - let row = rows.next().await.unwrap().unwrap(); - assert_eq!(row.get::(0).unwrap(), 1); - stmt.reset(); - }, - BatchSize::SmallInput, - ); - }, - ); -} - -criterion_group! { - name = benches; - config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); - targets = bench -} -criterion_main!(benches); diff --git a/vendor/libsql/examples/deserialization.rs b/vendor/libsql/examples/deserialization.rs deleted file mode 100644 index b1c8ce47..00000000 --- a/vendor/libsql/examples/deserialization.rs +++ /dev/null @@ -1,58 +0,0 @@ -use libsql::{de, Builder}; - -#[tokio::main] -async fn main() { - let db = if let Ok(url) = std::env::var("LIBSQL_URL") { - let token = std::env::var("LIBSQL_AUTH_TOKEN").unwrap_or_else(|_| { - println!("LIBSQL_TOKEN not set, using empty token..."); - "".to_string() - }); - - Builder::new_remote(url, token).build().await.unwrap() - } else { - Builder::new_local(":memory:").build().await.unwrap() - }; - - let conn = db.connect().unwrap(); - - conn.execute( - "CREATE TABLE users (name TEXT, age INTEGER, vision FLOAT, avatar BLOB)", - (), - ) - .await - .unwrap(); - - let stmt = conn - .prepare("INSERT INTO users (name, age, vision, avatar) VALUES (?1, ?2, ?3, ?4)") - .await - .unwrap(); - stmt.execute(("Ferris the Crab", 8, -6.5, vec![1, 2, 3])) - .await - .unwrap(); - - let stmt = conn - .prepare("SELECT * FROM users WHERE name = ?1") - .await - .unwrap(); - let row = stmt - .query(["Ferris the Crab"]) - .await - .unwrap() - .next() - .await - .unwrap() - .unwrap(); - - #[derive(Debug, serde::Deserialize)] - #[allow(dead_code)] - struct User { - name: String, - age: i64, - vision: f64, - avatar: Vec, - } - - let user = de::from_row::(&row).unwrap(); - - println!("User: {:?}", user); -} diff --git a/vendor/libsql/examples/encryption_local.rs b/vendor/libsql/examples/encryption_local.rs deleted file mode 100644 index 8b03cc0d..00000000 --- a/vendor/libsql/examples/encryption_local.rs +++ /dev/null @@ -1,72 +0,0 @@ -// Example of showing using an encrypted local database with libsql. It also shows how to -// attach another encrypted database. The example expects a local `world.db` encrypted database -// to be present in the same directory. - -use libsql::{params, Builder}; -use libsql::{Cipher, EncryptionConfig}; - -#[tokio::main] -async fn main() { - tracing_subscriber::fmt::init(); - - // The local database path where the data will be stored. - let db_path = std::env::var("LIBSQL_DB_PATH").unwrap(); - // The encryption key for the database. - let encryption_key = std::env::var("LIBSQL_ENCRYPTION_KEY").unwrap_or("s3cR3t".to_string()); - - let mut db_builder = Builder::new_local(db_path); - - db_builder = db_builder.encryption_config(EncryptionConfig { - cipher: Cipher::Aes256Cbc, - encryption_key: encryption_key.into(), - }); - - let db = db_builder.build().await.unwrap(); - let conn = db.connect().unwrap(); - conn.execute( - "CREATE TABLE IF NOT EXISTS guest_book_entries (text TEXT)", - (), - ) - .await - .unwrap(); - - // let's attach another encrypted database and print its contents - conn.execute("ATTACH DATABASE 'world.db' AS world KEY s3cR3t", ()) - .await - .unwrap(); - - let mut attached_results = conn - .query("SELECT * FROM world.guest_book_entries", ()) - .await - .unwrap(); - - println!("attached database guest book entries:"); - while let Some(row) = attached_results.next().await.unwrap() { - let text: String = row.get(0).unwrap(); - println!(" {}", text); - } - - let mut input = String::new(); - println!("Please write your entry to the guestbook:"); - match std::io::stdin().read_line(&mut input) { - Ok(_) => { - println!("You entered: {}", input); - let params = params![input.as_str()]; - conn.execute("INSERT INTO guest_book_entries (text) VALUES (?)", params) - .await - .unwrap(); - } - Err(error) => { - eprintln!("Error reading input: {}", error); - } - } - let mut results = conn - .query("SELECT * FROM guest_book_entries", ()) - .await - .unwrap(); - println!("Guest book entries:"); - while let Some(row) = results.next().await.unwrap() { - let text: String = row.get(0).unwrap(); - println!(" {}", text); - } -} diff --git a/vendor/libsql/examples/encryption_sync.rs b/vendor/libsql/examples/encryption_sync.rs deleted file mode 100644 index 8159b3c5..00000000 --- a/vendor/libsql/examples/encryption_sync.rs +++ /dev/null @@ -1,86 +0,0 @@ -// Example of using offline writes with encryption - -use libsql::{params, Builder}; -use libsql::{EncryptionContext, EncryptionKey}; - -#[tokio::main] -async fn main() { - tracing_subscriber::fmt::init(); - - // The local database path where the data will be stored. - let db_path = std::env::var("LIBSQL_DB_PATH").unwrap(); - - // The remote sync URL to use. - let sync_url = std::env::var("LIBSQL_SYNC_URL").unwrap(); - - // The authentication token for the remote sync server. - let auth_token = std::env::var("LIBSQL_AUTH_TOKEN").unwrap_or("".to_string()); - - // Optional encryption key for the database, if provided. - let encryption = if let Ok(key) = std::env::var("LIBSQL_ENCRYPTION_KEY") { - Some(EncryptionContext { - key: EncryptionKey::Base64Encoded(key), - }) - } else { - None - }; - - let mut db_builder = Builder::new_synced_database(db_path, sync_url, auth_token); - - if let Some(enc) = encryption { - db_builder = db_builder.remote_encryption(enc); - } - - let db = match db_builder.build().await { - Ok(db) => db, - Err(error) => { - eprintln!("Error connecting to remote sync server: {}", error); - return; - } - }; - - let conn = db.connect().unwrap(); - - print!("Syncing with remote database..."); - db.sync().await.unwrap(); - println!(" done"); - - let mut results = conn.query("SELECT count(*) FROM dummy", ()).await.unwrap(); - let count: u32 = results.next().await.unwrap().unwrap().get(0).unwrap(); - println!("dummy table has {} entries", count); - - conn.execute( - r#" - CREATE TABLE IF NOT EXISTS guest_book_entries ( - text TEXT - )"#, - (), - ) - .await - .unwrap(); - - let mut input = String::new(); - println!("Please write your entry to the guestbook:"); - match std::io::stdin().read_line(&mut input) { - Ok(_) => { - println!("You entered: {}", input); - let params = params![input.as_str()]; - conn.execute("INSERT INTO guest_book_entries (text) VALUES (?)", params) - .await - .unwrap(); - } - Err(error) => { - eprintln!("Error reading input: {}", error); - } - } - db.sync().await.unwrap(); - let mut results = conn - .query("SELECT * FROM guest_book_entries", ()) - .await - .unwrap(); - println!("Guest book entries:"); - while let Some(row) = results.next().await.unwrap() { - let text: String = row.get(0).unwrap(); - println!(" {}", text); - } -} diff --git a/vendor/libsql/examples/example.rs b/vendor/libsql/examples/example.rs deleted file mode 100644 index 1416d550..00000000 --- a/vendor/libsql/examples/example.rs +++ /dev/null @@ -1,43 +0,0 @@ -use libsql::Builder; - -#[tokio::main] -async fn main() { - let db = if let Ok(url) = std::env::var("LIBSQL_URL") { - let token = std::env::var("LIBSQL_AUTH_TOKEN").unwrap_or_else(|_| { - println!("LIBSQL_TOKEN not set, using empty token..."); - "".to_string() - }); - - Builder::new_remote(url, token).build().await.unwrap() - } else { - Builder::new_local(":memory:").build().await.unwrap() - }; - - let conn = db.connect().unwrap(); - - conn.query("select 1; select 1;", ()).await.unwrap(); - - conn.execute("CREATE TABLE IF NOT EXISTS users (email TEXT)", ()) - .await - .unwrap(); - - let stmt = conn - .prepare("INSERT INTO users (email) VALUES (?1)") - .await - .unwrap(); - - stmt.execute(["foo@example.com"]).await.unwrap(); - - let stmt = conn - .prepare("SELECT * FROM users WHERE email = ?1") - .await - .unwrap(); - - let mut rows = stmt.query(["foo@example.com"]).await.unwrap(); - - let row = rows.next().await.unwrap().unwrap(); - - let value = row.get_value(0).unwrap(); - - println!("Row: {:?}", value); -} diff --git a/vendor/libsql/examples/example_v2.rs b/vendor/libsql/examples/example_v2.rs deleted file mode 100644 index 85815d3d..00000000 --- a/vendor/libsql/examples/example_v2.rs +++ /dev/null @@ -1,43 +0,0 @@ -use libsql::Builder; - -#[tokio::main] -async fn main() { - let db = if let Ok(url) = std::env::var("LIBSQL_HRANA_URL") { - let token = std::env::var("TURSO_AUTH_TOKEN").unwrap_or_else(|_| { - println!("TURSO_AUTH_TOKEN not set, using empty token..."); - "".to_string() - }); - - Builder::new_remote(url, token).build().await.unwrap() - } else { - Builder::new_local(":memory:").build().await.unwrap() - }; - - let conn = db.connect().unwrap(); - - conn.query("select 1; select 1;", ()).await.unwrap(); - - conn.execute("CREATE TABLE IF NOT EXISTS users (email TEXT)", ()) - .await - .unwrap(); - - let stmt = conn - .prepare("INSERT INTO users (email) VALUES (?1)") - .await - .unwrap(); - - stmt.execute(["foo@example.com"]).await.unwrap(); - - let stmt = conn - .prepare("SELECT * FROM users WHERE email = ?1") - .await - .unwrap(); - - let mut rows = stmt.query(["foo@example.com"]).await.unwrap(); - - let row = rows.next().await.unwrap().unwrap(); - - let value = row.get_value(0).unwrap(); - - println!("Row: {:?}", value); -} diff --git a/vendor/libsql/examples/flutter.rs b/vendor/libsql/examples/flutter.rs deleted file mode 100644 index ba928bcf..00000000 --- a/vendor/libsql/examples/flutter.rs +++ /dev/null @@ -1,52 +0,0 @@ -use libsql::Builder; - -#[tokio::main] -async fn main() { - let db = if let Ok(url) = std::env::var("LIBSQL_HRANA_URL") { - let token = std::env::var("TURSO_AUTH_TOKEN").unwrap_or_else(|_| { - println!("TURSO_AUTH_TOKEN not set, using empty token..."); - "".to_string() - }); - - let https = hyper_rustls::HttpsConnectorBuilder::new() - .with_webpki_roots() - .https_or_http() - .enable_http1() - .build(); - - Builder::new_remote(url, token) - .connector(https) - .build() - .await - .unwrap() - } else { - Builder::new_local(":memory:").build().await.unwrap() - }; - let conn = db.connect().unwrap(); - - conn.query("select 1; select 1;", ()).await.unwrap(); - - conn.execute("CREATE TABLE IF NOT EXISTS users (email TEXT)", ()) - .await - .unwrap(); - - let stmt = conn - .prepare("INSERT INTO users (email) VALUES (?1)") - .await - .unwrap(); - - stmt.execute(["foo@example.com"]).await.unwrap(); - - let stmt = conn - .prepare("SELECT * FROM users WHERE email = ?1") - .await - .unwrap(); - - let mut rows = stmt.query(["foo@example.com"]).await.unwrap(); - - let row = rows.next().await.unwrap().unwrap(); - - let value = row.get_value(0).unwrap(); - - println!("Row: {:?}", value); -} diff --git a/vendor/libsql/examples/local_sync.rs b/vendor/libsql/examples/local_sync.rs deleted file mode 100644 index fa0ef9d7..00000000 --- a/vendor/libsql/examples/local_sync.rs +++ /dev/null @@ -1,63 +0,0 @@ -use libsql::{ - replication::{Frames, SnapshotFile}, - Builder, -}; - -#[tokio::main] -async fn main() { - tracing_subscriber::fmt::init(); - - let db = Builder::new_local_replica("test.db") - .http_request_callback(|r| { - let _uri = r.uri_mut(); - - // You can modify any part of the http request you would like including headers - // and the URI. - }) - .build() - .await - .unwrap(); - let conn = db.connect().unwrap(); - - let args = std::env::args().collect::>(); - if args.len() < 2 { - println!("Usage: {} ", args[0]); - return; - } - let snapshots_path = args.get(1).unwrap(); - - loop { - let paths = std::fs::read_dir(snapshots_path).unwrap(); - for snapshot_path in paths { - let snapshot_path = snapshot_path.unwrap().path(); - println!( - "Applying snapshot to local database: {}\n", - snapshot_path.display() - ); - let snapshot = SnapshotFile::open(&snapshot_path, None).await.unwrap(); - match db.sync_frames(Frames::Snapshot(snapshot)).await { - Ok(n) => println!( - "{} applied, new commit index: {n:?}", - snapshot_path.display() - ), - Err(e) => println!( - "Syncing frames from {} failed: {e}", - snapshot_path.display() - ), - } - } - - let mut rows = conn.query("SELECT * FROM sqlite_master", ()).await.unwrap(); - while let Ok(Some(row)) = rows.next().await { - println!( - "| {:024} | {:024} | {:024} | {:024} |", - row.get_str(0).unwrap(), - row.get_str(1).unwrap(), - row.get_str(2).unwrap(), - row.get_str(3).unwrap(), - ); - } - println!("Sleeping for 5 seconds ..."); - std::thread::sleep(std::time::Duration::from_secs(5)); - } -} diff --git a/vendor/libsql/examples/offline_writes.rs b/vendor/libsql/examples/offline_writes.rs deleted file mode 100644 index a59dc166..00000000 --- a/vendor/libsql/examples/offline_writes.rs +++ /dev/null @@ -1,82 +0,0 @@ -// Example of using a offline writes with libSQL. - -use libsql::{params, Builder}; - -#[tokio::main] -async fn main() { - tracing_subscriber::fmt::init(); - - // The local database path where the data will be stored. - let db_path = std::env::var("LIBSQL_DB_PATH") - .map_err(|_| { - eprintln!( - "Please set the LIBSQL_DB_PATH environment variable to set to local database path." - ) - }) - .unwrap(); - - // The remote sync URL to use. - let sync_url = std::env::var("LIBSQL_SYNC_URL") - .map_err(|_| { - eprintln!( - "Please set the LIBSQL_SYNC_URL environment variable to set to remote sync URL." - ) - }) - .unwrap(); - - // The authentication token to use. - let auth_token = std::env::var("LIBSQL_AUTH_TOKEN").unwrap_or("".to_string()); - - let db_builder = Builder::new_synced_database(db_path, sync_url, auth_token); - - let db = match db_builder.build().await { - Ok(db) => db, - Err(error) => { - eprintln!("Error connecting to remote sync server: {}", error); - return; - } - }; - - let conn = db.connect().unwrap(); - - println!("Syncing database from remote..."); - db.sync().await.unwrap(); - - conn.execute( - r#" - CREATE TABLE IF NOT EXISTS guest_book_entries ( - text TEXT - )"#, - (), - ) - .await - .unwrap(); - - let mut input = String::new(); - println!("Please write your entry to the guestbook:"); - match std::io::stdin().read_line(&mut input) { - Ok(_) => { - println!("You entered: {}", input); - let params = params![input.as_str()]; - conn.execute("INSERT INTO guest_book_entries (text) VALUES (?)", params) - .await - .unwrap(); - } - Err(error) => { - eprintln!("Error reading input: {}", error); - } - } - let mut results = conn - .query("SELECT * FROM guest_book_entries", ()) - .await - .unwrap(); - println!("Guest book entries:"); - while let Some(row) = results.next().await.unwrap() { - let text: String = row.get(0).unwrap(); - println!(" {}", text); - } - - println!("Syncing database to remote..."); - db.sync().await.unwrap(); - println!("Done!"); -} diff --git a/vendor/libsql/examples/remote_sync.rs b/vendor/libsql/examples/remote_sync.rs deleted file mode 100644 index d42ccd1f..00000000 --- a/vendor/libsql/examples/remote_sync.rs +++ /dev/null @@ -1,86 +0,0 @@ -// Example of using a remote sync server with libsql. - -use libsql::{params, Builder}; - -#[tokio::main] -async fn main() { - tracing_subscriber::fmt::init(); - - // The local database path where the data will be stored. - let db_path = std::env::var("LIBSQL_DB_PATH") - .map_err(|_| { - eprintln!( - "Please set the LIBSQL_DB_PATH environment variable to set to local database path." - ) - }) - .unwrap(); - - // The remote sync URL to use. - let sync_url = std::env::var("LIBSQL_SYNC_URL") - .map_err(|_| { - eprintln!( - "Please set the LIBSQL_SYNC_URL environment variable to set to remote sync URL." - ) - }) - .unwrap(); - - let namespace = std::env::var("LIBSQL_NAMESPACE").ok(); - - // The authentication token to use. - let auth_token = std::env::var("LIBSQL_AUTH_TOKEN").unwrap_or("".to_string()); - - let db_builder = if let Some(ns) = namespace { - Builder::new_remote_replica(db_path, sync_url, auth_token).namespace(&ns) - } else { - Builder::new_remote_replica(db_path, sync_url, auth_token) - }; - - let db = match db_builder.build().await { - Ok(db) => db, - Err(error) => { - eprintln!("Error connecting to remote sync server: {}", error); - return; - } - }; - - let conn = db.connect().unwrap(); - - print!("Syncing with remote database..."); - db.sync().await.unwrap(); - println!(" done"); - - conn.execute( - r#" - CREATE TABLE IF NOT EXISTS guest_book_entries ( - text TEXT - )"#, - (), - ) - .await - .unwrap(); - - let mut input = String::new(); - println!("Please write your entry to the guestbook:"); - match std::io::stdin().read_line(&mut input) { - Ok(_) => { - println!("You entered: {}", input); - let params = params![input.as_str()]; - conn.execute("INSERT INTO guest_book_entries (text) VALUES (?)", params) - .await - .unwrap(); - } - Err(error) => { - eprintln!("Error reading input: {}", error); - } - } - db.sync().await.unwrap(); - let mut results = conn - .query("SELECT * FROM guest_book_entries", ()) - .await - .unwrap(); - println!("Guest book entries:"); - while let Some(row) = results.next().await.unwrap() { - let text: String = row.get(0).unwrap(); - println!(" {}", text); - } -} diff --git a/vendor/libsql/examples/replica.rs b/vendor/libsql/examples/replica.rs deleted file mode 100644 index db0fe57e..00000000 --- a/vendor/libsql/examples/replica.rs +++ /dev/null @@ -1,93 +0,0 @@ -use libsql::{Builder, Cipher, EncryptionConfig, Value}; -use std::time::Duration; - -#[tokio::main] -async fn main() { - tracing_subscriber::fmt::init(); - - let db_dir = tempfile::tempdir().unwrap(); - let db_file = db_dir.path().join("data.db"); - println!("Database {}", db_file.display()); - - let auth_token = std::env::var("LIBSQL_AUTH_TOKEN").unwrap_or_else(|_| { - println!("Using empty token since LIBSQL_TOKEN was not set"); - "".to_string() - }); - - let url = std::env::var("LIBSQL_URL") - .unwrap_or_else(|_| { - println!("Using http://localhost:8080 LIBSQL_URL was not set"); - "http://localhost:8080".to_string() - }) - .replace("libsql", "https"); - - let db = if cfg!(feature = "encryption") { - let encryption_config = EncryptionConfig { - cipher: Cipher::Aes256Cbc, - encryption_key: "s3cr3t".into(), - }; - Builder::new_remote_replica(&db_file, url, auth_token) - .encryption_config(encryption_config) - .build() - .await - .unwrap() - } else { - Builder::new_remote_replica(&db_file, url, auth_token) - .build() - .await - .unwrap() - }; - - let conn = db.connect().unwrap(); - - let f = db.sync().await.unwrap(); - println!("inital sync complete, frame no: {f:?}"); - - conn.execute("CREATE TABLE IF NOT EXISTS foo (x TEXT)", ()) - .await - .unwrap(); - - db.sync().await.unwrap(); - - let mut jh = tokio::spawn(async move { - let mut rows = conn - .query( - "INSERT INTO foo (x) VALUES (?1) RETURNING *", - vec![Value::from( - "this value was written by an embedded replica!", - )], - ) - .await - .unwrap(); - - println!("Rows insert call"); - while let Some(row) = rows.next().await.unwrap() { - println!("Row: {}", row.get_str(0).unwrap()); - } - - println!("--------"); - - let mut rows = conn.query("SELECT * FROM foo", ()).await.unwrap(); - - println!("Rows coming from a read after write call"); - while let Some(row) = rows.next().await.unwrap() { - println!("Row: {}", row.get_str(0).unwrap()); - } - - println!("--------"); - }); - - loop { - tokio::select! { - _ = tokio::time::sleep(Duration::from_secs(1)) => { - let r = db.sync().await.unwrap(); - println!("replicated until index {r:?}"); - } - - r = &mut jh => { - r.unwrap(); - return; - } - } - } -} diff --git a/vendor/libsql/examples/transaction.rs b/vendor/libsql/examples/transaction.rs deleted file mode 100644 index d944c522..00000000 --- a/vendor/libsql/examples/transaction.rs +++ /dev/null @@ -1,42 +0,0 @@ -use libsql::Builder; - -#[tokio::main] -async fn main() { - tracing_subscriber::fmt::init(); - - let db_file = tempfile::NamedTempFile::new().unwrap(); - println!("Database {}", db_file.path().display()); - - let auth_token = std::env::var("TURSO_AUTH_TOKEN").unwrap_or_else(|_| { - println!("Using empty token since TURSO_AUTH_TOKEN was not set"); - "".to_string() - }); - - let db = Builder::new_remote_replica( - db_file.path(), - "http://localhost:8080".to_string(), - auth_token, - ) - .build() - .await - .unwrap(); - - db.sync().await.unwrap(); - - let conn = db.connect().unwrap(); - - conn.execute("BEGIN READONLY", ()).await.unwrap(); - conn.query("SELECT 1", ()).await.unwrap(); - conn.query("COMMIT", ()).await.unwrap(); - - let tx = conn - .transaction_with_behavior(libsql::TransactionBehavior::Immediate) - .await - .unwrap(); - - tx.execute("INSERT INTO foo (x) VALUES (?1)", ["hello world"]) - .await - .unwrap(); - - tx.commit().await.unwrap(); -} diff --git a/vendor/libsql/src/auth.rs b/vendor/libsql/src/auth.rs deleted file mode 100644 index 927cfad9..00000000 --- a/vendor/libsql/src/auth.rs +++ /dev/null @@ -1,263 +0,0 @@ -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct AuthContext<'a> { - pub action: AuthAction<'a>, - - pub database_name: Option<&'a str>, - - pub accessor: Option<&'a str>, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum AuthAction<'a> { - Unknown { - code: i32, - arg1: Option<&'a str>, - arg2: Option<&'a str>, - }, - CreateIndex { - index_name: &'a str, - table_name: &'a str, - }, - CreateTable { - table_name: &'a str, - }, - CreateTempIndex { - index_name: &'a str, - table_name: &'a str, - }, - CreateTempTable { - table_name: &'a str, - }, - CreateTempTrigger { - trigger_name: &'a str, - table_name: &'a str, - }, - CreateTempView { - view_name: &'a str, - }, - CreateTrigger { - trigger_name: &'a str, - table_name: &'a str, - }, - CreateView { - view_name: &'a str, - }, - Delete { - table_name: &'a str, - }, - DropIndex { - index_name: &'a str, - table_name: &'a str, - }, - DropTable { - table_name: &'a str, - }, - DropTempIndex { - index_name: &'a str, - table_name: &'a str, - }, - DropTempTable { - table_name: &'a str, - }, - DropTempTrigger { - trigger_name: &'a str, - table_name: &'a str, - }, - DropTempView { - view_name: &'a str, - }, - DropTrigger { - trigger_name: &'a str, - table_name: &'a str, - }, - DropView { - view_name: &'a str, - }, - Insert { - table_name: &'a str, - }, - Pragma { - pragma_name: &'a str, - pragma_value: Option<&'a str>, - }, - Read { - table_name: &'a str, - column_name: &'a str, - }, - Select, - Transaction { - operation: TransactionOperation, - }, - Update { - table_name: &'a str, - column_name: &'a str, - }, - Attach { - filename: &'a str, - }, - Detach { - database_name: &'a str, - }, - AlterTable { - database_name: &'a str, - table_name: &'a str, - }, - Reindex { - index_name: &'a str, - }, - Analyze { - table_name: &'a str, - }, - CreateVtable { - table_name: &'a str, - module_name: &'a str, - }, - DropVtable { - table_name: &'a str, - module_name: &'a str, - }, - Function { - function_name: &'a str, - }, - Savepoint { - operation: TransactionOperation, - savepoint_name: &'a str, - }, - Recursive, -} - -#[cfg(feature = "core")] -impl<'a> AuthAction<'a> { - pub(crate) fn from_raw(code: i32, arg1: Option<&'a str>, arg2: Option<&'a str>) -> Self { - use libsql_sys::ffi; - - match (code, arg1, arg2) { - (ffi::SQLITE_CREATE_INDEX, Some(index_name), Some(table_name)) => Self::CreateIndex { - index_name, - table_name, - }, - (ffi::SQLITE_CREATE_TABLE, Some(table_name), _) => Self::CreateTable { table_name }, - (ffi::SQLITE_CREATE_TEMP_INDEX, Some(index_name), Some(table_name)) => { - Self::CreateTempIndex { - index_name, - table_name, - } - } - (ffi::SQLITE_CREATE_TEMP_TABLE, Some(table_name), _) => { - Self::CreateTempTable { table_name } - } - (ffi::SQLITE_CREATE_TEMP_TRIGGER, Some(trigger_name), Some(table_name)) => { - Self::CreateTempTrigger { - trigger_name, - table_name, - } - } - (ffi::SQLITE_CREATE_TEMP_VIEW, Some(view_name), _) => { - Self::CreateTempView { view_name } - } - (ffi::SQLITE_CREATE_TRIGGER, Some(trigger_name), Some(table_name)) => { - Self::CreateTrigger { - trigger_name, - table_name, - } - } - (ffi::SQLITE_CREATE_VIEW, Some(view_name), _) => Self::CreateView { view_name }, - (ffi::SQLITE_DELETE, Some(table_name), None) => Self::Delete { table_name }, - (ffi::SQLITE_DROP_INDEX, Some(index_name), Some(table_name)) => Self::DropIndex { - index_name, - table_name, - }, - (ffi::SQLITE_DROP_TABLE, Some(table_name), _) => Self::DropTable { table_name }, - (ffi::SQLITE_DROP_TEMP_INDEX, Some(index_name), Some(table_name)) => { - Self::DropTempIndex { - index_name, - table_name, - } - } - (ffi::SQLITE_DROP_TEMP_TABLE, Some(table_name), _) => { - Self::DropTempTable { table_name } - } - (ffi::SQLITE_DROP_TEMP_TRIGGER, Some(trigger_name), Some(table_name)) => { - Self::DropTempTrigger { - trigger_name, - table_name, - } - } - (ffi::SQLITE_DROP_TEMP_VIEW, Some(view_name), _) => Self::DropTempView { view_name }, - (ffi::SQLITE_DROP_TRIGGER, Some(trigger_name), Some(table_name)) => Self::DropTrigger { - trigger_name, - table_name, - }, - (ffi::SQLITE_DROP_VIEW, Some(view_name), _) => Self::DropView { view_name }, - (ffi::SQLITE_INSERT, Some(table_name), _) => Self::Insert { table_name }, - (ffi::SQLITE_PRAGMA, Some(pragma_name), pragma_value) => Self::Pragma { - pragma_name, - pragma_value, - }, - (ffi::SQLITE_READ, Some(table_name), Some(column_name)) => Self::Read { - table_name, - column_name, - }, - (ffi::SQLITE_SELECT, ..) => Self::Select, - (ffi::SQLITE_TRANSACTION, Some(operation_str), _) => Self::Transaction { - operation: TransactionOperation::from_str(operation_str), - }, - (ffi::SQLITE_UPDATE, Some(table_name), Some(column_name)) => Self::Update { - table_name, - column_name, - }, - (ffi::SQLITE_ATTACH, Some(filename), _) => Self::Attach { filename }, - (ffi::SQLITE_DETACH, Some(database_name), _) => Self::Detach { database_name }, - (ffi::SQLITE_ALTER_TABLE, Some(database_name), Some(table_name)) => Self::AlterTable { - database_name, - table_name, - }, - (ffi::SQLITE_REINDEX, Some(index_name), _) => Self::Reindex { index_name }, - (ffi::SQLITE_ANALYZE, Some(table_name), _) => Self::Analyze { table_name }, - (ffi::SQLITE_CREATE_VTABLE, Some(table_name), Some(module_name)) => { - Self::CreateVtable { - table_name, - module_name, - } - } - (ffi::SQLITE_DROP_VTABLE, Some(table_name), Some(module_name)) => Self::DropVtable { - table_name, - module_name, - }, - (ffi::SQLITE_FUNCTION, _, Some(function_name)) => Self::Function { function_name }, - (ffi::SQLITE_SAVEPOINT, Some(operation_str), Some(savepoint_name)) => Self::Savepoint { - operation: TransactionOperation::from_str(operation_str), - savepoint_name, - }, - (ffi::SQLITE_RECURSIVE, ..) => Self::Recursive, - (code, arg1, arg2) => Self::Unknown { code, arg1, arg2 }, - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum TransactionOperation { - Unknown, - Begin, - Release, - Rollback, -} - -#[cfg(feature = "core")] -impl TransactionOperation { - fn from_str(op_str: &str) -> Self { - match op_str { - "BEGIN" => Self::Begin, - "RELEASE" => Self::Release, - "ROLLBACK" => Self::Rollback, - _ => Self::Unknown, - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Authorization { - Allow, - Ignore, - Deny, -} diff --git a/vendor/libsql/src/connection.rs b/vendor/libsql/src/connection.rs deleted file mode 100644 index 396f2e1f..00000000 --- a/vendor/libsql/src/connection.rs +++ /dev/null @@ -1,311 +0,0 @@ -use std::collections::VecDeque; -use std::fmt; -use std::path::Path; -use std::sync::Arc; -use std::time::Duration; - -use crate::auth::{AuthContext, Authorization}; -use crate::params::{IntoParams, Params}; -use crate::rows::Rows; -use crate::statement::Statement; -use crate::transaction::Transaction; -use crate::{Result, TransactionBehavior}; - -pub type AuthHook = Arc Authorization>; - -pub type UpdateHook = dyn Fn(Op, &str, &str, i64) + Send + Sync; - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Op { - Insert = 0, - Delete = 1, - Update = 2, -} - -#[async_trait::async_trait] -pub(crate) trait Conn { - async fn execute(&self, sql: &str, params: Params) -> Result; - - async fn execute_batch(&self, sql: &str) -> Result; - - async fn execute_transactional_batch(&self, sql: &str) -> Result; - - async fn prepare(&self, sql: &str) -> Result; - - async fn transaction(&self, tx_behavior: TransactionBehavior) -> Result; - - fn interrupt(&self) -> Result<()>; - - fn busy_timeout(&self, timeout: Duration) -> Result<()>; - - fn is_autocommit(&self) -> bool; - - fn changes(&self) -> u64; - - fn total_changes(&self) -> u64; - - fn last_insert_rowid(&self) -> i64; - - async fn reset(&self); - - fn set_reserved_bytes(&self, _reserved_bytes: i32) -> Result<()> { - Err(crate::Error::ReservedBytesNotSupported) - } - - fn get_reserved_bytes(&self) -> Result { - Err(crate::Error::ReservedBytesNotSupported) - } - - fn enable_load_extension(&self, _onoff: bool) -> Result<()> { - Err(crate::Error::LoadExtensionNotSupported) - } - - fn load_extension(&self, _dylib_path: &Path, _entry_point: Option<&str>) -> Result<()> { - Err(crate::Error::LoadExtensionNotSupported) - } - - fn authorizer(&self, _hook: Option) -> Result<()> { - Err(crate::Error::AuthorizerNotSupported) - } - - fn add_update_hook(&self, _cb: Box) -> Result<()> { - Err(crate::Error::UpdateHookNotSupported) - } -} - -/// A set of rows returned from `execute_batch`/`execute_transactional_batch`. It is essentially -/// rows of rows for each statement in the batch call. -/// -/// # Note -/// -/// All rows will be materialized in memory, if you would like to stream them then use `query` -/// instead as this is optimized better for memory usage. -pub struct BatchRows { - inner: VecDeque>, - skip_last_amt: usize, -} - -impl BatchRows { - #[allow(unused)] - pub(crate) fn empty() -> Self { - Self { - inner: VecDeque::new(), - skip_last_amt: 0, - } - } - - #[cfg(any(feature = "hrana", feature = "core"))] - pub(crate) fn new(rows: Vec>) -> Self { - Self { - inner: rows.into(), - skip_last_amt: 0, - } - } - - #[cfg(feature = "hrana")] - pub(crate) fn new_skip_last(rows: Vec>, skip_last_amt: usize) -> Self { - Self { - inner: rows.into(), - skip_last_amt, - } - } - - /// Get the next set of rows, it is wrapped in two options, if the first option returns `None` - /// then the set of batch statement results has ended. If the inner option returns `None` then - /// the statement was never executed (potentially due to a conditional). - pub fn next_stmt_row(&mut self) -> Option> { - if self.inner.len() <= self.skip_last_amt { - return None; - } - - self.inner.pop_front() - } -} - -impl fmt::Debug for BatchRows { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("BatchRows").finish() - } -} - -/// A connection to some libsql database, this can be a remote one or a local one. -#[derive(Clone)] -pub struct Connection { - pub(crate) conn: Arc, -} - -impl Connection { - /// Execute sql query provided some type that implements [`IntoParams`] returning - /// on success the number of rows that were changed. - /// - /// # Example - /// - /// ```rust,no_run - /// # async fn run(conn: &libsql::Connection) { - /// # use libsql::params; - /// conn.execute("INSERT INTO foo (id) VALUES (?1)", [42]).await.unwrap(); - /// conn.execute("INSERT INTO foo (id, name) VALUES (?1, ?2)", params![42, "baz"]).await.unwrap(); - /// # } - /// ``` - /// - /// For more info on how to pass params check [`IntoParams`]'s docs. - pub async fn execute(&self, sql: &str, params: impl IntoParams) -> Result { - tracing::trace!("executing `{}`", sql); - self.conn.execute(sql, params.into_params()?).await - } - - /// Execute a batch set of statements. - /// - /// # Return - /// - /// This returns a `BatchRows` currently only the `remote` and `local` connection supports this feature and - /// all other connection types will return an empty set always. - pub async fn execute_batch(&self, sql: &str) -> Result { - tracing::trace!("executing batch `{}`", sql); - self.conn.execute_batch(sql).await - } - - /// Execute a batch set of statements atomically in a transaction. - /// - /// # Return - /// - /// This returns a `BatchRows` currently only the `remote` and `local` connection supports this feature and - /// all other connection types will return an empty set always. - pub async fn execute_transactional_batch(&self, sql: &str) -> Result { - tracing::trace!("executing batch transactional `{}`", sql); - self.conn.execute_transactional_batch(sql).await - } - - /// Execute sql query provided some type that implements [`IntoParams`] returning - /// on success the [`Rows`]. - /// - /// # Example - /// - /// ```rust,no_run - /// # async fn run(conn: &libsql::Connection) { - /// # use libsql::params; - /// conn.query("SELECT foo FROM bar WHERE id = ?1", [42]).await.unwrap(); - /// conn.query("SELECT foo FROM bar WHERE id = ?1 AND name = ?2", params![42, "baz"]).await.unwrap(); - /// # } - /// ``` - /// For more info on how to pass params check [`IntoParams`]'s docs and on how to - /// extract values out of the rows check the [`Rows`] docs. - pub async fn query(&self, sql: &str, params: impl IntoParams) -> Result { - let stmt = self.prepare(sql).await?; - - stmt.query(params).await - } - - /// Prepares a cached statement. - pub async fn prepare(&self, sql: &str) -> Result { - tracing::trace!("preparing `{}`", sql); - self.conn.prepare(sql).await - } - - /// Begin a new transaction in `DEFERRED` mode, which is the default. - pub async fn transaction(&self) -> Result { - tracing::trace!("starting deferred transaction"); - self.transaction_with_behavior(TransactionBehavior::Deferred) - .await - } - - /// Begin a new transaction in the given [`TransactionBehavior`]. - pub async fn transaction_with_behavior( - &self, - tx_behavior: TransactionBehavior, - ) -> Result { - tracing::trace!("starting {:?} transaction", tx_behavior); - self.conn.transaction(tx_behavior).await - } - - /// Cancel ongoing operations and return at earliest opportunity. - pub fn interrupt(&self) -> Result<()> { - self.conn.interrupt() - } - - pub fn busy_timeout(&self, timeout: Duration) -> Result<()> { - self.conn.busy_timeout(timeout) - } - - /// Check whether libsql is in `autocommit` or not. - pub fn is_autocommit(&self) -> bool { - self.conn.is_autocommit() - } - - /// Check the amount of changes the last query created. - pub fn changes(&self) -> u64 { - self.conn.changes() - } - - /// Check the total amount of changes the connection has done. - pub fn total_changes(&self) -> u64 { - self.conn.total_changes() - } - - /// Check the last inserted row id. - pub fn last_insert_rowid(&self) -> i64 { - self.conn.last_insert_rowid() - } - - pub async fn reset(&self) { - self.conn.reset().await - } - - pub fn set_reserved_bytes(&self, reserved_bytes: i32) -> Result<()> { - self.conn.set_reserved_bytes(reserved_bytes) - } - - pub fn get_reserved_bytes(&self) -> Result { - self.conn.get_reserved_bytes() - } - - /// Enable loading SQLite extensions from SQL queries and Rust API. - /// - /// See [`load_extension`](Connection::load_extension) documentation for more details. - pub fn load_extension_enable(&self) -> Result<()> { - self.conn.enable_load_extension(true) - } - - /// Disable loading SQLite extensions from SQL queries and Rust API. - /// - /// See [`load_extension`](Connection::load_extension) documentation for more details. - pub fn load_extension_disable(&self) -> Result<()> { - self.conn.enable_load_extension(false) - } - - /// Load a SQLite extension from a dynamic library at `dylib_path`, specifying optional - /// entry point `entry_point`. - /// - /// # Security - /// - /// Loading extensions from dynamic libraries is a potential security risk, as it allows - /// arbitrary code execution. Only load extensions that you trust. - /// - /// Extension loading is disabled by default. Please use the [`load_extension_enable`](Connection::load_extension_enable) - /// method to enable it. It's recommended to disable extension loading after you're done - /// loading extensions to avoid SQL injection attacks from loading extensions. - /// - /// See SQLite's documentation on `sqlite3_load_extension` for more information: - /// https://sqlite.org/c3ref/load_extension.html - pub fn load_extension>( - &self, - dylib_path: P, - entry_point: Option<&str>, - ) -> Result<()> { - self.conn.load_extension(dylib_path.as_ref(), entry_point) - } - - pub fn authorizer(&self, hook: Option) -> Result<()> { - self.conn.authorizer(hook) - } - - pub fn add_update_hook(&self, cb: Box) -> Result<()> { - self.conn.add_update_hook(cb) - } -} - -impl fmt::Debug for Connection { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Connection").finish() - } -} diff --git a/vendor/libsql/src/database.rs b/vendor/libsql/src/database.rs deleted file mode 100644 index b4da6171..00000000 --- a/vendor/libsql/src/database.rs +++ /dev/null @@ -1,816 +0,0 @@ -#![allow(deprecated)] - -mod builder; - -pub use builder::Builder; - -#[cfg(feature = "core")] -pub use libsql_sys::{Cipher, EncryptionConfig}; - -use crate::{Connection, Result}; -#[cfg(any(feature = "remote", feature = "sync"))] -use base64::{engine::general_purpose, Engine}; -use std::fmt; -use std::sync::atomic::AtomicU64; - -cfg_core! { - bitflags::bitflags! { - /// Flags that can be passed to libsql to open a database in specific - /// modes. - #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] - #[repr(C)] - pub struct OpenFlags: ::std::os::raw::c_int { - const SQLITE_OPEN_READ_ONLY = libsql_sys::ffi::SQLITE_OPEN_READONLY; - const SQLITE_OPEN_READ_WRITE = libsql_sys::ffi::SQLITE_OPEN_READWRITE; - const SQLITE_OPEN_CREATE = libsql_sys::ffi::SQLITE_OPEN_CREATE; - } - } - - impl Default for OpenFlags { - #[inline] - fn default() -> OpenFlags { - OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE - } - } -} - -cfg_replication_or_sync! { - - pub type FrameNo = u64; - - #[derive(Debug)] - // TODO(lucio): remove this once we use these fields in our sync code - #[allow(dead_code)] - pub struct Replicated { - pub(crate) frame_no: Option, - pub(crate) frames_synced: usize, - } - - impl Replicated { - /// The currently synced frame number. This can be used to track - /// where in the log you might be. Beware that this value can be reset to a lower value by the - /// server in certain situations. Please use `frames_synced` if you want to track the amount of - /// work a sync has done. - // TODO(lucio): remove this once we use these fields in our sync code - #[allow(dead_code)] - pub fn frame_no(&self) -> Option { - self.frame_no - } - - /// The count of frames synced during this call of `sync`. A frame is a 4kB frame from the - /// libsql write ahead log. - // TODO(lucio): remove this once we use these fields in our sync code - #[allow(dead_code)] - pub fn frames_synced(&self) -> usize { - self.frames_synced - } - } - -} - -cfg_sync! { - #[derive(Default)] - pub enum SyncProtocol { - #[default] - Auto, - V1, - V2, - } -} - -enum DbType { - #[cfg(feature = "core")] - Memory { db: crate::local::Database }, - #[cfg(feature = "core")] - File { - path: String, - flags: OpenFlags, - encryption_config: Option, - skip_safety_assert: bool, - }, - #[cfg(feature = "replication")] - Sync { - db: crate::local::Database, - encryption_config: Option, - }, - #[cfg(feature = "sync")] - Offline { - db: crate::local::Database, - remote_writes: bool, - read_your_writes: bool, - url: String, - auth_token: String, - connector: crate::util::ConnectorService, - _bg_abort: Option>, - remote_encryption: Option, - }, - #[cfg(feature = "remote")] - Remote { - url: String, - auth_token: String, - connector: crate::util::ConnectorService, - version: Option, - namespace: Option, - remote_encryption: Option, - }, -} - -impl fmt::Debug for DbType { - #[allow(unreachable_patterns)] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - #[cfg(feature = "core")] - Self::Memory { .. } => write!(f, "Memory"), - #[cfg(feature = "core")] - Self::File { .. } => write!(f, "File"), - #[cfg(feature = "replication")] - Self::Sync { .. } => write!(f, "Sync"), - #[cfg(feature = "sync")] - Self::Offline { .. } => write!(f, "Offline"), - #[cfg(feature = "remote")] - Self::Remote { .. } => write!(f, "Remote"), - _ => write!(f, "no database type set"), - } - } -} - -/// A struct that knows how to build [`Connection`]'s, this type does -/// not do much work until the [`Database::connect`] fn is called. -pub struct Database { - db_type: DbType, - /// The maximum replication index returned from a write performed using any connection created using this Database object. - #[allow(dead_code)] - max_write_replication_index: std::sync::Arc, -} - -cfg_core! { - impl Database { - /// Open an in-memory libsql database. - #[deprecated = "Use the new `Builder` to construct `Database`"] - pub fn open_in_memory() -> Result { - let db = crate::local::Database::open(":memory:", OpenFlags::default())?; - - Ok(Database { - db_type: DbType::Memory { db }, - max_write_replication_index: Default::default(), - }) - } - - /// Open a file backed libsql database. - #[deprecated = "Use the new `Builder` to construct `Database`"] - pub fn open(db_path: impl Into) -> Result { - Database::open_with_flags(db_path, OpenFlags::default()) - } - - /// Open a file backed libsql database with flags. - #[deprecated = "Use the new `Builder` to construct `Database`"] - pub fn open_with_flags(db_path: impl Into, flags: OpenFlags) -> Result { - Ok(Database { - db_type: DbType::File { - path: db_path.into(), - flags, - encryption_config: None, - skip_safety_assert: false, - }, - max_write_replication_index: Default::default(), - }) - } - } -} - -cfg_replication! { - use crate::Error; - - - impl Database { - /// Open a local database file with the ability to sync from snapshots from local filesystem. - #[deprecated = "Use the new `Builder` to construct `Database`"] - pub async fn open_with_local_sync( - db_path: impl Into, - encryption_config: Option - ) -> Result { - let db = crate::local::Database::open_local_sync( - db_path, - OpenFlags::default(), - encryption_config.clone() - ).await?; - - Ok(Database { - db_type: DbType::Sync { db, encryption_config }, - max_write_replication_index: Default::default(), - }) - } - - - /// Open a local database file with the ability to sync from snapshots from local filesystem - /// and forward writes to the provided endpoint. - #[deprecated = "Use the new `Builder` to construct `Database`"] - pub async fn open_with_local_sync_remote_writes( - db_path: impl Into, - endpoint: String, - auth_token: String, - encryption_config: Option, - ) -> Result { - let https = connector()?; - - Self::open_with_local_sync_remote_writes_connector( - db_path, - endpoint, - auth_token, - https, - encryption_config, - ).await - } - - /// Open a local database file with the ability to sync from snapshots from local filesystem - /// and forward writes to the provided endpoint and a custom http connector. - #[deprecated = "Use the new `Builder` to construct `Database`"] - pub async fn open_with_local_sync_remote_writes_connector( - db_path: impl Into, - endpoint: String, - auth_token: String, - connector: C, - encryption_config: Option, - ) -> Result - where - C: tower::Service + Send + Clone + Sync + 'static, - C::Response: crate::util::Socket, - C::Future: Send + 'static, - C::Error: Into>, - { - use tower::ServiceExt; - - let svc = connector - .map_err(|e| e.into()) - .map_response(|s| Box::new(s) as Box); - - let svc = crate::util::ConnectorService::new(svc); - - let db = crate::local::Database::open_local_sync_remote_writes( - svc, - db_path.into(), - endpoint, - auth_token, - None, - OpenFlags::default(), - encryption_config.clone(), - None, - None, - ).await?; - - Ok(Database { - db_type: DbType::Sync { db, encryption_config }, - max_write_replication_index: Default::default(), - }) - } - - /// Open a local database file with the ability to sync from a remote database. - #[deprecated = "Use the new `Builder` to construct `Database`"] - pub async fn open_with_remote_sync( - db_path: impl Into, - url: impl Into, - token: impl Into, - encryption_config: Option, - ) -> Result { - let https = connector()?; - - Self::open_with_remote_sync_connector(db_path, url, token, https, false, encryption_config).await - } - - /// Open a local database file with the ability to sync from a remote database - /// in consistent mode. - /// - /// Consistent mode means that when a write happens it will not complete until - /// that write is visible in the local db. - #[deprecated = "Use the new `Builder` to construct `Database`"] - pub async fn open_with_remote_sync_consistent( - db_path: impl Into, - url: impl Into, - token: impl Into, - encryption_config: Option, - ) -> Result { - let https = connector()?; - - Self::open_with_remote_sync_connector(db_path, url, token, https, true, encryption_config).await - } - - /// Connect an embedded replica to a remote primary with a custom - /// http connector. - #[deprecated = "Use the new `Builder` to construct `Database`"] - pub async fn open_with_remote_sync_connector( - db_path: impl Into, - url: impl Into, - token: impl Into, - connector: C, - read_your_writes: bool, - encryption_config: Option, - ) -> Result - where - C: tower::Service + Send + Clone + Sync + 'static, - C::Response: crate::util::Socket, - C::Future: Send + 'static, - C::Error: Into>, - { - Self::open_with_remote_sync_connector_internal( - db_path, - url, - token, - connector, - None, - read_your_writes, - encryption_config, - None - ).await - } - - #[doc(hidden)] - pub async fn open_with_remote_sync_internal( - db_path: impl Into, - url: impl Into, - token: impl Into, - version: Option, - read_your_writes: bool, - encryption_config: Option, - sync_interval: Option, - ) -> Result { - let https = connector()?; - - Self::open_with_remote_sync_connector_internal( - db_path, - url, - token, - https, - version, - read_your_writes, - encryption_config, - sync_interval - ).await - } - - #[doc(hidden)] - async fn open_with_remote_sync_connector_internal( - db_path: impl Into, - url: impl Into, - token: impl Into, - connector: C, - version: Option, - read_your_writes: bool, - encryption_config: Option, - sync_interval: Option, - ) -> Result - where - C: tower::Service + Send + Clone + Sync + 'static, - C::Response: crate::util::Socket, - C::Future: Send + 'static, - C::Error: Into>, - { - use tower::ServiceExt; - - let svc = connector - .map_err(|e| e.into()) - .map_response(|s| Box::new(s) as Box); - - let svc = crate::util::ConnectorService::new(svc); - - let db = crate::local::Database::open_http_sync_internal( - svc, - db_path.into(), - url.into(), - token.into(), - version, - read_your_writes, - encryption_config.clone(), - sync_interval, - None, - None - ).await?; - - Ok(Database { - db_type: DbType::Sync { db, encryption_config }, - max_write_replication_index: Default::default(), - }) - } - - - /// Sync database from remote, and returns the committed frame_no after syncing, if - /// applicable. - pub async fn sync(&self) -> Result { - match &self.db_type { - #[cfg(feature = "replication")] - DbType::Sync { db, encryption_config: _ } => db.sync().await, - #[cfg(feature = "sync")] - DbType::Offline { db, remote_writes: false, .. } => db.sync_offline().await, - #[cfg(feature = "sync")] - DbType::Offline { db, remote_writes: true, .. } => { - let mut sync_ctx = db.sync_ctx.as_ref().unwrap().lock().await; - crate::sync::bootstrap_db(&mut sync_ctx).await?; - let conn = db.connect()?; - crate::sync::try_pull(&mut sync_ctx, &conn).await - }, - _ => Err(Error::SyncNotSupported(format!("{:?}", self.db_type))), - } - } - - /// Sync database from remote until it gets to a given replication_index or further, - /// and returns the committed frame_no after syncing, if applicable. - pub async fn sync_until(&self, replication_index: FrameNo) -> Result { - if let DbType::Sync { db, encryption_config: _ } = &self.db_type { - db.sync_until(replication_index).await - } else { - Err(Error::SyncNotSupported(format!("{:?}", self.db_type))) - } - } - - /// Apply a set of frames to the database and returns the committed frame_no after syncing, if - /// applicable. - pub async fn sync_frames(&self, frames: crate::replication::Frames) -> Result> { - if let DbType::Sync { db, encryption_config: _ } = &self.db_type { - db.sync_frames(frames).await - } else { - Err(Error::SyncNotSupported(format!("{:?}", self.db_type))) - } - } - - /// Force buffered replication frames to be applied, and return the current commit frame_no - /// if applicable. - pub async fn flush_replicator(&self) -> Result> { - if let DbType::Sync { db, encryption_config: _ } = &self.db_type { - db.flush_replicator().await - } else { - Err(Error::SyncNotSupported(format!("{:?}", self.db_type))) - } - } - - /// Returns the database currently committed replication index - pub async fn replication_index(&self) -> Result> { - if let DbType::Sync { db, encryption_config: _ } = &self.db_type { - db.replication_index().await - } else { - Err(Error::SyncNotSupported(format!("{:?}", self.db_type))) - } - } - - /// Freeze this embedded replica and convert it into a regular - /// non-embedded replica database. - /// - /// # Error - /// - /// Returns `FreezeNotSupported` if the database is not configured in - /// embedded replica mode. - pub fn freeze(self) -> Result { - match self.db_type { - DbType::Sync { db, .. } => { - let path = db.path().to_string(); - Ok(Database { - db_type: DbType::File { path, flags: OpenFlags::default(), encryption_config: None, skip_safety_assert: false }, - max_write_replication_index: Default::default(), - }) - } - t => Err(Error::FreezeNotSupported(format!("{:?}", t))) - } - } - - /// Get the maximum replication index returned from a write performed using any connection created using this Database object. - pub fn max_write_replication_index(&self) -> Option { - let index = self - .max_write_replication_index - .load(std::sync::atomic::Ordering::SeqCst); - if index == 0 { - None - } else { - Some(index) - } - } - } -} - -impl Database {} - -cfg_remote! { - impl Database { - /// Open a remote based HTTP database using libsql's hrana protocol. - #[deprecated = "Use the new `Builder` to construct `Database`"] - pub fn open_remote(url: impl Into, auth_token: impl Into) -> Result { - let https = connector()?; - - Self::open_remote_with_connector_internal(url, auth_token, https, None) - } - - #[doc(hidden)] - pub fn open_remote_internal( - url: impl Into, - auth_token: impl Into, - version: impl Into, - ) -> Result { - let https = connector()?; - - Self::open_remote_with_connector_internal(url, auth_token, https, Some(version.into())) - } - - /// Connect to a remote libsql using libsql's hrana protocol with a custom connector. - #[deprecated = "Use the new `Builder` to construct `Database`"] - pub fn open_remote_with_connector( - url: impl Into, - auth_token: impl Into, - connector: C, - ) -> Result - where - C: tower::Service + Send + Clone + Sync + 'static, - C::Response: crate::util::Socket, - C::Future: Send + 'static, - C::Error: Into>, - { - Self::open_remote_with_connector_internal(url, auth_token, connector, None) - } - - #[doc(hidden)] - fn open_remote_with_connector_internal( - url: impl Into, - auth_token: impl Into, - connector: C, - version: Option, - ) -> Result - where - C: tower::Service + Send + Clone + Sync + 'static, - C::Response: crate::util::Socket, - C::Future: Send + 'static, - C::Error: Into>, - { - use tower::ServiceExt; - - let svc = connector - .map_err(|e| e.into()) - .map_response(|s| Box::new(s) as Box); - Ok(Database { - db_type: DbType::Remote { - url: url.into(), - auth_token: auth_token.into(), - connector: crate::util::ConnectorService::new(svc), - version, - namespace: None, - remote_encryption: None - }, - max_write_replication_index: Default::default(), - }) - } - } -} - -impl Database { - /// Connect to the database this can mean a few things depending on how it was constructed: - /// - /// - When constructed with `open`/`open_with_flags`/`open_in_memory` this will call into the - /// libsql C ffi and create a connection to the libsql database. - /// - When constructed with `open_remote` and friends it will not call any C ffi and will - /// lazily create a HTTP connection to the provided endpoint. - /// - When constructed with `open_with_remote_sync_` and friends it will attempt to perform a - /// handshake with the remote server and will attempt to replicate the remote database - /// locally. - #[allow(unreachable_patterns)] - pub fn connect(&self) -> Result { - match &self.db_type { - #[cfg(feature = "core")] - DbType::Memory { db } => { - use crate::local::impls::LibsqlConnection; - - let conn = db.connect()?; - - let conn = std::sync::Arc::new(LibsqlConnection { conn }); - - Ok(Connection { conn }) - } - - #[cfg(feature = "core")] - DbType::File { - path, - flags, - encryption_config, - skip_safety_assert, - } => { - use crate::local::impls::LibsqlConnection; - - let db = if !skip_safety_assert { - crate::local::Database::open(path, *flags)? - } else { - unsafe { crate::local::Database::open_raw(path, *flags)? } - }; - - let conn = db.connect()?; - - if !cfg!(feature = "encryption") && encryption_config.is_some() { - return Err(crate::Error::Misuse( - "Encryption is not enabled: enable the `encryption` feature in order to enable encryption-at-rest".to_string(), - )); - } - - #[cfg(feature = "encryption")] - if let Some(cfg) = encryption_config { - if unsafe { - libsql_sys::connection::set_encryption_cipher(conn.raw, cfg.cipher_id()) - } == -1 - { - return Err(crate::Error::Misuse( - "failed to set encryption cipher".to_string(), - )); - } - if unsafe { - libsql_sys::connection::set_encryption_key(conn.raw, &cfg.encryption_key) - } != crate::ffi::SQLITE_OK - { - return Err(crate::Error::Misuse( - "failed to set encryption key".to_string(), - )); - } - } - - let conn = std::sync::Arc::new(LibsqlConnection { conn }); - - Ok(Connection { conn }) - } - - #[cfg(feature = "replication")] - DbType::Sync { - db, - encryption_config, - } => { - use crate::local::impls::LibsqlConnection; - - let conn = db.connect()?; - - if !cfg!(feature = "encryption") && encryption_config.is_some() { - return Err(crate::Error::Misuse( - "Encryption is not enabled: enable the `encryption` feature in order to enable encryption-at-rest".to_string(), - )); - } - #[cfg(feature = "encryption")] - if let Some(cfg) = encryption_config { - if unsafe { - libsql_sys::connection::set_encryption_cipher(conn.raw, cfg.cipher_id()) - } == -1 - { - return Err(crate::Error::Misuse( - "failed to set encryption cipher".to_string(), - )); - } - if unsafe { - libsql_sys::connection::set_encryption_key(conn.raw, &cfg.encryption_key) - } != crate::ffi::SQLITE_OK - { - return Err(crate::Error::Misuse( - "failed to set encryption key".to_string(), - )); - } - } - - let local = LibsqlConnection { conn }; - let writer = local.conn.new_connection_writer(); - let remote = crate::replication::RemoteConnection::new( - local, - writer, - self.max_write_replication_index.clone(), - ); - let conn = std::sync::Arc::new(remote); - - Ok(Connection { conn }) - } - - #[cfg(feature = "sync")] - DbType::Offline { - db, - remote_writes, - read_your_writes, - url, - auth_token, - connector, - remote_encryption, - .. - } => { - use crate::{ - hrana::connection::HttpConnection, local::impls::LibsqlConnection, - replication::connection::State, sync::connection::SyncedConnection, - }; - use tokio::sync::Mutex; - - tokio::task::block_in_place(move || { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - rt.block_on(async { - db.bootstrap_db().await?; - Ok::<(), crate::Error>(()) - }) - })?; - - let local = db.connect()?; - - if *remote_writes { - let synced = SyncedConnection { - local, - remote: HttpConnection::new_with_connector( - url.clone(), - auth_token.clone(), - connector.clone(), - None, - None, - remote_encryption.clone(), - ), - read_your_writes: *read_your_writes, - context: db.sync_ctx.clone().unwrap(), - state: std::sync::Arc::new(Mutex::new(State::Init)), - }; - - let conn = std::sync::Arc::new(synced); - return Ok(Connection { conn }); - } - - let conn = std::sync::Arc::new(LibsqlConnection { conn: local }); - Ok(Connection { conn }) - } - - #[cfg(feature = "remote")] - DbType::Remote { - url, - auth_token, - connector, - version, - namespace, - remote_encryption, - } => { - let conn = std::sync::Arc::new( - crate::hrana::connection::HttpConnection::new_with_connector( - url, - auth_token, - connector.clone(), - version.as_ref().map(|s| s.as_str()), - namespace.as_ref().map(|s| s.as_str()), - remote_encryption.clone(), - ), - ); - - Ok(Connection { conn }) - } - - _ => unreachable!("no database type set"), - } - } -} - -#[cfg(any( - all(feature = "tls", feature = "replication"), - all(feature = "tls", feature = "remote"), - all(feature = "tls", feature = "sync") -))] -fn connector() -> Result> { - let mut http = hyper::client::HttpConnector::new(); - http.enforce_http(false); - http.set_nodelay(true); - - Ok(hyper_rustls::HttpsConnectorBuilder::new() - .with_native_roots() - .map_err(crate::Error::InvalidTlsConfiguration)? - .https_or_http() - .enable_http1() - .wrap_connector(http)) -} - -#[cfg(any( - all(not(feature = "tls"), feature = "replication"), - all(not(feature = "tls"), feature = "remote"), - all(not(feature = "tls"), feature = "sync") -))] -fn connector() -> Result { - panic!("The `tls` feature is disabled, you must provide your own http connector"); -} - -impl std::fmt::Debug for Database { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Database").finish() - } -} - -#[cfg(any(feature = "remote", feature = "sync"))] -#[derive(Debug, Clone)] -pub enum EncryptionKey { - /// The key is a base64-encoded string. - Base64Encoded(String), - /// The key is a byte array. - Bytes(Vec), -} - -#[cfg(any(feature = "remote", feature = "sync"))] -impl EncryptionKey { - pub fn as_string(&self) -> String { - match self { - EncryptionKey::Base64Encoded(s) => s.clone(), - EncryptionKey::Bytes(b) => general_purpose::STANDARD.encode(b), - } - } -} - -#[cfg(any(feature = "remote", feature = "sync"))] -#[derive(Debug, Clone)] -pub struct EncryptionContext { - /// The base64-encoded key for the encryption, sent on every request. - pub key: EncryptionKey, -} diff --git a/vendor/libsql/src/database/builder.rs b/vendor/libsql/src/database/builder.rs deleted file mode 100644 index e691adc2..00000000 --- a/vendor/libsql/src/database/builder.rs +++ /dev/null @@ -1,873 +0,0 @@ -cfg_core! { - use crate::EncryptionConfig; -} - -use super::DbType; -use crate::{Database, Result}; - -#[cfg(any(feature = "remote", feature = "sync"))] -pub use crate::database::EncryptionContext; - -/// A builder for [`Database`]. This struct can be used to build -/// all variants of [`Database`]. These variants include: -/// -/// - `new_local`/`Local` which means a `Database` that is just a local libsql database -/// it does no networking and does not connect to any remote database. -/// - `new_remote_replica`/`RemoteReplica` creates an embedded replica database that will be able -/// to sync from the remote url and delegate writes to the remote primary. -/// - `new_synced_database`/`SyncedDatabase` creates a database that can be written offline and -/// synced to a remote server. -/// - `new_local_replica`/`LocalReplica` creates an embedded replica similar to the remote version -/// except you must use `Database::sync_frames` to sync with the remote. This version also -/// includes the ability to delegate writes to a remote primary. -/// - `new_remote`/`Remote` creates a database that does not create anything locally but will -/// instead run all queries on the remote database. This is essentially the pure HTTP api. -/// -/// # Note -/// -/// Embedded replicas require a clean database (no database file) or a previously synced database or else it will -/// throw an error to prevent any misuse. To work around this error a user can delete the database -/// and let it resync and create the wal_index metadata file. -pub struct Builder { - inner: T, -} - -impl Builder<()> { - cfg_core! { - /// Create a new local database. - pub fn new_local(path: impl AsRef) -> Builder { - Builder { - inner: Local { - path: path.as_ref().to_path_buf(), - flags: crate::OpenFlags::default(), - encryption_config: None, - skip_safety_assert: false, - }, - } - } - } - - cfg_replication! { - /// Create a new remote embedded replica. - pub fn new_remote_replica( - path: impl AsRef, - url: String, - auth_token: String, - ) -> Builder { - Builder { - inner: RemoteReplica { - path: path.as_ref().to_path_buf(), - remote: Remote { - url, - auth_token, - connector: None, - version: None, - namespace: None, - #[cfg(any(feature = "remote", feature = "sync"))] - remote_encryption: None, - }, - encryption_config: None, - read_your_writes: true, - sync_interval: None, - http_request_callback: None, - skip_safety_assert: false, - #[cfg(feature = "sync")] - sync_protocol: Default::default(), - #[cfg(feature = "sync")] - remote_encryption: None - }, - } - } - - /// Create a new local replica. - pub fn new_local_replica(path: impl AsRef) -> Builder { - Builder { - inner: LocalReplica { - path: path.as_ref().to_path_buf(), - flags: crate::OpenFlags::default(), - remote: None, - encryption_config: None, - http_request_callback: None - }, - } - } - } - - cfg_sync! { - /// Create a database that can be written offline and synced to a remote server. - pub fn new_synced_database( - path: impl AsRef, - url: String, - auth_token: String, - ) -> Builder { - Builder { - inner: SyncedDatabase { - path: path.as_ref().to_path_buf(), - flags: crate::OpenFlags::default(), - remote: Remote { - url, - auth_token, - connector: None, - version: None, - namespace: None, - remote_encryption: None, - }, - connector: None, - read_your_writes: true, - remote_writes: false, - push_batch_size: 0, - sync_interval: None, - remote_encryption: None, - }, - } - } - } - - cfg_remote! { - /// Create a new remote database. - pub fn new_remote(url: String, auth_token: String) -> Builder { - Builder { - inner: Remote { - url, - auth_token, - connector: None, - version: None, - namespace: None, - remote_encryption: None, - }, - } - } - } -} - -cfg_replication_or_remote_or_sync! { - /// Remote configuration type used in [`Builder`]. - pub struct Remote { - url: String, - auth_token: String, - connector: Option, - version: Option, - namespace: Option, - #[cfg(any(feature = "remote", feature = "sync"))] - remote_encryption: Option, - } -} - -cfg_core! { - /// Local database configuration type in [`Builder`]. - pub struct Local { - path: std::path::PathBuf, - flags: crate::OpenFlags, - encryption_config: Option, - skip_safety_assert: bool, - } - - impl Builder { - /// Set [`OpenFlags`] for this database. - pub fn flags(mut self, flags: crate::OpenFlags) -> Builder { - self.inner.flags = flags; - self - } - - /// Set an encryption config that will encrypt the local database. - pub fn encryption_config( - mut self, - encryption_config: EncryptionConfig, - ) -> Builder { - self.inner.encryption_config = Some(encryption_config); - self - } - - /// Skip the saftey assert used to ensure that sqlite3 is configured correctly for the way - /// that libsql uses the ffi code. By default, libsql will try to use the SERIALIZED - /// threadsafe mode for sqlite3. This allows us to implement Send/Sync for all the types to - /// allow them to move between threads safely. Due to the fact that sqlite3 has a global - /// config this may conflict with other sqlite3 connections in the same process. - /// - /// Using this setting is very UNSAFE and you are expected to use the libsql in adherence - /// with the sqlite3 threadsafe rules or else you WILL create undefined behavior. Use at - /// your own risk. - pub unsafe fn skip_safety_assert(mut self, skip: bool) -> Builder { - self.inner.skip_safety_assert = skip; - self - } - - /// Build the local database. - pub async fn build(self) -> Result { - let db = if self.inner.path == std::path::Path::new(":memory:") { - let db = if !self.inner.skip_safety_assert { - crate::local::Database::open(":memory:", crate::OpenFlags::default())? - } else { - unsafe { crate::local::Database::open_raw(":memory:", crate::OpenFlags::default())? } - }; - - Database { - db_type: DbType::Memory { db } , - max_write_replication_index: Default::default(), - } - } else { - let path = self - .inner - .path - .to_str() - .ok_or(crate::Error::InvalidUTF8Path)? - .to_owned(); - - Database { - db_type: DbType::File { - path, - flags: self.inner.flags, - encryption_config: self.inner.encryption_config, - skip_safety_assert: self.inner.skip_safety_assert - }, - max_write_replication_index: Default::default(), - } - }; - - Ok(db) - } - } -} - -cfg_replication! { - /// Remote replica configuration type in [`Builder`]. - pub struct RemoteReplica { - path: std::path::PathBuf, - remote: Remote, - encryption_config: Option, - read_your_writes: bool, - sync_interval: Option, - http_request_callback: Option, - skip_safety_assert: bool, - #[cfg(feature = "sync")] - sync_protocol: super::SyncProtocol, - #[cfg(feature = "sync")] - remote_encryption: Option, - } - - /// Local replica configuration type in [`Builder`]. - pub struct LocalReplica { - path: std::path::PathBuf, - flags: crate::OpenFlags, - remote: Option, - encryption_config: Option, - http_request_callback: Option, - } - - impl Builder { - /// Provide a custom http connector that will be used to create http connections. - pub fn connector(mut self, connector: C) -> Builder - where - C: tower::Service + Send + Clone + Sync + 'static, - C::Response: crate::util::Socket, - C::Future: Send + 'static, - C::Error: Into>, - { - self.inner.remote = self.inner.remote.connector(connector); - self - } - - /// Set an encryption key that will encrypt the local database. - pub fn encryption_config( - mut self, - encryption_config: EncryptionConfig, - ) -> Builder { - self.inner.encryption_config = Some(encryption_config.into()); - self - } - - /// Set whether you want writes to be visible locally before the write query returns. This - /// means that you will be able to read your own writes if this is set to `true`. - /// - /// # Default - /// - /// This defaults to `true`. - pub fn read_your_writes(mut self, read_your_writes: bool) -> Builder { - self.inner.read_your_writes = read_your_writes; - self - } - - /// Set the duration at which the replicator will automatically call `sync` in the - /// background. The sync will continue for the duration that the resulted `Database` - /// type is alive for, once it is dropped the background task will get dropped and stop. - pub fn sync_interval(mut self, duration: std::time::Duration) -> Builder { - self.inner.sync_interval = Some(duration); - self - } - - /// Set the duration at which the replicator will automatically call `sync` in the - /// background. The sync will continue for the duration that the resulted `Database` - /// type is alive for, once it is dropped the background task will get dropped and stop. - #[cfg(feature = "sync")] - pub fn sync_protocol(mut self, protocol: super::SyncProtocol) -> Builder { - self.inner.sync_protocol = protocol; - self - } - - /// Set the encryption context if the database is encrypted in remote server. - #[cfg(feature = "sync")] - pub fn remote_encryption(mut self, encryption_context: EncryptionContext) -> Builder { - self.inner.remote_encryption = Some(encryption_context); - self - } - - pub fn http_request_callback(mut self, f: F) -> Builder - where - F: Fn(&mut http::Request<()>) + Send + Sync + 'static - { - self.inner.http_request_callback = Some(std::sync::Arc::new(f)); - self - - } - - /// Set the namespace that will be communicated to remote replica in the http header. - pub fn namespace(mut self, namespace: impl Into) -> Builder - { - self.inner.remote.namespace = Some(namespace.into()); - self - } - - #[doc(hidden)] - pub fn version(mut self, version: String) -> Builder { - self.inner.remote = self.inner.remote.version(version); - self - } - - /// Skip the safety assert used to ensure that sqlite3 is configured correctly for the way - /// that libsql uses the ffi code. By default, libsql will try to use the SERIALIZED - /// threadsafe mode for sqlite3. This allows us to implement Send/Sync for all the types to - /// allow them to move between threads safely. Due to the fact that sqlite3 has a global - /// config this may conflict with other sqlite3 connections in the same process. - /// - /// Using this setting is very UNSAFE and you are expected to use the libsql in adherence - /// with the sqlite3 threadsafe rules or else you WILL create undefined behavior. Use at - /// your own risk. - pub unsafe fn skip_safety_assert(mut self, skip: bool) -> Builder { - self.inner.skip_safety_assert = skip; - self - } - - /// Build the remote embedded replica database. - pub async fn build(self) -> Result { - let RemoteReplica { - path, - remote: - Remote { - url, - auth_token, - connector, - version, - namespace, - .. - }, - encryption_config, - read_your_writes, - sync_interval, - http_request_callback, - skip_safety_assert, - #[cfg(feature = "sync")] - sync_protocol, - #[cfg(feature = "sync")] - remote_encryption, - } = self.inner; - - let connector = if let Some(connector) = connector { - connector - } else { - let https = super::connector()?; - use tower::ServiceExt; - - let svc = https - .map_err(|e| e.into()) - .map_response(|s| Box::new(s) as Box); - - crate::util::ConnectorService::new(svc) - }; - - #[cfg(feature = "sync")] - { - use super::SyncProtocol; - match sync_protocol { - p @ (SyncProtocol::Auto | SyncProtocol::V2) => { - tracing::trace!("Probing for sync protocol version for {}", url); - let client = hyper::client::Client::builder() - .build::<_, hyper::Body>(connector.clone()); - - let prefix = if url.starts_with("libsql://") { - url.replacen("libsql://", "https://", 1) - } else { - url.to_string() - }; - let req = http::Request::get(format!("{prefix}/info")) - .header("Authorization", format!("Bearer {}", auth_token)); - - let req = if let Some(ref remote_encryption) = remote_encryption { - req.header("x-turso-encryption-key", remote_encryption.key.as_string()) - } else { - req - }; - let req = req.body(hyper::Body::empty()).unwrap(); - - let res = client - .request(req) - .await - .map_err(|err| crate::Error::Sync(err.into()))?; - - tracing::trace!("Probe for sync protocol version for {} returned status {}", url, res.status()); - - if res.status() == http::StatusCode::UNAUTHORIZED { - return Err(crate::Error::Sync("Unauthorized".into())); - } - - if matches!(p, SyncProtocol::V2) { - if !res.status().is_success() { - let status = res.status(); - let body_bytes = hyper::body::to_bytes(res.into_body()) - .await - .map_err(|err| crate::Error::Sync(err.into()))?; - let error_message = String::from_utf8_lossy(&body_bytes); - return Err(crate::Error::Sync(format!("HTTP error {}: {}", status, error_message).into())); - } - } - - if res.status().is_success() { - tracing::trace!("Using sync protocol v2 for {}", url); - let mut builder = Builder::new_synced_database(path, url, auth_token) - .connector(connector) - .remote_writes(true) - .read_your_writes(read_your_writes); - - if let Some(encryption) = remote_encryption { - builder = builder.remote_encryption(encryption); - } - - let builder = if let Some(sync_interval) = sync_interval { - builder.sync_interval(sync_interval) - } else { - builder - }; - - return builder.build().await; - } - tracing::trace!("Using sync protocol v1 for {} based on probe results", url); - } - SyncProtocol::V1 => { - tracing::trace!("Using sync protocol v1 for {}", url); - } - } - } - - let path = path.to_str().ok_or(crate::Error::InvalidUTF8Path)?.to_owned(); - - let db = if !skip_safety_assert { - crate::local::Database::open_http_sync_internal( - connector, - path, - url, - auth_token, - version, - read_your_writes, - encryption_config.clone(), - sync_interval, - http_request_callback, - namespace, - ) - .await? - } else { - // SAFETY: this can only be enabled via the unsafe config function - // `skip_safety_assert`. - unsafe { - crate::local::Database::open_http_sync_internal2( - connector, - path, - url, - auth_token, - version, - read_your_writes, - encryption_config.clone(), - sync_interval, - http_request_callback, - namespace, - ) - .await? - } - - }; - - - Ok(Database { - db_type: DbType::Sync { - db, - encryption_config, - }, - max_write_replication_index: Default::default(), - }) - } - } - - impl Builder { - /// Set [`OpenFlags`] for this database. - pub fn flags(mut self, flags: crate::OpenFlags) -> Builder { - self.inner.flags = flags; - self - } - - pub fn http_request_callback(mut self, f: F) -> Builder - where - F: Fn(&mut http::Request<()>) + Send + Sync + 'static - { - self.inner.http_request_callback = Some(std::sync::Arc::new(f)); - self - - } - - /// Build the local embedded replica database. - pub async fn build(self) -> Result { - let LocalReplica { - path, - flags, - remote, - encryption_config, - http_request_callback - } = self.inner; - - let path = path.to_str().ok_or(crate::Error::InvalidUTF8Path)?.to_owned(); - - let db = if let Some(Remote { - url, - auth_token, - connector, - version, - namespace, - .. - }) = remote - { - let connector = if let Some(connector) = connector { - connector - } else { - let https = super::connector()?; - use tower::ServiceExt; - - let svc = https - .map_err(|e| e.into()) - .map_response(|s| Box::new(s) as Box); - - crate::util::ConnectorService::new(svc) - }; - - crate::local::Database::open_local_sync_remote_writes( - connector, - path, - url, - auth_token, - version, - flags, - encryption_config.clone(), - http_request_callback, - namespace, - ) - .await? - } else { - crate::local::Database::open_local_sync(path, flags, encryption_config.clone()).await? - }; - - Ok(Database { - db_type: DbType::Sync { db, encryption_config }, - max_write_replication_index: Default::default(), - }) - } - } -} - -cfg_sync! { - /// Remote replica configuration type in [`Builder`]. - pub struct SyncedDatabase { - path: std::path::PathBuf, - flags: crate::OpenFlags, - remote: Remote, - connector: Option, - remote_writes: bool, - read_your_writes: bool, - push_batch_size: u32, - sync_interval: Option, - remote_encryption: Option, - } - - impl Builder { - #[doc(hidden)] - pub fn version(mut self, version: String) -> Builder { - self.inner.remote = self.inner.remote.version(version); - self - } - - pub fn read_your_writes(mut self, v: bool) -> Builder { - self.inner.read_your_writes = v; - self - } - - pub fn remote_writes(mut self, v: bool) -> Builder { - self.inner.remote_writes = v; - self - } - - pub fn set_push_batch_size(mut self, v: u32) -> Builder { - self.inner.push_batch_size = v; - self - } - - /// Set the duration at which the replicator will automatically call `sync` in the - /// background. The sync will continue for the duration that the resulted `Database` - /// type is alive for, once it is dropped the background task will get dropped and stop. - pub fn sync_interval(mut self, duration: std::time::Duration) -> Builder { - self.inner.sync_interval = Some(duration); - self - } - - /// Set the encryption context if the database is encrypted in remote server. - pub fn remote_encryption(mut self, encryption_context: EncryptionContext) -> Builder { - self.inner.remote_encryption = Some(encryption_context); - self - } - - /// Provide a custom http connector that will be used to create http connections. - pub fn connector(mut self, connector: C) -> Builder - where - C: tower::Service + Send + Clone + Sync + 'static, - C::Response: crate::util::Socket, - C::Future: Send + 'static, - C::Error: Into>, - { - self.inner.connector = Some(wrap_connector(connector)); - self - } - - /// Build a connection to a local database that can be synced to remote server. - pub async fn build(self) -> Result { - use tracing::Instrument as _; - - let SyncedDatabase { - path, - flags, - remote: - Remote { - url, - auth_token, - connector: _, - version: _, - namespace: _, - .. - }, - connector, - remote_writes, - read_your_writes, - push_batch_size, - sync_interval, - remote_encryption, - } = self.inner; - - let path = path.to_str().ok_or(crate::Error::InvalidUTF8Path)?.to_owned(); - - let https = if let Some(connector) = connector { - connector - } else { - wrap_connector(super::connector()?) - }; - use tower::ServiceExt; - - let svc = https - .map_err(|e| e.into()) - .map_response(|s| Box::new(s) as Box); - - let connector = crate::util::ConnectorService::new(svc); - - let db = crate::local::Database::open_local_with_offline_writes( - connector.clone(), - path, - flags, - url.clone(), - auth_token.clone(), - remote_encryption.clone(), - ) - .await?; - - if push_batch_size > 0 { - db.sync_ctx.as_ref().unwrap().lock().await.set_push_batch_size(push_batch_size); - } - - let mut bg_abort: Option> = None; - - - if let Some(sync_interval) = sync_interval { - let (cancel_tx, mut cancel_rx) = tokio::sync::oneshot::channel::<()>(); - - let sync_span = tracing::debug_span!("sync_interval"); - let _enter = sync_span.enter(); - - let sync_ctx = db.sync_ctx.as_ref().unwrap().clone(); - { - let mut ctx = sync_ctx.lock().await; - crate::sync::bootstrap_db(&mut ctx).await?; - tracing::debug!("finished bootstrap with sync interval"); - } - - // db.connect creates a local db file, so it is important that we always call - // `bootstrap_db` (for synced dbs) before calling connect. Otherwise, the sync - // protocol skips calling `export` endpoint causing slowdown in initial bootstrap. - let conn = db.connect()?; - - tokio::spawn( - async move { - let mut interval = tokio::time::interval(sync_interval); - - loop { - tokio::select! { - _ = &mut cancel_rx => break, - _ = interval.tick() => { - tracing::debug!("trying to sync"); - - let mut ctx = sync_ctx.lock().await; - - let result = if remote_writes { - crate::sync::try_pull(&mut ctx, &conn).await - } else { - crate::sync::sync_offline(&mut ctx, &conn).await - }; - - if let Err(e) = result { - tracing::error!("Error syncing database: {}", e); - } - } - } - } - } - .instrument(tracing::debug_span!("sync interval thread")), - ); - - bg_abort.replace(std::sync::Arc::new(crate::sync::DropAbort(Some(cancel_tx)))); - } - - Ok(Database { - db_type: DbType::Offline { - db, - remote_writes, - read_your_writes, - url, - auth_token, - connector, - _bg_abort: bg_abort, - #[cfg(feature = "sync")] - remote_encryption, - }, - max_write_replication_index: Default::default(), - }) - } - } -} - -cfg_remote! { - impl Builder { - /// Provide a custom http connector that will be used to create http connections. - pub fn connector(mut self, connector: C) -> Builder - where - C: tower::Service + Send + Clone + Sync + 'static, - C::Response: crate::util::Socket, - C::Future: Send + 'static, - C::Error: Into>, - { - self.inner = self.inner.connector(connector); - self - } - - #[doc(hidden)] - pub fn version(mut self, version: String) -> Builder { - self.inner = self.inner.version(version); - self - } - - /// Set the namespace that will be communicated to the remote in the http header. - pub fn namespace(mut self, namespace: impl Into) -> Builder - { - self.inner.namespace = Some(namespace.into()); - self - } - - /// Set the encryption context if the database is encrypted in remote server. - pub fn remote_encryption(mut self, encryption_context: EncryptionContext) -> Builder { - self.inner.remote_encryption = Some(encryption_context); - self - } - - /// Build the remote database client. - pub async fn build(self) -> Result { - let Remote { - url, - auth_token, - connector, - version, - namespace, - remote_encryption, - } = self.inner; - - let connector = if let Some(connector) = connector { - connector - } else { - let https = super::connector()?; - use tower::ServiceExt; - - let svc = https - .map_err(|e| e.into()) - .map_response(|s| Box::new(s) as Box); - - crate::util::ConnectorService::new(svc) - }; - - Ok(Database { - db_type: DbType::Remote { - url, - auth_token, - connector, - version, - namespace, - remote_encryption - }, - max_write_replication_index: Default::default(), - }) - } - } -} - -cfg_replication_or_remote_or_sync! { - fn wrap_connector(connector: C) -> crate::util::ConnectorService - where - C: tower::Service + Send + Clone + Sync + 'static, - C::Response: crate::util::Socket, - C::Future: Send + 'static, - C::Error: Into>, - { - use tower::ServiceExt; - - let svc = connector - .map_err(|e| e.into()) - .map_response(|s| Box::new(s) as Box); - - crate::util::ConnectorService::new(svc) - } - - impl Remote { - fn connector(mut self, connector: C) -> Remote - where - C: tower::Service + Send + Clone + Sync + 'static, - C::Response: crate::util::Socket, - C::Future: Send + 'static, - C::Error: Into>, - { - self.connector = Some(wrap_connector(connector)); - self - } - - fn version(mut self, version: String) -> Remote { - self.version = Some(version); - self - } - } -} diff --git a/vendor/libsql/src/de.rs b/vendor/libsql/src/de.rs deleted file mode 100644 index 44f231c1..00000000 --- a/vendor/libsql/src/de.rs +++ /dev/null @@ -1,86 +0,0 @@ -//! Deserialization utilities. - -use crate::{Row, Value}; -use serde::de::{value::Error as DeError, Error, IntoDeserializer, MapAccess, Visitor}; -use serde::{Deserialize, Deserializer}; - -struct RowDeserializer<'de> { - row: &'de Row, -} - -impl<'de> Deserializer<'de> for RowDeserializer<'de> { - type Error = DeError; - - fn deserialize_any(self, _visitor: V) -> Result - where - V: Visitor<'de>, - { - Err(DeError::custom("Expects a struct")) - } - - fn deserialize_struct( - self, - _name: &'static str, - _fields: &'static [&'static str], - visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - struct RowMapAccess<'a> { - row: &'a Row, - idx: std::ops::Range, - value: Option, - } - - impl<'de> MapAccess<'de> for RowMapAccess<'de> { - type Error = DeError; - - fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> - where - K: serde::de::DeserializeSeed<'de>, - { - match self.idx.next() { - None => Ok(None), - Some(i) => { - let value = self.row.get_value(i as i32).map_err(DeError::custom)?; - self.value = Some(value); - self.row - .column_name(i as i32) - .map(|name| seed.deserialize(name.into_deserializer())) - .transpose() - } - } - } - - fn next_value_seed(&mut self, seed: V) -> Result - where - V: serde::de::DeserializeSeed<'de>, - { - let value = self - .value - .take() - .ok_or(DeError::custom("Expects a value but row is exhausted"))?; - - seed.deserialize(value.into_deserializer()) - } - } - - visitor.visit_map(RowMapAccess { - row: self.row, - idx: 0..(self.row.inner.column_count() as usize), - value: None, - }) - } - - serde::forward_to_deserialize_any! { - bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string - bytes byte_buf option unit unit_struct newtype_struct seq tuple - tuple_struct map enum identifier ignored_any - } -} - -pub fn from_row<'de, T: Deserialize<'de>>(row: &'de Row) -> Result { - let de = RowDeserializer { row }; - T::deserialize(de) -} diff --git a/vendor/libsql/src/errors.rs b/vendor/libsql/src/errors.rs deleted file mode 100644 index f612aa62..00000000 --- a/vendor/libsql/src/errors.rs +++ /dev/null @@ -1,119 +0,0 @@ -#[derive(Debug, thiserror::Error)] -#[non_exhaustive] -pub enum Error { - #[error("Failed to connect to database: `{0}`")] - ConnectionFailed(String), - #[error("SQLite failure: `{1}`")] - SqliteFailure(std::ffi::c_int, String), - #[error("Null value")] - NullValue, // Not in rusqlite - #[error("API misuse: `{0}`")] - Misuse(String), // Not in rusqlite - #[error("Execute returned rows")] - ExecuteReturnedRows, - #[error("Query returned no rows")] - QueryReturnedNoRows, - #[error("Invalid column name: `{0}`")] - InvalidColumnName(String), - #[error("SQL conversion failure: `{0}`")] - ToSqlConversionFailure(crate::BoxError), - #[error("Sync is not supported in databases opened in {0} mode.")] - SyncNotSupported(String), // Not in rusqlite - #[error("Loading extension is only supported in local databases.")] - LoadExtensionNotSupported, // Not in rusqlite - #[error("Authorizer is only supported in local databases.")] - AuthorizerNotSupported, // Not in rusqlite - #[error("Update hooks are only supported in local databases.")] - UpdateHookNotSupported, // Not in rusqlite - #[error("Column not found: {0}")] - ColumnNotFound(i32), // Not in rusqlite - #[error("Hrana: `{0}`")] - Hrana(crate::BoxError), // Not in rusqlite - #[error("Write delegation: `{0}`")] - WriteDelegation(crate::BoxError), // Not in rusqlite - #[error("bincode: `{0}`")] - Bincode(crate::BoxError), - #[error("invalid column index")] - InvalidColumnIndex, - #[error("invalid column type")] - InvalidColumnType, - #[error("syntax error around L{0}:{1}: `{2}`")] - Sqlite3SyntaxError(u64, usize, String), - #[error("unsupported statement")] - Sqlite3UnsupportedStatement, - #[error("sqlite3 parser error: `{0}`")] - Sqlite3ParserError(crate::BoxError), - #[error("Remote SQlite failure: `{0}:{1}:{2}`")] - RemoteSqliteFailure(i32, i32, String), - #[error("replication error: {0}")] - Replication(crate::BoxError), - #[error("path has invalid UTF-8")] - InvalidUTF8Path, - #[error("freeze is not supported in {0} mode.")] - FreezeNotSupported(String), - #[error("connection has reached an invalid state, started with {0}")] - InvalidParserState(String), - #[error("TLS error: {0}")] - InvalidTlsConfiguration(std::io::Error), - #[error("Transactional batch error: {0}")] - TransactionalBatchError(String), - #[error("Invalid blob size, expected {0}")] - InvalidBlobSize(usize), - #[error("sync error: {0}")] - Sync(crate::BoxError), - #[error("WAL frame insert conflict")] - WalConflict, - #[error("Reserved bytes not supported")] - ReservedBytesNotSupported, -} - -#[cfg(feature = "hrana")] -impl From for Error { - fn from(e: crate::hrana::HranaError) -> Self { - Error::Hrana(e.into()) - } -} - -#[cfg(feature = "sync")] -impl From for Error { - fn from(e: crate::sync::SyncError) -> Self { - Error::Sync(e.into()) - } -} - -impl From for Error { - fn from(_: std::convert::Infallible) -> Self { - unreachable!() - } -} - -#[cfg(feature = "core")] -pub(crate) fn error_from_handle(raw: *mut libsql_sys::ffi::sqlite3) -> String { - let errmsg = unsafe { libsql_sys::ffi::sqlite3_errmsg(raw) }; - sqlite_errmsg_to_string(errmsg) -} - -#[cfg(feature = "core")] -pub(crate) fn extended_error_code(raw: *mut libsql_sys::ffi::sqlite3) -> std::ffi::c_int { - unsafe { libsql_sys::ffi::sqlite3_extended_errcode(raw) } -} - -#[cfg(feature = "core")] -pub fn error_from_code(code: i32) -> String { - let errmsg = unsafe { libsql_sys::ffi::sqlite3_errstr(code) }; - sqlite_errmsg_to_string(errmsg) -} - -#[cfg(feature = "core")] -#[allow(clippy::not_unsafe_ptr_arg_deref)] -pub fn sqlite_errmsg_to_string(errmsg: *const std::ffi::c_char) -> String { - let errmsg = unsafe { std::ffi::CStr::from_ptr(errmsg) }.to_bytes(); - String::from_utf8_lossy(errmsg).to_string() -} - -#[cfg(feature = "replication")] -impl From for Error { - fn from(e: bincode::Error) -> Self { - Error::Bincode(e.into()) - } -} diff --git a/vendor/libsql/src/hrana/connection.rs b/vendor/libsql/src/hrana/connection.rs deleted file mode 100644 index e6030ff7..00000000 --- a/vendor/libsql/src/hrana/connection.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::hrana::stream::{parse_hrana_urls, HranaStream}; -use crate::hrana::{HttpSend, Statement}; -use crate::util::coerce_url_scheme; -use std::ops::Deref; -use std::sync::Arc; - -#[derive(Debug)] -pub struct HttpConnection(Arc>) -where - T: HttpSend; - -#[derive(Debug)] -struct InnerClient -where - T: HttpSend, -{ - /// Actual implementation of a client used to send HTTP requests. - inner: T, - /// Hrana stream used to execute statements directly on the connection itself. - conn_stream: HranaStream, - /// URL of a pipeline API: `{base_url}/v3/pipeline`. - pipeline_url: Arc, - /// URL of a cursor API: `{base_url}/v3/cursor`. - cursor_url: Arc, - /// Authentication token. - auth: Arc, -} - -impl HttpConnection -where - T: HttpSend + Send + Sync + 'static, -{ - pub fn new(url: String, token: String, inner: T) -> Self { - // The `libsql://` protocol is an alias for `https://`. - let base_url = coerce_url_scheme(url); - let (pipeline_url, cursor_url) = parse_hrana_urls(&base_url); - let auth: Arc = Arc::from(format!("Bearer {token}")); - let conn_stream = HranaStream::open( - inner.clone(), - pipeline_url.clone(), - cursor_url.clone(), - auth.clone(), - ); - HttpConnection(Arc::new(InnerClient { - inner, - pipeline_url, - cursor_url, - conn_stream, - auth, - })) - } - - pub fn affected_row_count(&self) -> u64 { - self.current_stream().affected_row_count() - } - - pub fn total_changes(&self) -> u64 { - self.current_stream().total_changes() - } - - pub fn last_insert_rowid(&self) -> i64 { - self.current_stream().last_insert_rowid() - } - - pub fn is_autocommit(&self) -> bool { - self.current_stream().is_autocommit() - } - - pub(crate) fn current_stream(&self) -> &HranaStream { - &self.0.conn_stream - } - - pub(crate) fn open_stream(&self) -> HranaStream { - let client = self.0.deref(); - HranaStream::open( - client.inner.clone(), - client.pipeline_url.clone(), - client.cursor_url.clone(), - client.auth.clone(), - ) - } - - pub async fn prepare(&self, sql: &str) -> crate::Result> { - let stream = self.current_stream().clone(); - Statement::new(stream, sql.to_string(), true).await - } -} - -impl Clone for HttpConnection -where - T: HttpSend, -{ - fn clone(&self) -> Self { - HttpConnection(self.0.clone()) - } -} - -pub(crate) enum CommitBehavior { - Commit, - Rollback, -} diff --git a/vendor/libsql/src/hrana/cursor.rs b/vendor/libsql/src/hrana/cursor.rs deleted file mode 100644 index aa0b1191..00000000 --- a/vendor/libsql/src/hrana/cursor.rs +++ /dev/null @@ -1,490 +0,0 @@ -// https://github.com/tursodatabase/libsql/blob/main/docs/HRANA_3_SPEC.md#cursor-entries - -use crate::hrana::proto::{Batch, BatchResult, Col, StmtResult, Value}; -use crate::hrana::{CursorResponseError, HranaError, Result, Row}; -use bytes::Bytes; -use futures::{ready, Stream, StreamExt}; -use serde::{Deserialize, Serialize}; -use std::fmt::Formatter; -use std::future::poll_fn; -use std::pin::Pin; -use std::sync::Arc; -use std::task::{Context, Poll}; -use tokio::io::{AsyncBufReadExt, Lines}; -use tokio_util::io::StreamReader; - -#[derive(Serialize, Debug)] -pub struct CursorReq { - pub baton: Option, - pub batch: Batch, -} - -#[derive(Deserialize, Debug)] -pub struct CursorResp { - pub baton: Option, - pub base_url: Option, -} - -#[derive(Deserialize, Debug)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum CursorEntry { - StepBegin(StepBeginEntry), - StepEnd(StepEndEntry), - StepError(StepErrorEntry), - Row(RowEntry), - Error(ErrorEntry), -} - -#[derive(Deserialize, Debug)] -pub struct StepBeginEntry { - pub step: u32, - pub cols: Vec, -} - -#[derive(Deserialize, Debug)] -pub struct StepEndEntry { - pub affected_row_count: u32, - pub last_inserted_rowid: Option, -} - -#[derive(Deserialize, Debug)] -pub struct RowEntry { - pub row: Vec, -} - -#[derive(Deserialize, Debug)] -pub struct StepErrorEntry { - pub step: u32, - pub error: Error, -} - -#[derive(Deserialize, Debug)] -pub struct Error { - pub message: String, - pub code: String, -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "(error code: {}) `{}`", self.code, self.message) - } -} - -#[derive(Deserialize, Debug)] -pub struct ErrorEntry { - pub error: String, -} - -pub struct Cursor { - stream: Lines>, -} - -impl Cursor -where - S: Stream> + Unpin, -{ - pub(super) async fn open(stream: S) -> Result<(Self, CursorResp)> { - let stream = StreamReader::new(stream).lines(); - let mut cursor = Cursor { stream }; - match cursor.next_line().await? { - None => Err(HranaError::CursorError(CursorResponseError::CursorClosed)), - Some(line) => { - let response: CursorResp = serde_json::from_str(&line)?; - Ok((cursor, response)) - } - } - } - - pub async fn into_batch_result(mut self) -> Result { - use serde::de::Error; - //FIXME: this is for the compatibility with the current libsql client API, - // which expects BatchResult to be returned - let mut step_results = Vec::new(); - let mut step_errors = Vec::new(); - - while let Ok(mut step) = self.next_step().await { - let cols = step.state.cols.to_vec(); - let mut rows = Vec::new(); - while let Some(res) = step.next().await { - match res { - Ok(row) => { - rows.push(crate::hrana::proto::Row { values: row.inner }); - } - Err(err) => step_errors.push(Some(crate::hrana::proto::Error { - message: err.to_string(), - code: String::default(), - })), - } - } - let affected_row_count = step.affected_rows() as u64; - let last_insert_rowid = if let Some(rowid) = step.last_inserted_rowid() { - let id = rowid.parse::().map_err(|_| { - serde_json::Error::invalid_value( - serde::de::Unexpected::Str(rowid), - &"decimal integer as a string", - ) - })?; - Some(id) - } else { - None - }; - let step_res = StmtResult { - cols, - rows, - affected_row_count, - last_insert_rowid, - replication_index: None, - rows_read: 0, - rows_written: 0, - query_duration_ms: 0.0, - }; - step_results.push(Some(step_res)); - } - Ok(BatchResult { - step_results, - step_errors, - replication_index: None, - }) - } - - pub async fn next_step(&mut self) -> Result> { - CursorStep::new(self).await - } - - pub async fn next_step_owned(self) -> Result> { - OwnedCursorStep::new(self).await - } - - pub async fn next_line(&mut self) -> Result> { - let mut pin = Pin::new(self); - poll_fn(move |cx| pin.as_mut().poll_next_line(cx)).await - } - - fn poll_next_line(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll>> { - let ret = ready!(Pin::new(&mut self.stream).poll_next_line(cx)) - .map_err(|e| HranaError::CursorError(CursorResponseError::Other(e.to_string()))); - Poll::Ready(ret) - } -} - -impl Stream for Cursor -where - S: Stream> + Unpin, -{ - type Item = Result; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let res = ready!(self.poll_next_line(cx)); - match res { - Err(e) => Poll::Ready(Some(Err(e))), - Ok(None) => Poll::Ready(None), - Ok(Some(line)) => { - let entry: CursorEntry = match serde_json::from_str(line.as_str()) { - Ok(entry) => entry, - Err(e) => return Poll::Ready(Some(Err(e.into()))), - }; - Poll::Ready(Some(Ok(entry))) - } - } - } -} - -pub struct OwnedCursorStep { - cursor: Option>, - state: CursorStepState, -} - -impl OwnedCursorStep -where - S: Stream> + Unpin, -{ - async fn new(mut cursor: Cursor) -> Result { - let begin = get_next_step(&mut cursor).await?; - Ok(OwnedCursorStep { - cursor: Some(cursor), - state: CursorStepState { - cols: begin.cols.into(), - step_no: begin.step, - affected_rows: 0, - last_inserted_rowid: None, - }, - }) - } - - pub fn cursor(&self) -> Option<&Cursor> { - self.cursor.as_ref() - } - - pub fn cursor_mut(&mut self) -> Option<&mut Cursor> { - self.cursor.as_mut() - } - - /// Consume and discard all rows, fast running current cursor step to the end. - pub async fn consume(&mut self) -> Result<()> { - while let Some(res) = self.next().await { - res?; - } - self.cursor.take(); - Ok(()) - } - - pub fn cols(&self) -> &[Col] { - &self.state.cols - } - - pub fn step_no(&self) -> u32 { - self.state.step_no - } - - pub fn affected_rows(&self) -> u32 { - self.state.affected_rows - } - - pub fn last_inserted_rowid(&self) -> Option<&str> { - self.state.last_inserted_rowid.as_deref() - } -} - -impl Stream for OwnedCursorStep -where - S: Stream> + Unpin, -{ - type Item = Result; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let cursor = if let Some(cursor) = &mut self.cursor { - Pin::new(cursor) - } else { - return Poll::Ready(None); - }; - match ready!(cursor.poll_next(cx)) { - None => { - self.cursor = None; - Poll::Ready(None) - } - Some(Err(e)) => Poll::Ready(Some(Err(HranaError::CursorError( - CursorResponseError::Other(e.to_string()), - )))), - Some(Ok(entry)) => { - let result = self.state.update(entry); - if result.is_none() { - self.cursor.take(); - } - Poll::Ready(result) - } - } - } -} - -pub struct CursorStep<'a, S> { - cursor: Option<&'a mut Cursor>, - state: CursorStepState, -} - -impl<'a, S> CursorStep<'a, S> -where - S: Stream> + Unpin, -{ - async fn new(cursor: &'a mut Cursor) -> Result> { - let begin = get_next_step(cursor).await?; - Ok(CursorStep { - cursor: Some(cursor), - state: CursorStepState { - cols: begin.cols.into(), - step_no: begin.step, - affected_rows: 0, - last_inserted_rowid: None, - }, - }) - } - - /// Consume and discard all rows, fast running current cursor step to the end. - pub async fn consume(&mut self) -> Result<()> { - while let Some(res) = self.next().await { - res?; - } - Ok(()) - } - - pub fn cols(&self) -> &[Col] { - &self.state.cols - } - - pub fn step_no(&self) -> u32 { - self.state.step_no - } - - pub fn affected_rows(&self) -> u32 { - self.state.affected_rows - } - - pub fn last_inserted_rowid(&self) -> Option<&str> { - self.state.last_inserted_rowid.as_deref() - } -} - -async fn get_next_step(cursor: &mut Cursor) -> Result -where - S: Stream> + Unpin, -{ - let mut begin = None; - while let Some(res) = cursor.next().await { - match res? { - CursorEntry::StepBegin(entry) => { - begin = Some(entry); - break; - } - CursorEntry::Row(_) => { - tracing::trace!("skipping over row message for previous cursor step") - } - CursorEntry::StepEnd(_) => { - tracing::debug!("skipping over StepEnd message for previous cursor step") - } - CursorEntry::StepError(e) => { - return Err(HranaError::CursorError(CursorResponseError::StepError { - step: e.step, - error: e.error, - })) - } - CursorEntry::Error(e) => { - return Err(HranaError::CursorError(CursorResponseError::Other(e.error))) - } - } - } - if let Some(begin) = begin { - tracing::trace!("begin cursor step: {}", begin.step); - Ok(begin) - } else { - Err(HranaError::CursorError(CursorResponseError::CursorClosed)) - } -} - -impl<'a, S> Stream for CursorStep<'a, S> -where - S: Stream> + Unpin, -{ - type Item = Result; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let cursor = if let Some(cursor) = &mut self.cursor { - Pin::new(cursor) - } else { - return Poll::Ready(None); - }; - match ready!(cursor.poll_next(cx)) { - None => { - self.cursor = None; - Poll::Ready(None) - } - Some(Err(e)) => Poll::Ready(Some(Err(e))), - Some(Ok(entry)) => { - let result = self.state.update(entry); - if result.is_none() { - self.cursor.take(); - } - Poll::Ready(result) - } - } - } -} - -#[derive(Debug)] -struct CursorStepState { - cols: Arc<[Col]>, - step_no: u32, - affected_rows: u32, - last_inserted_rowid: Option, -} - -impl CursorStepState { - fn update(&mut self, entry: CursorEntry) -> Option> { - match entry { - CursorEntry::Row(row) => { - let row = Row::new(self.cols.clone(), row.row); - Some(Ok(row)) - } - CursorEntry::StepEnd(end) => { - self.affected_rows = end.affected_row_count; - self.last_inserted_rowid = end.last_inserted_rowid; - None - } - CursorEntry::StepBegin(begin) => Some(Err(HranaError::CursorError( - CursorResponseError::NotClosed { - expected: self.step_no, - actual: begin.step, - }, - ))), - CursorEntry::StepError(e) => Some(Err(HranaError::CursorError( - CursorResponseError::StepError { - step: e.step, - error: e.error, - }, - ))), - CursorEntry::Error(e) => Some(Err(HranaError::CursorError( - CursorResponseError::Other(e.error), - ))), - } - } -} - -#[cfg(test)] -mod test { - use crate::hrana::cursor::Cursor; - use crate::rows::RowInner; - use crate::Value; - use bytes::Bytes; - use futures::{Stream, StreamExt}; - use serde_json::json; - - type ByteStream = Box> + Unpin>; - - fn byte_stream(entries: impl IntoIterator) -> ByteStream { - let mut payload = Vec::new(); - const NEW_LINE: &[u8] = "\n".as_bytes(); - for v in entries.into_iter() { - serde_json::to_writer(&mut payload, &v).unwrap(); - payload.extend_from_slice(NEW_LINE); - } - let chunks: Vec<_> = Bytes::from(payload) - .chunks(23) - .map(|chunk| Ok(Bytes::copy_from_slice(chunk))) - .collect(); - let stream = futures::stream::iter(chunks); - Box::new(stream) - } - #[tokio::test] - async fn cursor_streaming() { - let byte_stream = byte_stream(vec![ - json!({"baton": null, "base_url": null}), - json!({"type": "step_begin", "step": 0, "cols": [{"name": "id"}, {"name": "email"}]}), - json!({"type": "row", "row": [{"type": "integer", "value": "1"}, {"type": "text", "value": "alice@test.com"}]}), - json!({"type": "row", "row": [{"type": "integer", "value": "2"}, {"type": "text", "value": "bob@test.com"}]}), - json!({"type": "step_end", "affected_row_count": 0, "last_insert_rowid": null}), - ]); - let (mut cursor, resp) = Cursor::open(byte_stream).await.unwrap(); - assert_eq!(resp.baton, None); - assert_eq!(resp.base_url, None); - - let mut step = cursor.next_step().await.unwrap(); - assert_eq!(step.step_no(), 0); - { - let cols: Vec<_> = step - .cols() - .iter() - .map(|col| col.name.as_deref().unwrap_or("")) - .collect(); - assert_eq!(cols, vec!["id", "email"]); - } - - let row = step.next().await.unwrap().unwrap(); - assert_eq!(row.column_value(0).unwrap(), Value::from(1)); - assert_eq!(row.column_value(1).unwrap(), Value::from("alice@test.com")); - - let row = step.next().await.unwrap().unwrap(); - assert_eq!(row.column_value(0).unwrap(), Value::from(2)); - assert_eq!(row.column_value(1).unwrap(), Value::from("bob@test.com")); - - let row = step.next().await; - assert!(row.is_none(), "last row should be None: {:?}", row); - } -} diff --git a/vendor/libsql/src/hrana/hyper.rs b/vendor/libsql/src/hrana/hyper.rs deleted file mode 100644 index 300602c2..00000000 --- a/vendor/libsql/src/hrana/hyper.rs +++ /dev/null @@ -1,446 +0,0 @@ -use crate::connection::{BatchRows, Conn}; -use crate::hrana::connection::HttpConnection; -use crate::hrana::proto::{Batch, Stmt}; -use crate::hrana::stream::HranaStream; -use crate::hrana::transaction::{HttpTransaction, TxScopeCounter}; -use crate::hrana::{bind_params, unwrap_err, HranaError, HttpSend, Result}; -use crate::params::Params; -use crate::transaction::Tx; -use crate::util::ConnectorService; -use crate::{Error, Rows, Statement}; -use bytes::Bytes; -use futures::future::BoxFuture; -use futures::{Stream, TryStreamExt}; -use http::header::AUTHORIZATION; -use http::{HeaderValue, StatusCode}; -use hyper::body::HttpBody; -use std::io::ErrorKind; -use std::sync::Arc; -use std::time::Duration; - -use super::StmtResultRows; - -pub type ByteStream = Box> + Send + Sync + Unpin>; - -#[derive(Clone, Debug)] -pub struct HttpSender { - inner: hyper::Client, - version: HeaderValue, - namespace: Option, - #[cfg(any(feature = "remote", feature = "sync"))] - remote_encryption: Option, -} - -impl HttpSender { - pub fn new( - connector: ConnectorService, - version: Option<&str>, - namespace: Option<&str>, - #[cfg(any(feature = "remote", feature = "sync"))] remote_encryption: Option< - crate::database::EncryptionContext, - >, - ) -> Self { - let ver = version.unwrap_or(env!("CARGO_PKG_VERSION")); - - let version = HeaderValue::try_from(format!("libsql-remote-{ver}")).unwrap(); - let namespace = namespace.map(|v| HeaderValue::try_from(v).unwrap()); - - let inner = hyper::Client::builder().build(connector); - Self { - inner, - version, - namespace, - #[cfg(any(feature = "remote", feature = "sync"))] - remote_encryption, - } - } - - async fn send( - self, - url: Arc, - auth: Arc, - body: String, - ) -> Result> { - let mut req_builder = hyper::Request::post(url.as_ref()) - .header(AUTHORIZATION, auth.as_ref()) - .header("x-libsql-client-version", self.version.clone()); - - if let Some(namespace) = self.namespace { - req_builder = req_builder.header("x-namespace", namespace); - } - - #[cfg(any(feature = "remote", feature = "sync"))] - if let Some(remote_encryption) = &self.remote_encryption { - req_builder = - req_builder.header("x-turso-encryption-key", remote_encryption.key.as_string()); - } - - let req = req_builder - .body(hyper::Body::from(body)) - .map_err(|err| HranaError::Http(format!("{:?}", err)))?; - - let resp = self.inner.request(req).await.map_err(HranaError::from)?; - - let status = resp.status(); - if status != StatusCode::OK { - let body = hyper::body::to_bytes(resp.into_body()) - .await - .map_err(HranaError::from)?; - let body = String::from_utf8(body.into()).unwrap(); - return Err(HranaError::Api(format!("status={}, body={}", status, body))); - } - - let body: super::HttpBody = if resp.is_end_stream() { - let body = hyper::body::to_bytes(resp.into_body()) - .await - .map_err(HranaError::from)?; - super::HttpBody::from(body) - } else { - let stream = resp - .into_body() - .into_stream() - .map_err(|e| std::io::Error::new(ErrorKind::Other, e)); - super::HttpBody::Stream(Box::new(stream)) - }; - - Ok(body) - } -} - -impl HttpSend for HttpSender { - type Stream = super::HttpBody; - type Result = BoxFuture<'static, Result>; - - fn http_send(&self, url: Arc, auth: Arc, body: String) -> Self::Result { - let fut = self.clone().send(url, auth, body); - Box::pin(fut) - } - - fn oneshot(self, url: Arc, auth: Arc, body: String) { - if let Ok(rt) = tokio::runtime::Handle::try_current() { - rt.spawn(self.send(url, auth, body)); - } else { - tracing::warn!("tried to send request to `{url}` while no runtime was available"); - } - } -} - -impl From for HranaError { - fn from(value: hyper::Error) -> Self { - HranaError::Http(value.to_string()) - } -} - -impl HttpConnection { - pub(crate) fn new_with_connector( - url: impl Into, - token: impl Into, - connector: ConnectorService, - version: Option<&str>, - namespace: Option<&str>, - #[cfg(any(feature = "remote", feature = "sync"))] remote_encryption: Option< - crate::database::EncryptionContext, - >, - ) -> Self { - let inner = HttpSender::new( - connector, - version, - namespace, - #[cfg(any(feature = "remote", feature = "sync"))] - remote_encryption, - ); - Self::new(url.into(), token.into(), inner) - } -} - -#[async_trait::async_trait] -impl Conn for HttpConnection { - async fn execute(&self, sql: &str, params: Params) -> crate::Result { - self.current_stream().execute(sql, params).await - } - - async fn execute_batch(&self, sql: &str) -> crate::Result { - self.current_stream().execute_batch(sql).await - } - - async fn execute_transactional_batch(&self, sql: &str) -> crate::Result { - self.current_stream().execute_transactional_batch(sql).await - } - - async fn prepare(&self, sql: &str) -> crate::Result { - let stream = self.current_stream().clone(); - let stmt = crate::hrana::Statement::new(stream, sql.to_string(), true).await?; - Ok(Statement { - inner: Box::new(stmt), - }) - } - - async fn transaction( - &self, - tx_behavior: crate::TransactionBehavior, - ) -> crate::Result { - let stream = self.open_stream(); - let mut tx = HttpTransaction::open(stream, tx_behavior) - .await - .map_err(|e| crate::Error::Hrana(Box::new(e)))?; - Ok(crate::Transaction { - inner: Box::new(tx.clone()), - conn: crate::Connection { - conn: Arc::new(tx.stream().clone()), - }, - close: Some(Box::new(|| { - // make sure that Hrana connection is closed and all uncommitted changes - // are rolled back when we're about to drop the transaction - if let Ok(rt) = tokio::runtime::Handle::try_current() { - // transaction will rollback automatically after timeout on the server side - // this is gracefull rollback on best-effort basis - rt.spawn(async move { - let _ = tx.rollback().await; - }); - } - })), - }) - } - - fn interrupt(&self) -> crate::Result<()> { - // Interrupt is a no-op for remote connections. - Ok(()) - } - - fn busy_timeout(&self, _timeout: Duration) -> crate::Result<()> { - // Busy timeout is a no-op for remote connections. - Ok(()) - } - - fn is_autocommit(&self) -> bool { - self.is_autocommit() - } - - fn changes(&self) -> u64 { - self.affected_row_count() - } - - fn total_changes(&self) -> u64 { - self.total_changes() - } - - fn last_insert_rowid(&self) -> i64 { - self.last_insert_rowid() - } - - async fn reset(&self) { - self.current_stream().reset().await; - } -} - -#[async_trait::async_trait] -impl crate::statement::Stmt for crate::hrana::Statement { - fn finalize(&mut self) {} - - async fn execute(&self, params: &Params) -> crate::Result { - self.execute(params).await - } - - async fn query(&self, params: &Params) -> crate::Result { - self.query(params).await - } - - async fn run(&self, params: &Params) -> crate::Result<()> { - self.run(params).await - } - - fn interrupt(&self) -> crate::Result<()> { - Err(crate::Error::Misuse( - "interrupt is not supported for remote connections".to_string(), - )) - } - - fn reset(&self) {} - - fn parameter_count(&self) -> usize { - let stmt = &self.inner; - stmt.args.len() + stmt.named_args.len() - } - - fn parameter_name(&self, idx: i32) -> Option<&str> { - //FIXME: actual rules of named args are pretty convoluted and may require full AST parsing. Here we basically - // assume, that if one needs a param name, they don't use named and un-named params mixed in. - if !self.inner.args.is_empty() { - return None; - } - let named_param = self.inner.named_args.get(idx as usize)?; - Some(&named_param.name) - } - - fn column_count(&self) -> usize { - self.cols.len() - } - - fn columns(&self) -> Vec { - //FIXME: there are several blockers here: - // 1. We cannot know the column types before sending a query, so this method will never return results right - // away. - // 2. Even if we do execute query, Hrana doesn't return all info that Column exposes. - // 3. Even if we would like to return some of the column info ie. column [ValueType], this information is not - // present in Hrana [Col] but rather inferred from the row cell type. - self.cols - .iter() - .map(|name| crate::Column { - name, - origin_name: None, - table_name: None, - database_name: None, - decl_type: None, - }) - .collect() - } -} - -#[async_trait::async_trait] -impl Tx for HttpTransaction { - async fn commit(&mut self) -> crate::Result<()> { - self.commit() - .await - .map_err(|e| crate::Error::Hrana(Box::new(e)))?; - Ok(()) - } - - async fn rollback(&mut self) -> crate::Result<()> { - self.rollback() - .await - .map_err(|e| crate::Error::Hrana(Box::new(e)))?; - Ok(()) - } -} - -#[async_trait::async_trait] -impl Conn for HranaStream { - async fn execute(&self, sql: &str, params: Params) -> crate::Result { - // SQLite: execute() will only execute a single SQL statement - let mut parsed = crate::parser::Statement::parse(sql); - let mut c = TxScopeCounter::default(); - if let Some(s) = parsed.next() { - let s = s?; - c.count(s.kind); - let in_tx_scope = !self.is_autocommit() || c.begin_tx(); - let close = !in_tx_scope || c.end_tx(); - let mut stmt = Stmt::new(s.stmt, false); - bind_params(params, &mut stmt); - let result = self - .execute_inner(stmt, close) - .await - .map_err(|e| crate::Error::Hrana(e.into()))?; - Ok(result.affected_row_count) - } else { - Err(crate::Error::Misuse( - "no SQL statement provided".to_string(), - )) - } - } - - async fn execute_batch(&self, sql: &str) -> crate::Result { - let mut stmts = Vec::new(); - let parse = crate::parser::Statement::parse(sql); - let mut c = TxScopeCounter::default(); - for s in parse { - let s = s?; - c.count(s.kind); - stmts.push(Stmt::new(s.stmt, false)); - } - let in_tx_scope = !self.is_autocommit() || c.begin_tx(); - let close = !in_tx_scope || c.end_tx(); - let res = self - .batch_inner(Batch::from_iter(stmts), close) - .await - .map_err(|e| crate::Error::Hrana(e.into()))?; - unwrap_err(&res)?; - let rows = res - .step_results - .into_iter() - .map(|r| r.map(StmtResultRows::new).map(Rows::new)) - .collect::>(); - - Ok(BatchRows::new(rows)) - } - - async fn execute_transactional_batch(&self, sql: &str) -> crate::Result { - let mut stmts = Vec::new(); - let parse = crate::parser::Statement::parse(sql); - for s in parse { - let s = s?; - - use crate::parser::StmtKind; - if matches!( - s.kind, - StmtKind::TxnBegin | StmtKind::TxnBeginReadOnly | StmtKind::TxnEnd - ) { - return Err(Error::TransactionalBatchError( - "Transactions forbidden inside transactional batch".to_string(), - )); - } - - stmts.push(Stmt::new(s.stmt, false)); - } - let res = self - .batch_inner(Batch::transactional(stmts), true) - .await - .map_err(|e| crate::Error::Hrana(e.into()))?; - unwrap_err(&res)?; - let rows = res - .step_results - .into_iter() - // skip the first row since this is related to the already injected - // BEGIN statement. - .skip(1) - .map(|r| r.map(StmtResultRows::new).map(Rows::new)) - .collect::>(); - - // Skip the last row as well since this corresponds to the injected commit statement - // that the user never sees. - Ok(BatchRows::new_skip_last(rows, 2)) - } - - async fn prepare(&self, sql: &str) -> crate::Result { - let stmt = crate::hrana::Statement::new(self.clone(), sql.to_string(), true).await?; - Ok(Statement { - inner: Box::new(stmt), - }) - } - - async fn transaction( - &self, - _tx_behavior: crate::TransactionBehavior, - ) -> crate::Result { - todo!("sounds like nested transactions innit?") - } - - fn interrupt(&self) -> crate::Result<()> { - // Interrupt is a no-op for remote connections. - Ok(()) - } - - fn busy_timeout(&self, _timeout: Duration) -> crate::Result<()> { - // Busy timeout is a no-op for remote connections. - Ok(()) - } - - fn is_autocommit(&self) -> bool { - false // for streams this method is callable only when we're within explicit transaction - } - - fn changes(&self) -> u64 { - self.affected_row_count() - } - - fn total_changes(&self) -> u64 { - self.total_changes() - } - - fn last_insert_rowid(&self) -> i64 { - self.last_insert_rowid() - } - - async fn reset(&self) { - self.reset().await; - } -} diff --git a/vendor/libsql/src/hrana/mod.rs b/vendor/libsql/src/hrana/mod.rs deleted file mode 100644 index 129cf154..00000000 --- a/vendor/libsql/src/hrana/mod.rs +++ /dev/null @@ -1,470 +0,0 @@ -#![allow(dead_code)] - -pub mod connection; - -cfg_remote! { - pub mod hyper; -} - -mod cursor; -mod stream; -pub mod transaction; - -use crate::hrana::cursor::{Cursor, Error, OwnedCursorStep}; -use crate::hrana::stream::HranaStream; -use crate::parser::StmtKind; -use crate::{params::Params, ValueType}; -use bytes::Bytes; -use futures::{Stream, StreamExt}; -pub use libsql_hrana::proto; -use libsql_hrana::proto::{Batch, BatchResult, Col, Stmt, StmtResult}; -use std::collections::VecDeque; -use std::future::Future; -use std::pin::Pin; -use std::sync::atomic::Ordering; -use std::sync::Arc; -use std::task::{Context, Poll}; - -use super::rows::{ColumnsInner, RowInner, RowsInner}; - -pub(crate) type Result = std::result::Result; - -/// Information about the current session: the server-generated cookie -/// and the URL that should be used for further communication. -#[derive(Clone, Debug, Default)] -struct Cookie { - baton: Option, - base_url: Option, -} - -pub trait HttpSend: Clone { - type Stream: Stream> + Unpin; - type Result: Future>; - fn http_send(&self, url: Arc, auth: Arc, body: String) -> Self::Result; - - /// Schedule sending a HTTP post request without waiting for the completion. - fn oneshot(self, url: Arc, auth: Arc, body: String); -} - -pub enum HttpBody { - Body(Option), - Stream(S), -} - -impl From for HttpBody { - fn from(value: Bytes) -> Self { - HttpBody::Body(Some(value)) - } -} - -impl Stream for HttpBody -where - S: Stream> + Unpin, -{ - type Item = std::io::Result; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.get_mut() { - HttpBody::Body(bytes) => { - if let Some(bytes) = bytes.take() { - Poll::Ready(Some(Ok(bytes))) - } else { - Poll::Ready(None) - } - } - HttpBody::Stream(stream) => { - let pinned = Pin::new(stream); - pinned.poll_next(cx) - } - } - } -} - -#[derive(Debug, thiserror::Error)] -pub enum HranaError { - #[error("unexpected response: `{0}`")] - UnexpectedResponse(String), - #[error("stream closed: `{0}`")] - StreamClosed(String), - #[error("stream error: `{0:?}`")] - StreamError(proto::Error), - #[error("cursor error: `{0}`")] - CursorError(CursorResponseError), - #[error("json error: `{0}`")] - Json(#[from] serde_json::Error), - #[error("http error: `{0}`")] - Http(String), - #[error("api error: `{0}`")] - Api(String), -} - -#[derive(Debug, thiserror::Error)] -pub enum CursorResponseError { - #[error("cursor step {actual} arrived before step {expected} end message")] - NotClosed { expected: u32, actual: u32 }, - #[error("error at step {step}: {error}")] - StepError { step: u32, error: Error }, - #[error("cursor stream ended prematurely")] - CursorClosed, - #[error("cursor hasn't fetched any rows yet")] - NoRowsFetched, - #[error("{0}")] - Other(String), -} - -pub struct Statement -where - T: HttpSend, -{ - stream: HranaStream, - close_stream: bool, - inner: Stmt, - cols: Vec, -} - -impl Statement -where - T: HttpSend + Send + Sync + 'static, -{ - pub(crate) async fn new( - stream: HranaStream, - sql: String, - want_rows: bool, - ) -> crate::Result { - let desc = stream.describe(&sql).await?; - - let cols: Vec<_> = desc.cols.into_iter().map(|col| col.name).collect(); - - // in SQLite when a multiple statements are glued together into one string, only the first one is - // executed and then a handle to continue execution is returned. However Hrana API doesn't allow - // passing multi-statement strings, so we just pick first one. - let mut parse = crate::parser::Statement::parse(&sql); - match parse.next() { - None => Err(crate::Error::Misuse( - "no SQL statement provided".to_string(), - )), - Some(Err(e)) => Err(e), - Some(Ok(stmt)) => { - // if we're already in transaction scope (non-autocommit) or we're starting - // a transaction, we DON'T want to close the stream - let in_tx_scope = !stream.is_autocommit() - || matches!(stmt.kind, StmtKind::TxnBegin | StmtKind::TxnBeginReadOnly); - // if we're at COMMIT/ROLLBACK statement, we DO want to close the stream - let close_stream = !in_tx_scope || matches!(stmt.kind, StmtKind::TxnEnd); - let inner = Stmt::new(stmt.stmt, want_rows); - Ok(Statement { - stream, - close_stream, - inner, - cols, - }) - } - } - } - - pub async fn execute(&self, params: &Params) -> crate::Result { - let mut stmt = self.inner.clone(); - bind_params(params.clone(), &mut stmt); - - let result = self.stream.execute_inner(stmt, self.close_stream).await?; - Ok(result.affected_row_count as usize) - } - - pub async fn run(&self, params: &Params) -> crate::Result<()> { - let mut stmt = self.inner.clone(); - bind_params(params.clone(), &mut stmt); - - let _ = self.stream.execute_inner(stmt, self.close_stream).await?; - Ok(()) - } - - pub(crate) async fn query_raw( - &self, - params: &Params, - ) -> crate::Result> { - let mut stmt = self.inner.clone(); - bind_params(params.clone(), &mut stmt); - - let cursor = self.stream.cursor(Batch::single(stmt)).await?; - let rows = HranaRows::from_cursor(cursor, self.stream.clone()).await?; - - Ok(rows) - } -} - -impl Statement -where - T: HttpSend + Send + Sync + 'static, - ::Stream: Send + Sync + 'static, -{ - pub async fn query(&self, params: &Params) -> crate::Result { - let rows = self.query_raw(params).await?; - Ok(super::Rows::new(rows)) - } -} - -pub struct HranaRows { - cursor_step: OwnedCursorStep, - column_types: Option>, - stream: HranaStream, -} - -impl HranaRows -where - T: HttpSend + Send + Sync + 'static, - S: Stream> + Unpin, -{ - async fn from_cursor(cursor: Cursor, stream: HranaStream) -> Result { - let cursor_step = cursor.next_step_owned().await?; - Ok(HranaRows { - cursor_step, - column_types: None, - stream, - }) - } - - pub async fn next(&mut self) -> crate::Result> { - let row = match self.cursor_step.next().await { - Some(Ok(row)) => row, - Some(Err(e)) => return Err(crate::Error::Hrana(Box::new(e))), - None => { - self.stream - .inner - .affected_row_count - .store(self.cursor_step.affected_rows().into(), Ordering::SeqCst); - return Ok(None); - } - }; - - if self.column_types.is_none() { - self.init_column_types(&row); - } - - Ok(Some(super::Row { - inner: Box::new(row), - })) - } - - fn init_column_types(&mut self, row: &Row) { - self.column_types = Some( - row.inner - .iter() - .map(|value| match value { - proto::Value::Null | proto::Value::None => ValueType::Null, - proto::Value::Integer { value: _ } => ValueType::Integer, - proto::Value::Float { value: _ } => ValueType::Real, - proto::Value::Text { value: _ } => ValueType::Text, - proto::Value::Blob { value: _ } => ValueType::Blob, - }) - .collect(), - ); - } - - pub fn column_count(&self) -> i32 { - self.cursor_step.cols().len() as i32 - } - - pub fn column_name(&self, idx: i32) -> Option<&str> { - self.cursor_step - .cols() - .get(idx as usize) - .and_then(|c| c.name.as_ref()) - .map(|s| s.as_str()) - } -} - -#[async_trait::async_trait] -impl RowsInner for HranaRows -where - T: HttpSend + Send + Sync + 'static, - S: Stream> + Send + Sync + Unpin, -{ - async fn next(&mut self) -> crate::Result> { - self.next().await - } -} - -impl ColumnsInner for HranaRows -where - T: HttpSend + Send + Sync + 'static, - S: Stream> + Send + Sync + Unpin, -{ - fn column_count(&self) -> i32 { - self.column_count() - } - - fn column_name(&self, idx: i32) -> Option<&str> { - self.column_name(idx) - } - - fn column_type(&self, idx: i32) -> crate::Result { - if let Some(col_types) = &self.column_types { - if let Some(t) = col_types.get(idx as usize) { - Ok(*t) - } else { - Err(crate::Error::ColumnNotFound(idx)) - } - } else { - Err(crate::Error::Hrana(Box::new(HranaError::CursorError( - CursorResponseError::NoRowsFetched, - )))) - } - } -} - -#[derive(Debug)] -pub struct Row { - cols: Arc<[Col]>, - inner: Vec, -} - -impl Row { - pub(super) fn new(cols: Arc<[Col]>, inner: Vec) -> Self { - Row { cols, inner } - } -} - -impl RowInner for Row { - fn column_value(&self, idx: i32) -> crate::Result { - let v = self.inner.get(idx as usize).cloned().unwrap(); - Ok(into_value2(v)) - } - - fn column_str(&self, idx: i32) -> crate::Result<&str> { - if let Some(value) = self.inner.get(idx as usize) { - if let proto::Value::Text { value } = value { - Ok(value) - } else { - Err(crate::Error::InvalidColumnType) - } - } else { - Err(crate::Error::ColumnNotFound(idx)) - } - } -} - -impl ColumnsInner for Row { - fn column_name(&self, idx: i32) -> Option<&str> { - self.cols - .get(idx as usize) - .and_then(|c| c.name.as_ref()) - .map(|s| s.as_str()) - } - - fn column_type(&self, idx: i32) -> crate::Result { - if let Some(value) = self.inner.get(idx as usize) { - Ok(match value { - proto::Value::Null => ValueType::Null, - proto::Value::Integer { value: _ } => ValueType::Integer, - proto::Value::Float { value: _ } => ValueType::Real, - proto::Value::Text { value: _ } => ValueType::Text, - proto::Value::Blob { value: _ } => ValueType::Blob, - proto::Value::None => return Err(crate::Error::InvalidColumnType), - }) - } else { - Err(crate::Error::ColumnNotFound(idx)) - } - } - - fn column_count(&self) -> i32 { - self.cols.len() as i32 - } -} - -pub(super) fn bind_params(params: Params, stmt: &mut Stmt) { - match params { - Params::None => {} - Params::Positional(values) => { - for value in values { - stmt.bind(into_value(value)); - } - } - Params::Named(values) => { - for (name, value) in values { - stmt.bind_named(name, into_value(value)); - } - } - } -} - -fn into_value(value: crate::Value) -> proto::Value { - match value { - crate::Value::Null => proto::Value::Null, - crate::Value::Integer(value) => proto::Value::Integer { value }, - crate::Value::Real(value) => proto::Value::Float { value }, - crate::Value::Text(value) => proto::Value::Text { - value: value.into(), - }, - crate::Value::Blob(value) => proto::Value::Blob { - value: value.into(), - }, - } -} - -fn into_value2(value: proto::Value) -> crate::Value { - match value { - proto::Value::Null | proto::Value::None => crate::Value::Null, - proto::Value::Integer { value } => crate::Value::Integer(value), - proto::Value::Float { value } => crate::Value::Real(value), - proto::Value::Text { value } => crate::Value::Text(value.to_string()), - proto::Value::Blob { value } => crate::Value::Blob(value.into()), - } -} - -pub(crate) fn unwrap_err(batch_res: &BatchResult) -> crate::Result<()> { - batch_res - .step_errors - .iter() - .find_map(|e| e.clone()) - .map(|e| Err(crate::Error::Hrana(Box::new(HranaError::Api(e.message))))) - .unwrap_or(Ok(())) -} - -struct StmtResultRows { - cols: Arc<[Col]>, - rows: VecDeque, -} - -impl StmtResultRows { - pub(crate) fn new(stmt: StmtResult) -> Self { - Self { - rows: stmt.rows.into(), - cols: stmt.cols.into(), - } - } -} - -#[async_trait::async_trait] -impl RowsInner for StmtResultRows { - async fn next(&mut self) -> crate::Result> { - let Some(row) = self.rows.pop_front() else { - return Ok(None); - }; - let row = Row::new(self.cols.clone(), row.values); - - Ok(Some(super::Row { - inner: Box::new(row), - })) - } -} - -impl ColumnsInner for StmtResultRows { - fn column_count(&self) -> i32 { - self.cols.len() as i32 - } - - fn column_name(&self, idx: i32) -> Option<&str> { - self.cols - .get(idx as usize) - .and_then(|r| r.name.as_ref()) - .map(|n| n.as_str()) - } - - fn column_type(&self, idx: i32) -> crate::Result { - self.cols - .get(idx as usize) - .and_then(|r| r.decltype.as_ref()) - .ok_or(crate::Error::InvalidColumnType) - .and_then(|v| v.parse().map_err(|_| crate::Error::InvalidColumnType)) - } -} diff --git a/vendor/libsql/src/hrana/stream.rs b/vendor/libsql/src/hrana/stream.rs deleted file mode 100644 index 686013d3..00000000 --- a/vendor/libsql/src/hrana/stream.rs +++ /dev/null @@ -1,556 +0,0 @@ -use crate::hrana::cursor::{Cursor, CursorReq}; -use crate::hrana::proto::{Batch, BatchResult, DescribeResult, Stmt, StmtResult}; -use crate::hrana::{CursorResponseError, HranaError, HttpSend, Result}; -use bytes::{Bytes, BytesMut}; -use futures::Stream; -use libsql_hrana::proto::{ - BatchStreamReq, CloseSqlStreamReq, CloseStreamReq, CloseStreamResp, DescribeStreamReq, - GetAutocommitStreamReq, PipelineReqBody, PipelineRespBody, SequenceStreamReq, - StoreSqlStreamReq, StreamRequest, StreamResponse, StreamResult, -}; -use std::cell::RefCell; -use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; -use std::sync::Arc; -use tokio::sync::Mutex; - -macro_rules! unexpected { - ($value:ident) => { - return Err(HranaError::UnexpectedResponse(format!( - "Unexpected response from server: {:?}", - $value - ))) - }; -} - -pub type SqlId = i32; - -/// A representation of Hrana HTTP stream. Since streams rely on sequential execution of requests, -/// it's realized internally as a mutex lock. -#[derive(Debug)] -pub struct HranaStream -where - T: HttpSend, -{ - pub inner: Arc>, -} - -impl Clone for HranaStream -where - T: HttpSend, -{ - fn clone(&self) -> Self { - HranaStream { - inner: self.inner.clone(), - } - } -} - -impl HranaStream -where - T: HttpSend, -{ - pub(super) fn open( - client: T, - pipeline_url: Arc, - cursor_url: Arc, - auth_token: Arc, - ) -> Self { - tracing::trace!("opening stream"); - HranaStream { - inner: Arc::new(Inner { - affected_row_count: AtomicU64::new(0), - total_changes: AtomicU64::new(0), - last_insert_rowid: AtomicI64::new(0), - is_autocommit: AtomicBool::new(true), - stream: Mutex::new(RawStream { - client, - pipeline_url, - cursor_url, - auth_token, - sql_id_generator: RefCell::new(0), - baton: RefCell::new(None), - }), - }), - } - } - - /// Executes a final request and immediately closes current stream - all in one request - /// Returns true if request was finalized correctly, false if stream was already closed. - pub(super) async fn finalize(&mut self, req: StreamRequest) -> Result { - let mut client = self.inner.stream.lock().await; - if client.baton.borrow().is_none() { - tracing::trace!("baton not found - skipping finalize for {:?}", req); - return Ok(false); - } - let (resp, is_autocommit) = client.finalize(req).await?; - self.inner - .is_autocommit - .store(is_autocommit, Ordering::SeqCst); - let (affected_row_count, last_insert_rowid) = if let StreamResponse::Execute(resp) = resp { - ( - resp.result.affected_row_count, - resp.result.last_insert_rowid.unwrap_or(0), - ) - } else { - (0, 0) - }; - - self.inner - .total_changes - .fetch_add(affected_row_count, Ordering::SeqCst); - self.inner - .affected_row_count - .store(affected_row_count, Ordering::SeqCst); - self.inner - .last_insert_rowid - .store(last_insert_rowid, Ordering::SeqCst); - Ok(true) - } - - pub(super) async fn execute_inner(&self, stmt: Stmt, close_stream: bool) -> Result { - let mut batch_res = self.batch_inner(Batch::single(stmt), close_stream).await?; - if let Some(Some(error)) = batch_res.step_errors.pop() { - return Err(HranaError::StreamError(error)); - } - if let Some(Some(resp)) = batch_res.step_results.pop() { - Ok(resp) - } else { - Err(HranaError::CursorError(CursorResponseError::Other( - "no result has been returned".to_string(), - ))) - } - } - - pub(crate) async fn batch_inner( - &self, - batch: Batch, - close_stream: bool, - ) -> Result { - let mut client = self.inner.stream.lock().await; - let (resp, get_autocommit) = if close_stream { - tracing::trace!("send Hrana SQL batch (with closing the stream)"); - let [resp, get_autocommit, _] = client - .send_requests([ - StreamRequest::Batch(BatchStreamReq { batch }), - StreamRequest::GetAutocommit(GetAutocommitStreamReq {}), - StreamRequest::Close(CloseStreamReq {}), - ]) - .await?; - client.reset(); - (resp, get_autocommit) - } else { - tracing::trace!("send Hrana SQL batch (leave the stream open)"); - let [resp, get_autocommit] = client - .send_requests([ - StreamRequest::Batch(BatchStreamReq { batch }), - StreamRequest::GetAutocommit(GetAutocommitStreamReq {}), - ]) - .await?; - (resp, get_autocommit) - }; - drop(client); - match get_autocommit { - StreamResponse::GetAutocommit(r) => { - self.inner - .is_autocommit - .store(r.is_autocommit, Ordering::SeqCst); - } - other => unexpected!(other), - }; - match resp { - StreamResponse::Batch(resp) => { - if let Some(Some(result)) = resp.result.step_results.last() { - self.inner - .affected_row_count - .store(result.affected_row_count, Ordering::SeqCst); - if let Some(last_insert_rowid) = result.last_insert_rowid { - self.inner - .last_insert_rowid - .store(last_insert_rowid, Ordering::SeqCst); - } - } - Ok(resp.result) - } - other => unexpected!(other), - } - } - - pub async fn cursor(&self, batch: Batch) -> Result> { - let mut client = self.inner.stream.lock().await; - let cursor = client.open_cursor(batch).await?; - Ok(cursor) - } - - pub async fn store_sql(&self, sql: String) -> Result> { - let mut client = self.inner.stream.lock().await; - let sql_id = client.next_sql_id(); - let resp = client - .send(StreamRequest::StoreSql(StoreSqlStreamReq { sql, sql_id })) - .await?; - match resp { - StreamResponse::StoreSql(_) => { - drop(client); - Ok(StoredSql::new(self.clone(), sql_id)) - } - other => unexpected!(other), - } - } - - async fn close_sql(&self, sql_id: SqlId) -> Result<()> { - let mut client = self.inner.stream.lock().await; - let resp = client - .send(StreamRequest::CloseSql(CloseSqlStreamReq { sql_id })) - .await?; - match resp { - StreamResponse::CloseSql(_) => Ok(()), - other => unexpected!(other), - } - } - - pub async fn describe(&self, descriptor: &D) -> Result { - let mut client = self.inner.stream.lock().await; - let req = match descriptor.sql_description() { - SqlDescription::Sql(sql) => DescribeStreamReq { - sql: Some(sql), - sql_id: None, - replication_index: None, - }, - SqlDescription::SqlId(sql_id) => DescribeStreamReq { - sql: None, - sql_id: Some(sql_id), - replication_index: None, - }, - }; - let resp = client.send(StreamRequest::Describe(req)).await?; - match resp { - StreamResponse::Describe(resp) => Ok(resp.result), - other => unexpected!(other), - } - } - - pub async fn sequence(&self, descriptor: &D) -> Result<()> { - let mut client = self.inner.stream.lock().await; - let req = match descriptor.sql_description() { - SqlDescription::Sql(sql) => SequenceStreamReq { - sql: Some(sql), - sql_id: None, - replication_index: None, - }, - SqlDescription::SqlId(sql_id) => SequenceStreamReq { - sql: None, - sql_id: Some(sql_id), - replication_index: None, - }, - }; - let resp = client.send(StreamRequest::Sequence(req)).await?; - match resp { - StreamResponse::Sequence(_) => Ok(()), - other => unexpected!(other), - } - } - - pub async fn get_autocommit(&self) -> Result { - let mut client = self.inner.stream.lock().await; - let resp = client - .send(StreamRequest::GetAutocommit(GetAutocommitStreamReq {})) - .await?; - match resp { - StreamResponse::GetAutocommit(resp) => Ok(resp.is_autocommit), - other => unexpected!(other), - } - } - - pub fn affected_row_count(&self) -> u64 { - self.inner.affected_row_count.load(Ordering::SeqCst) - } - - pub fn total_changes(&self) -> u64 { - self.inner.total_changes.load(Ordering::SeqCst) - } - - pub fn last_insert_rowid(&self) -> i64 { - self.inner.last_insert_rowid.load(Ordering::SeqCst) - } - - pub fn is_autocommit(&self) -> bool { - self.inner.is_autocommit.load(Ordering::SeqCst) - } - - pub async fn reset(&self) { - (*self.inner).stream.lock().await.reset(); - } -} - -#[derive(Debug)] -pub struct Inner -where - T: HttpSend, -{ - pub affected_row_count: AtomicU64, - total_changes: AtomicU64, - last_insert_rowid: AtomicI64, - is_autocommit: AtomicBool, - stream: Mutex>, -} - -#[derive(Debug)] -struct RawStream -where - T: HttpSend, -{ - client: T, - baton: RefCell>, - pipeline_url: Arc, - cursor_url: Arc, - auth_token: Arc, - sql_id_generator: RefCell, -} - -impl RawStream -where - T: HttpSend, -{ - async fn send(&mut self, req: StreamRequest) -> Result { - let [resp] = self.send_requests([req]).await?; - Ok(resp) - } - - pub async fn open_cursor(&mut self, batch: Batch) -> Result> { - let msg = CursorReq { - baton: self.baton.borrow().clone(), - batch, - }; - let body = serde_json::to_string(&msg).map_err(HranaError::Json)?; - let stream = self - .client - .http_send(self.cursor_url.clone(), self.auth_token.clone(), body) - .await?; - let (cursor, mut response) = Cursor::open(stream).await?; - if let Some(base_url) = response.base_url.take() { - self.pipeline_url = Arc::from(format!("{base_url}/v3/pipeline")); - self.cursor_url = Arc::from(format!("{base_url}/v3/cursor")); - } - match response.baton.take() { - None => { - tracing::trace!("client stream has been closed by the server"); - self.reset(); - } // stream has been closed by the server - Some(baton) => { - tracing::trace!("client stream has been assigned with baton: `{}`", baton); - *self.baton.borrow_mut() = Some(baton) - } - } - Ok(cursor) - } - - async fn send_requests( - &mut self, - requests: [StreamRequest; N], - ) -> Result<[StreamResponse; N]> { - tracing::trace!( - "client stream sending {} requests with baton `{}`: {:?}", - N, - self.baton.borrow().as_deref().unwrap_or_default(), - requests - ); - let msg = PipelineReqBody { - baton: self.baton.borrow().clone(), - requests: Vec::from(requests), - }; - let body = serde_json::to_string(&msg).map_err(HranaError::Json)?; - let body = self - .client - .http_send(self.pipeline_url.clone(), self.auth_token.clone(), body) - .await?; - let body = stream_to_bytes(body).await?; - let mut response: PipelineRespBody = serde_json::from_slice(&body)?; - if let Some(base_url) = response.base_url.take() { - let (pipeline_url, cursor_url) = parse_hrana_urls(&base_url); - self.pipeline_url = pipeline_url; - self.cursor_url = cursor_url; - } - match response.baton.take() { - None => { - tracing::trace!("client stream has been closed by the server"); - self.reset(); - } // stream has been closed by the server - Some(baton) => { - tracing::trace!("client stream has been assigned with baton: `{}`", baton); - *self.baton.borrow_mut() = Some(baton) - } - } - - if response.results.is_empty() { - Err(HranaError::UnexpectedResponse(format!( - "Unexpected empty response from server: {:?}", - response.results - )))?; - } - if response.results.len() != N { - // One with actual results, one closing the stream - Err(HranaError::UnexpectedResponse(format!( - "Unexpected multiple responses from server: {:?}", - response.results - )))?; - } - let mut responses = std::array::from_fn(|_| StreamResponse::Close(CloseStreamResp {})); - for (i, result) in response.results.into_iter().enumerate() { - match result { - StreamResult::Ok { response } => responses[i] = response, - StreamResult::Error { error } => return Err(HranaError::StreamError(error)), - StreamResult::None => {} - } - } - Ok(responses) - } - - async fn finalize(&mut self, req: StreamRequest) -> Result<(StreamResponse, bool)> { - let [resp, get_autocommit, _] = self - .send_requests([ - req, - StreamRequest::GetAutocommit(GetAutocommitStreamReq {}), - StreamRequest::Close(CloseStreamReq {}), - ]) - .await?; - let is_autocommit = match get_autocommit { - StreamResponse::GetAutocommit(resp) => resp.is_autocommit, - other => { - return Err(HranaError::UnexpectedResponse(format!( - "expected GetAutocommitResp but got {:?}", - other - ))) - } - }; - self.reset(); - Ok((resp, is_autocommit)) - } - - fn reset(&self) { - if let Some(baton) = self.baton.borrow_mut().take() { - tracing::trace!("closing client stream (baton: `{}`)", baton); - } - *self.sql_id_generator.borrow_mut() = 0; - } - - fn next_sql_id(&mut self) -> SqlId { - let mut gen = self.sql_id_generator.borrow_mut(); - *gen = gen.wrapping_add(1); - *gen - } -} - -#[cfg(feature = "remote")] -impl Drop for RawStream -where - T: HttpSend, -{ - fn drop(&mut self) { - let baton = self.baton.get_mut().take(); - if let Some(baton) = baton { - // only send a close request if stream was ever used to send the data - tracing::trace!("closing client stream (baton: `{}`)", baton); - let req = serde_json::to_string(&PipelineReqBody { - baton: Some(baton), - requests: vec![StreamRequest::Close(CloseStreamReq {})], - }) - .unwrap(); - self.client - .clone() - .oneshot(self.pipeline_url.clone(), self.auth_token.clone(), req); - self.reset(); - } - } -} - -pub(super) fn parse_hrana_urls(url: &str) -> (Arc, Arc) { - let (mut base_url, query) = match url.rfind('?') { - Some(i) => url.split_at(i), - None => (url, ""), - }; - if base_url.ends_with('/') { - base_url = &base_url[0..(base_url.len() - 1)]; - } - let pipeline_url = Arc::from(format!("{base_url}/v3/pipeline{query}")); - let cursor_url = Arc::from(format!("{base_url}/v3/cursor{query}")); - (pipeline_url, cursor_url) -} - -async fn stream_to_bytes(mut stream: S) -> Result -where - S: Stream> + Unpin, -{ - use futures::StreamExt; - - let mut buf = BytesMut::new(); - while let Some(chunk) = stream.next().await { - buf.extend_from_slice(&chunk.map_err(|e| HranaError::Http(e.to_string()))?); - } - Ok(buf.freeze()) -} - -#[derive(Debug, Clone)] -pub struct StoredSql -where - T: HttpSend, -{ - stream: HranaStream, - sql_id: SqlId, -} - -impl StoredSql -where - T: HttpSend, -{ - fn new(stream: HranaStream, sql_id: SqlId) -> Self { - StoredSql { stream, sql_id } - } - - pub async fn describe(&self) -> Result { - self.stream.describe(self).await - } - - pub async fn execute(&self) -> Result<()> { - self.stream.sequence(self).await - } - - pub async fn close(self) -> Result<()> { - self.stream.close_sql(self.sql_id).await - } -} - -/// Trait that allows to refer to stored SQL statements. Stored SQL is first send by -/// the prepared statements as raw string and can be cached on the server side in scope of -/// a current transaction using a number identifier. This identifier can be send in subsequent -/// calls in place of SQL string to reduce the size of a message. -pub trait SqlDescriptor { - fn sql_description(&self) -> SqlDescription; -} - -impl SqlDescriptor for String { - fn sql_description(&self) -> SqlDescription { - SqlDescription::Sql(self.clone()) - } -} - -impl SqlDescriptor for StoredSql -where - T: HttpSend, -{ - fn sql_description(&self) -> SqlDescription { - SqlDescription::SqlId(self.sql_id) - } -} - -/// Enum used by stored SQL statements. It can refer to either fresh SQL statement text, or a -/// unique identifier assigned to that statement. -/// -/// For the same statement executed many times over it's better to cache it first under [SqlId] key -/// and then re-execute it in subsequent calls using only identifier. -#[derive(Debug)] -pub enum SqlDescription { - /// Non-cached SQL statement string. - Sql(String), - /// Key identifier of a SQL statement cached by the server. Key is valid only in - /// the scope of current transaction/prepared statement, which sent a store SQL request. - SqlId(SqlId), -} diff --git a/vendor/libsql/src/hrana/transaction.rs b/vendor/libsql/src/hrana/transaction.rs deleted file mode 100644 index 447f29ab..00000000 --- a/vendor/libsql/src/hrana/transaction.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::hrana::proto::Stmt; -use crate::hrana::stream::HranaStream; -use crate::hrana::{HttpSend, Result}; -use crate::parser::StmtKind; -use crate::TransactionBehavior; -use libsql_hrana::proto::{ExecuteStreamReq, StreamRequest}; - -#[derive(Debug, Clone)] -pub(crate) struct HttpTransaction -where - T: HttpSend, -{ - stream: HranaStream, -} - -impl HttpTransaction -where - T: HttpSend, -{ - pub fn stream(&self) -> &HranaStream { - &self.stream - } - - pub async fn open(stream: HranaStream, tx_behavior: TransactionBehavior) -> Result { - let begin_stmt = match tx_behavior { - TransactionBehavior::Deferred => "BEGIN DEFERRED", - TransactionBehavior::Immediate => "BEGIN IMMEDIATE", - TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE", - TransactionBehavior::ReadOnly => "BEGIN READONLY", - }; - stream - .execute_inner(Stmt::new(begin_stmt, false), false) - .await?; - Ok(HttpTransaction { stream }) - } - - pub async fn commit(&mut self) -> Result<()> { - let stmt = Stmt::new("COMMIT", false); - self.stream - .finalize(StreamRequest::Execute(ExecuteStreamReq { stmt })) - .await?; - Ok(()) - } - - pub async fn rollback(&mut self) -> Result<()> { - let stmt = Stmt::new("ROLLBACK", false); - self.stream - .finalize(StreamRequest::Execute(ExecuteStreamReq { stmt })) - .await?; - Ok(()) - } -} - -/// Counts number of transaction begin statements and transaction commits/rollback -/// in order to determine if current statement execution will end within transaction -/// scope, will start a new transaction or end existing one. -#[repr(transparent)] -#[derive(Default)] -pub(crate) struct TxScopeCounter { - scope: i32, -} - -impl TxScopeCounter { - pub(crate) fn count(&mut self, stmt_kind: StmtKind) { - match stmt_kind { - StmtKind::TxnBegin | StmtKind::TxnBeginReadOnly => self.scope += 1, - StmtKind::TxnEnd => self.scope -= 1, - _ => {} - } - } - - /// Check if within current scope we will eventually begin new transaction. - pub(crate) fn begin_tx(&self) -> bool { - self.scope > 0 - } - - /// Check if within current scope we will eventually close existing transaction. - pub(crate) fn end_tx(&self) -> bool { - self.scope < 0 - } -} diff --git a/vendor/libsql/src/lib.rs b/vendor/libsql/src/lib.rs deleted file mode 100644 index 13cf082e..00000000 --- a/vendor/libsql/src/lib.rs +++ /dev/null @@ -1,193 +0,0 @@ -//! # libSQL API for Rust -//! -//! libSQL is an embeddable SQL database engine based on SQLite. -//! This Rust API is a batteries-included wrapper around the SQLite C API to support transparent replication while retaining compatibility with the SQLite ecosystem, such as the SQL dialect and extensions. If you are building an application in Rust, this is the crate you should use. -//! There are also libSQL language bindings of this Rust crate to other languages such as [JavaScript](), Python, Go, and C. -//! -//! ## Getting Started -//! -//! To get started, you first need to create a [`Database`] object and then open a [`Connection`] to it, which you use to query: -//! -//! ```rust,no_run -//! # async fn run() { -//! use libsql::Builder; -//! -//! let db = Builder::new_local(":memory:").build().await.unwrap(); -//! let conn = db.connect().unwrap(); -//! conn.execute("CREATE TABLE IF NOT EXISTS users (email TEXT)", ()).await.unwrap(); -//! conn.execute("INSERT INTO users (email) VALUES ('alice@example.org')", ()).await.unwrap(); -//! # } -//! ``` -//! -//! ## Embedded Replicas -//! -//! Embedded replica is libSQL database that's running in your application process, which keeps a local copy of a remote database. -//! They are useful if you want to move data in the memory space of your application for fast access. -//! -//! You can open an embedded read-only replica by using the [`Database::open_with_local_sync`] constructor: -//! -//! ```rust,no_run -//! # async fn run() { -//! use libsql::Builder; -//! use libsql::replication::Frames; -//! -//! let mut db = Builder::new_local_replica("/tmp/test.db").build().await.unwrap(); -//! -//! let frames = Frames::Vec(vec![]); -//! db.sync_frames(frames).await.unwrap(); -//! let conn = db.connect().unwrap(); -//! conn.execute("SELECT * FROM users", ()).await.unwrap(); -//! # } -//! ``` -//! -//! ## Remote database -//! -//! It is also possible to create a libsql connection that does not open a local database but -//! instead sends queries to a remote database. -//! -//! ```rust,no_run -//! # async fn run() { -//! use libsql::Builder; -//! -//! let db = Builder::new_remote("libsql://my-remote-db.com".to_string(), "my-auth-token".to_string()).build().await.unwrap(); -//! let conn = db.connect().unwrap(); -//! conn.execute("CREATE TABLE IF NOT EXISTS users (email TEXT)", ()).await.unwrap(); -//! conn.execute("INSERT INTO users (email) VALUES ('alice@example.org')", ()).await.unwrap(); -//! # } -//! ``` -//! -//! ## WASM -//! -//! Due to WASM requiring `!Send` support and the [`Database`] type supporting async and using -//! `async_trait` to abstract between the different database types, we are unable to support WASM -//! via the [`Database`] type. Instead, we have provided simpler parallel types in the `wasm` -//! module that provide access to our remote HTTP protocol in WASM. -//! -//! ## Examples -//! -//! You can find more examples in the [`examples`](https://github.com/tursodatabase/libsql/tree/main/libsql/examples) directory. -//! -//! ## Feature flags -//! -//! This crate provides a few feature flags that will help you improve compile times by allowing -//! you to reduce the dependencies needed to compile specific features of this crate. For example, -//! you may not want to compile the libsql C code if you just want to make HTTP requests. Feature -//! flags may be used by including the libsql crate like: -//! -//! ```toml -//! libsql = { version = "*", default-features = false, features = ["core", "replication", "remote" ] -//! ``` -//! -//! By default, all the features are enabled but by providing `default-features = false` it will -//! remove those defaults. -//! -//! The features are descirbed like so: -//! - `core` this includes the core C code that backs both the basic local database usage and -//! embedded replica features. -//! - `replication` this feature flag includes the `core` feature flag and adds on top HTTP code -//! that will allow you to sync you remote database locally. -//! - `remote` this feature flag only includes HTTP code that will allow you to run queries against -//! a remote database. -//! - `tls` this feature flag disables the builtin TLS connector and instead requires that you pass -//! your own connector for any of the features that require HTTP. - -#![cfg_attr(docsrs, feature(doc_cfg))] -#![cfg_attr( - all( - any( - not(feature = "remote"), - not(feature = "replication"), - not(feature = "core") - ), - feature = "tls" - ), - allow(unused_imports) -)] -#![cfg_attr( - all( - any( - not(feature = "remote"), - not(feature = "replication"), - not(feature = "core") - ), - feature = "tls" - ), - allow(dead_code) -)] - -#[macro_use] -mod macros; - -cfg_core! { - mod local; - - pub use local::{version, version_number, RowsFuture}; - pub use database::OpenFlags; - - pub use database::{Cipher, EncryptionConfig}; -} - -pub mod params; - -cfg_sync! { - mod sync; - pub use database::SyncProtocol; - pub use database::EncryptionContext; - pub use database::EncryptionKey; -} - -cfg_replication! { - pub mod replication; -} - -cfg_core! { - pub use libsql_sys::ffi; -} - -cfg_wasm! { - pub mod wasm; -} - -mod util; - -pub mod errors; -pub use errors::Error; - -pub use params::params_from_iter; - -mod auth; -mod connection; -mod database; -mod load_extension_guard; - -cfg_parser! { - mod parser; -} - -mod rows; -mod statement; -mod transaction; -mod value; - -#[cfg(feature = "serde")] -pub mod de; - -pub use value::{Value, ValueRef, ValueType}; - -cfg_hrana! { - mod hrana; -} - -pub use self::{ - auth::{AuthAction, AuthContext, Authorization}, - connection::{AuthHook, BatchRows, Connection, Op}, - database::{Builder, Database}, - load_extension_guard::LoadExtensionGuard, - rows::{Column, Row, Rows}, - statement::Statement, - transaction::{Transaction, TransactionBehavior}, -}; - -/// Convenient alias for `Result` using the `libsql::Error` type. -pub type Result = std::result::Result; -pub(crate) type BoxError = Box; diff --git a/vendor/libsql/src/load_extension_guard.rs b/vendor/libsql/src/load_extension_guard.rs deleted file mode 100644 index 66de4496..00000000 --- a/vendor/libsql/src/load_extension_guard.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::sync::Arc; - -use crate::connection::Conn; -use crate::{Connection, Result}; - -/// A guard for safely loading SQLite extensions. -/// -/// # Example -/// -/// ```ignore -/// let _guard = LoadExtensionGuard::new(conn)?; -/// conn.load_extension("uuid", None)?; -/// ``` -pub struct LoadExtensionGuard { - pub(crate) conn: Arc, -} - -impl LoadExtensionGuard { - pub fn new(conn: &Connection) -> Result { - let conn = conn.conn.clone(); - conn.enable_load_extension(true).map(|_| Self { conn }) - } -} - -impl Drop for LoadExtensionGuard { - fn drop(&mut self) { - let _ = self.conn.enable_load_extension(false); - } -} diff --git a/vendor/libsql/src/local/connection.rs b/vendor/libsql/src/local/connection.rs deleted file mode 100644 index 70126516..00000000 --- a/vendor/libsql/src/local/connection.rs +++ /dev/null @@ -1,908 +0,0 @@ -#![allow(dead_code)] - -use crate::auth::{AuthAction, AuthContext, Authorization}; -use crate::connection::AuthHook; -use crate::local::rows::BatchedRows; -use crate::params::Params; -use crate::{ - connection::{BatchRows, Op, UpdateHook}, - errors, -}; -use std::time::Duration; - -use super::{Database, Error, Result, Rows, RowsFuture, Statement, Transaction}; - -use crate::TransactionBehavior; - -use libsql_sys::ffi; -use parking_lot::RwLock; -use std::{ffi::c_int, fmt, path::Path, sync::Arc}; - -struct Container { - cb: Box, -} - -/// A connection to a libSQL database. -#[derive(Clone)] -pub struct Connection { - pub(crate) raw: *mut ffi::sqlite3, - - drop_ref: Arc<()>, - - #[cfg(feature = "replication")] - pub(crate) writer: Option, - - authorizer: Arc>>, -} - -impl Drop for Connection { - fn drop(&mut self) { - self.disconnect() - } -} - -// SAFETY: This is safe because we compile sqlite3 w/ SQLITE_THREADSAFE=1 -unsafe impl Send for Connection {} -// SAFETY: This is safe because we compile sqlite3 w/ SQLITE_THREADSAFE=1 -unsafe impl Sync for Connection {} - -impl Connection { - /// Connect to the database. - pub(crate) fn connect(db: &Database) -> Result { - let mut raw = std::ptr::null_mut(); - let db_path = db.db_path.clone(); - let err = unsafe { - ffi::sqlite3_open_v2( - std::ffi::CString::new(db_path.as_str()) - .unwrap() - .as_c_str() - .as_ptr() as *const _, - &mut raw, - db.flags.bits() as c_int, - std::ptr::null(), - ) - }; - match err { - ffi::SQLITE_OK => {} - _ => { - return Err(Error::ConnectionFailed(format!( - "Unable to open connection to local database {db_path}: {err}", - ))); - } - } - let conn = Connection { - raw, - drop_ref: Arc::new(()), - #[cfg(feature = "replication")] - writer: db.writer()?, - authorizer: Arc::new(RwLock::new(None)), - }; - #[cfg(feature = "sync")] - if let Some(_) = db.sync_ctx { - // We need to make sure database is in WAL mode with checkpointing - // disabled so that we can sync our changes back to a remote - // server. - conn.query("PRAGMA journal_mode = WAL", Params::None)?; - conn.wal_disable_checkpoint()?; - } - Ok(conn) - } - - /// Get a raw handle to the underlying libSQL connection - pub fn handle(&self) -> *mut ffi::sqlite3 { - self.raw - } - - /// Create a connection from a raw handle to the underlying libSQL connection - pub fn from_handle(raw: *mut ffi::sqlite3) -> Self { - Self { - raw, - drop_ref: Arc::new(()), - #[cfg(feature = "replication")] - writer: None, - authorizer: Arc::new(RwLock::new(None)), - } - } - - /// Disconnect from the database. - pub fn disconnect(&mut self) { - if Arc::get_mut(&mut self.drop_ref).is_some() { - unsafe { libsql_sys::ffi::sqlite3_close_v2(self.raw) }; - } - } - - /// Prepare the SQL statement. - pub fn prepare>(&self, sql: S) -> Result { - Statement::prepare(self.clone(), self.raw, sql.into().as_str()) - } - - /// Convenience method to run a prepared statement query. - /// ## Example - /// - /// ```rust,no_run,ignore - /// # use libsql::Result; - /// # use libsql::v1::{Connection, Rows}; - /// # fn create_tables(conn: &Connection) -> Result> { - /// conn.query("SELECT * FROM users WHERE name = ?1;", vec![libsql::Value::from(1)]) - /// # } - /// ``` - pub fn query(&self, sql: S, params: P) -> Result> - where - S: Into, - P: TryInto, - P::Error: Into, - { - let stmt = Statement::prepare(self.clone(), self.raw, sql.into().as_str())?; - let params = params - .try_into() - .map_err(|e| Error::ToSqlConversionFailure(e.into()))?; - let ret = stmt.query(¶ms)?; - Ok(Some(ret)) - } - - /// Convenience method to run multiple SQL statements (that cannot take any - /// parameters). - /// - /// ## Example - /// - /// ```rust,no_run,ignore - /// # use libsql::Result; - /// # use libsql::v1::Connection; - /// # fn create_tables(conn: &Connection) -> Result<()> { - /// conn.execute_batch( - /// "BEGIN; - /// CREATE TABLE foo(x INTEGER); - /// CREATE TABLE bar(y TEXT); - /// COMMIT;", - /// ) - /// # } - /// ``` - /// - /// # Failure - /// - /// Will return `Err` if `sql` cannot be converted to a C-compatible string - /// or if the underlying SQLite call fails. - pub fn execute_batch(&self, sql: S) -> Result - where - S: Into, - { - let sql = sql.into(); - let mut sql = sql.as_str(); - - let mut batch_rows = Vec::new(); - - while !sql.is_empty() { - let stmt = self.prepare(sql)?; - - let tail = if !stmt.inner.raw_stmt.is_null() { - let returned_rows = stmt.step()?; - - let tail = stmt.tail(); - - // Check if there are rows to be extracted, we must do this upfront due to the lazy - // nature of sqlite and our somewhat hacked batch command. - if returned_rows { - // Extract columns - let cols = stmt - .columns() - .iter() - .enumerate() - .map(|(i, c)| { - use crate::value::ValueType; - - let val = stmt.inner.column_type(i as i32); - let t = match val { - libsql_sys::ffi::SQLITE_INTEGER => ValueType::Integer, - libsql_sys::ffi::SQLITE_FLOAT => ValueType::Real, - libsql_sys::ffi::SQLITE_BLOB => ValueType::Blob, - libsql_sys::ffi::SQLITE_TEXT => ValueType::Text, - libsql_sys::ffi::SQLITE_NULL => ValueType::Null, - _ => unreachable!("unknown column type {} at index {}", val, i), - }; - - (c.name.to_string(), t) - }) - .collect::>(); - - let mut rows = Vec::new(); - - // If returned rows we must extract the rows available right away instead of - // using the `Rows` type we have already. This is due to the step api once its - // returned SQLITE_ROWS we must extract them before we call step again. - { - let row = crate::local::Row { stmt: stmt.clone() }; - - let mut values = Vec::with_capacity(cols.len()); - - for i in 0..cols.len() { - let value = row.get_value(i as i32)?; - - values.push(value); - } - - rows.push(values); - } - - // Now we can use the normal rows type to extract any n+1 rows - let rows_sys = Rows::new(stmt); - - while let Some(row) = rows_sys.next()? { - let mut values = Vec::with_capacity(cols.len()); - - for i in 0..cols.len() { - let value = row.get_value(i as i32)?; - - values.push(value); - } - - rows.push(values); - } - - rows.len(); - - batch_rows.push(Some(crate::Rows::new(BatchedRows::new(cols, rows)))); - } else { - batch_rows.push(None); - } - - tail - } else { - stmt.tail() - }; - - if tail == 0 || tail >= sql.len() { - break; - } - - sql = &sql[tail..]; - } - - Ok(BatchRows::new(batch_rows)) - } - - fn execute_transactional_batch_inner(&self, sql: S) -> Result<()> - where - S: Into, - { - let sql = sql.into(); - let mut sql = sql.as_str(); - while !sql.is_empty() { - let stmt = self.prepare(sql)?; - - let tail = stmt.tail(); - let stmt_sql = if tail == 0 || tail >= sql.len() { - sql - } else { - &sql[..tail] - }; - let prefix_count = stmt_sql.chars().take_while(|c| c.is_whitespace()).count(); - let stmt_sql = &stmt_sql[prefix_count..]; - if stmt_sql.starts_with("BEGIN") - || stmt_sql.starts_with("COMMIT") - || stmt_sql.starts_with("ROLLBACK") - || stmt_sql.starts_with("END") - { - return Err(Error::TransactionalBatchError( - "Transactions forbidden inside transactional batch".to_string(), - )); - } - - if !stmt.inner.raw_stmt.is_null() { - stmt.step()?; - } - - if tail == 0 || tail >= sql.len() { - break; - } - - sql = &sql[tail..]; - } - - Ok(()) - } - - pub fn execute_transactional_batch(&self, sql: S) -> Result<()> - where - S: Into, - { - self.execute("BEGIN TRANSACTION", Params::None)?; - - match self.execute_transactional_batch_inner(sql) { - Ok(_) => { - self.execute("COMMIT", Params::None)?; - Ok(()) - } - Err(e) => { - self.execute("ROLLBACK", Params::None)?; - Err(e) - } - } - } - - /// Execute the SQL statement synchronously. - /// - /// If you execute a SQL query statement (e.g. `SELECT` statement) that - /// returns the number of rows changed. - /// - /// This method blocks the thread until the SQL statement is executed. - pub fn execute(&self, sql: S, params: P) -> Result - where - S: Into, - P: TryInto, - P::Error: Into, - { - let stmt = Statement::prepare(self.clone(), self.raw, sql.into().as_str())?; - let params = params - .try_into() - .map_err(|e| Error::ToSqlConversionFailure(e.into()))?; - stmt.execute(¶ms) - } - - /// Execute the SQL statement synchronously. - /// - /// This method never blocks the thread until, but instead returns a - /// `RowsFuture` object immediately that can be used to deferredly - /// execute the statement. - pub fn execute_async(&self, sql: S, params: P) -> RowsFuture - where - S: Into, - P: Into, - { - RowsFuture { - conn: self.clone(), - sql: sql.into(), - params: params.into(), - } - } - - /// Begin a new transaction in DEFERRED mode, which is the default. - pub fn transaction(&self) -> Result { - self.transaction_with_behavior(TransactionBehavior::Deferred) - } - - /// Begin a new transaction in the given mode. - pub fn transaction_with_behavior( - &self, - tx_behavior: TransactionBehavior, - ) -> Result { - Transaction::begin(self.clone(), tx_behavior) - } - - pub fn interrupt(&self) -> Result<()> { - unsafe { ffi::sqlite3_interrupt(self.raw) }; - Ok(()) - } - - pub fn busy_timeout(&self, timeout: Duration) -> Result<()> { - unsafe { ffi::sqlite3_busy_timeout(self.raw, timeout.as_millis() as i32) }; - Ok(()) - } - - pub fn is_autocommit(&self) -> bool { - unsafe { ffi::sqlite3_get_autocommit(self.raw) != 0 } - } - - pub fn changes(&self) -> u64 { - unsafe { ffi::sqlite3_changes64(self.raw) as u64 } - } - - pub fn total_changes(&self) -> u64 { - unsafe { ffi::sqlite3_total_changes(self.raw) as u64 } - } - - pub fn last_insert_rowid(&self) -> i64 { - unsafe { ffi::sqlite3_last_insert_rowid(self.raw) } - } - - #[cfg(feature = "replication")] - pub(crate) fn writer(&self) -> Option<&crate::replication::Writer> { - self.writer.as_ref() - } - - #[cfg(feature = "replication")] - pub(crate) fn new_connection_writer(&self) -> Option { - self.writer.as_ref().cloned().map(|mut w| { - w.new_client_id(); - w - }) - } - - /// Installs update hook - pub fn add_update_hook(&self, cb: Box) { - let c = Box::new(Container { cb }); - let ptr: *mut Container = std::ptr::from_mut(Box::leak(c)); - - let old_data = unsafe { - ffi::sqlite3_update_hook( - self.raw, - Some(update_hook_cb), - ptr as *mut ::std::os::raw::c_void, - ) - }; - - if !old_data.is_null() { - let _ = unsafe { Box::from_raw(old_data as *mut Container) }; - } - } - - pub fn enable_load_extension(&self, onoff: bool) -> Result<()> { - // SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION configration verb accepts 2 additional parameters: an on/off flag and a pointer to an c_int where new state of the parameter will be written (or NULL if reporting back the setting is not needed) - // See: https://sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigenableloadextension - let err = unsafe { - ffi::sqlite3_db_config( - self.raw, - ffi::SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, - onoff as i32, - std::ptr::null::(), - ) - }; - match err { - ffi::SQLITE_OK => Ok(()), - _ => Err(errors::Error::SqliteFailure( - err, - errors::error_from_code(err), - )), - } - } - - pub fn load_extension(&self, dylib_path: &Path, entry_point: Option<&str>) -> Result<()> { - let mut raw_err_msg: *mut std::ffi::c_char = std::ptr::null_mut(); - let dylib_path = match dylib_path.to_str() { - Some(dylib_path) => std::ffi::CString::new(dylib_path).unwrap(), - None => { - return Err(crate::Error::Misuse(format!( - "dylib path is not a valid utf8 string" - ))) - } - }; - let err = match entry_point { - Some(entry_point) => { - let entry_point = std::ffi::CString::new(entry_point).unwrap(); - unsafe { - ffi::sqlite3_load_extension( - self.raw, - dylib_path.as_ptr(), - entry_point.as_ptr(), - &mut raw_err_msg, - ) - } - } - None => unsafe { - ffi::sqlite3_load_extension( - self.raw, - dylib_path.as_ptr(), - std::ptr::null(), - &mut raw_err_msg, - ) - }, - }; - match err { - ffi::SQLITE_OK => Ok(()), - _ => { - let err_msg = unsafe { std::ffi::CStr::from_ptr(raw_err_msg) }; - let err_msg = err_msg.to_string_lossy().to_string(); - unsafe { ffi::sqlite3_free(raw_err_msg as *mut std::ffi::c_void) }; - Err(errors::Error::SqliteFailure(err, err_msg)) - } - } - } - - pub fn authorizer(&self, hook: Option) -> Result<()> { - unsafe { - let rc = - libsql_sys::ffi::sqlite3_set_authorizer(self.handle(), None, std::ptr::null_mut()); - if rc != ffi::SQLITE_OK { - return Err(crate::errors::Error::SqliteFailure( - rc as std::ffi::c_int, - "Failed to clear authorizer".to_string(), - )); - } - } - - *self.authorizer.write() = hook.clone(); - - let (callback, user_data) = match hook { - Some(_) => { - let callback = authorizer_callback as unsafe extern "C" fn(_, _, _, _, _, _) -> _; - let user_data = self as *const Connection as *mut ::std::os::raw::c_void; - (Some(callback), user_data) - } - None => (None, std::ptr::null_mut()), - }; - - let rc = - unsafe { libsql_sys::ffi::sqlite3_set_authorizer(self.handle(), callback, user_data) }; - if rc != ffi::SQLITE_OK { - return Err(crate::errors::Error::SqliteFailure( - rc as std::ffi::c_int, - "Failed to set authorizer".to_string(), - )); - } - Ok(()) - } - - pub(crate) fn wal_checkpoint(&self, truncate: bool) -> Result<()> { - let mut pn_log = 0i32; - let mut pn_ckpt = 0i32; - let checkpoint_mode = if truncate { - libsql_sys::ffi::SQLITE_CHECKPOINT_TRUNCATE - } else { - libsql_sys::ffi::SQLITE_CHECKPOINT_PASSIVE - }; - let rc = unsafe { - libsql_sys::ffi::sqlite3_wal_checkpoint_v2( - self.handle(), - std::ptr::null(), - checkpoint_mode, - &mut pn_log, - &mut pn_ckpt, - ) - }; - if rc != 0 { - let err_msg = unsafe { libsql_sys::ffi::sqlite3_errmsg(self.handle()) }; - let err_msg = unsafe { std::ffi::CStr::from_ptr(err_msg) }; - let err_msg = err_msg.to_string_lossy().to_string(); - return Err(crate::errors::Error::SqliteFailure( - rc as std::ffi::c_int, - format!("Failed to checkpoint WAL: {}", err_msg), - )); - } - if truncate && (pn_log != 0 || pn_ckpt != 0) { - return Err(crate::errors::Error::SqliteFailure( - libsql_sys::ffi::SQLITE_ERROR, - "unable to truncate WAL".to_string(), - )); - } - Ok(()) - } - - pub(crate) fn wal_frame_count(&self) -> u32 { - let mut max_frame_no: std::os::raw::c_uint = 0; - unsafe { libsql_sys::ffi::libsql_wal_frame_count(self.handle(), &mut max_frame_no) }; - - max_frame_no - } - - pub(crate) fn wal_get_frame(&self, frame_no: u32, page_size: u32) -> Result { - use bytes::BufMut; - - let frame_size: usize = 24 + page_size as usize; - - // Use a BytesMut to provide cheaper clones of frame data (think retries) - // and more efficient buffer usage for extracting wal frames and spliting them off. - let mut buf = bytes::BytesMut::with_capacity(frame_size); - - if frame_no == 0 { - return Err(errors::Error::SqliteFailure( - 1, - "frame_no must be non-zero".to_string(), - )); - } - - let rc = unsafe { - libsql_sys::ffi::libsql_wal_get_frame( - self.handle(), - frame_no, - buf.chunk_mut().as_mut_ptr() as *mut _, - frame_size as u32, - ) - }; - - if rc != 0 { - return Err(crate::errors::Error::SqliteFailure( - rc as std::ffi::c_int, - format!("Failed to get frame: {}", frame_no), - )); - } - - unsafe { buf.advance_mut(frame_size) }; - - Ok(buf) - } - - fn wal_disable_checkpoint(&self) -> Result<()> { - let rc = unsafe { libsql_sys::ffi::libsql_wal_disable_checkpoint(self.handle()) }; - if rc != 0 { - return Err(crate::errors::Error::SqliteFailure( - rc as std::ffi::c_int, - format!("wal_disable_checkpoint failed"), - )); - } - Ok(()) - } - fn wal_insert_begin(&self) -> Result<()> { - let rc = unsafe { libsql_sys::ffi::libsql_wal_insert_begin(self.handle()) }; - if rc != 0 { - return Err(crate::errors::Error::SqliteFailure( - rc as std::ffi::c_int, - format!("wal_insert_begin failed"), - )); - } - Ok(()) - } - - fn wal_insert_end(&self) -> Result<()> { - let rc = unsafe { libsql_sys::ffi::libsql_wal_insert_end(self.handle()) }; - if rc != 0 { - return Err(crate::errors::Error::SqliteFailure( - rc as std::ffi::c_int, - format!("wal_insert_end failed"), - )); - } - Ok(()) - } - - fn wal_insert_frame(&self, frame_no: u32, frame: &[u8]) -> Result<()> { - let mut conflict = 0i32; - let rc = unsafe { - libsql_sys::ffi::libsql_wal_insert_frame( - self.handle(), - frame_no, - frame.as_ptr() as *mut std::ffi::c_void, - frame.len() as u32, - &mut conflict, - ) - }; - - if conflict != 0 { - return Err(errors::Error::WalConflict); - } - if rc != 0 { - return Err(errors::Error::SqliteFailure( - rc as std::ffi::c_int, - "wal_insert_frame failed".to_string(), - )); - } - - Ok(()) - } - - pub(crate) fn wal_insert_handle(&self) -> WalInsertHandle<'_> { - WalInsertHandle { - conn: self, - in_session: RwLock::new(false), - } - } - - fn reserved_bytes(&self, reserve: Option) -> Result { - let mut reserve_value = reserve.unwrap_or(0) as std::ffi::c_int; - let rc = unsafe { - ffi::sqlite3_file_control( - self.raw, - "main\0".as_ptr() as *const _, - ffi::SQLITE_FCNTL_RESERVE_BYTES, - &mut reserve_value as *mut _ as *mut std::ffi::c_void, - ) - }; - if rc != ffi::SQLITE_OK { - return Err(Error::SqliteFailure( - rc, - errors::error_from_handle(self.raw), - )); - } - Ok(reserve_value as i32) - } - - pub fn set_reserved_bytes(&self, reserved_bytes: i32) -> Result<()> { - self.reserved_bytes(Some(reserved_bytes))?; - Ok(()) - } - - pub fn get_reserved_bytes(&self) -> Result { - self.reserved_bytes(None) - } -} - -unsafe extern "C" fn authorizer_callback( - user_data: *mut ::std::os::raw::c_void, - code: ::std::os::raw::c_int, - arg1: *const ::std::os::raw::c_char, - arg2: *const ::std::os::raw::c_char, - database_name: *const ::std::os::raw::c_char, - accessor: *const ::std::os::raw::c_char, -) -> ::std::os::raw::c_int { - let conn = user_data as *const Connection; - let hook = unsafe { (*conn).authorizer.read() }; - let hook = match &*hook { - Some(hook) => hook, - None => return ffi::SQLITE_OK, - }; - let arg1 = if arg1.is_null() { - None - } else { - unsafe { std::ffi::CStr::from_ptr(arg1).to_str().ok() } - }; - - let arg2 = if arg2.is_null() { - None - } else { - unsafe { std::ffi::CStr::from_ptr(arg2).to_str().ok() } - }; - let database_name = if database_name.is_null() { - None - } else { - unsafe { std::ffi::CStr::from_ptr(database_name).to_str().ok() } - }; - let accessor = if accessor.is_null() { - None - } else { - unsafe { std::ffi::CStr::from_ptr(accessor).to_str().ok() } - }; - let action = AuthAction::from_raw(code, arg1, arg2); - let auth_context = AuthContext { - action, - database_name, - accessor, - }; - match hook(&auth_context) { - Authorization::Allow => ffi::SQLITE_OK, - Authorization::Deny => ffi::SQLITE_DENY, - Authorization::Ignore => ffi::SQLITE_IGNORE, - } -} - -pub(crate) struct WalInsertHandle<'a> { - conn: &'a Connection, - in_session: RwLock, -} - -impl WalInsertHandle<'_> { - pub fn insert_at(&self, frame_no: u32, frame: &[u8]) -> Result<()> { - assert!(*self.in_session.read()); - self.conn.wal_insert_frame(frame_no, frame) - } - - pub fn in_session(&self) -> bool { - *self.in_session.read() - } - - pub fn begin(&self) -> Result<()> { - assert!(!*self.in_session.read()); - self.conn.wal_insert_begin()?; - *self.in_session.write() = true; - Ok(()) - } - - pub fn end(&self) -> Result<()> { - assert!(*self.in_session.read()); - self.conn.wal_insert_end()?; - *self.in_session.write() = false; - Ok(()) - } -} - -impl Drop for WalInsertHandle<'_> { - fn drop(&mut self) { - if *self.in_session.read() { - if let Err(err) = self.conn.wal_insert_end() { - tracing::error!("{:?}", err); - Err(err).unwrap() - } - } - } -} - -impl fmt::Debug for Connection { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Connection").finish() - } -} - -#[no_mangle] -extern "C" fn update_hook_cb( - data: *mut ::std::os::raw::c_void, - op: ::std::os::raw::c_int, - db_name: *const ::std::os::raw::c_char, - table_name: *const ::std::os::raw::c_char, - row_id: i64, -) { - let db = unsafe { std::ffi::CStr::from_ptr(db_name).to_string_lossy() }; - let table = unsafe { std::ffi::CStr::from_ptr(table_name).to_string_lossy() }; - - let c = unsafe { &mut *(data as *mut Container) }; - let o = match op { - 9 => Op::Delete, - 18 => Op::Insert, - 23 => Op::Update, - _ => unreachable!("Unknown operation {op}"), - }; - - (*c.cb)(o, &db, &table, row_id); -} - -#[cfg(test)] -mod tests { - use crate::{ - local::{Connection, Database}, - params::Params, - OpenFlags, - }; - - #[tokio::test] - pub async fn test_kek() { - let temp_dir = tempfile::tempdir().unwrap(); - let path1 = temp_dir.path().join("local1.db"); - let db1 = Database::new(path1.to_str().unwrap().to_string(), OpenFlags::default()); - let conn1 = Connection::connect(&db1).unwrap(); - conn1 - .query("PRAGMA journal_mode = WAL", Params::None) - .unwrap(); - conn1.wal_disable_checkpoint().unwrap(); - - let path2 = temp_dir.path().join("local2.db"); - let db2 = Database::new(path2.to_str().unwrap().to_string(), OpenFlags::default()); - let conn2 = Connection::connect(&db2).unwrap(); - conn2 - .query("PRAGMA journal_mode = WAL", Params::None) - .unwrap(); - conn2.wal_disable_checkpoint().unwrap(); - - conn1.execute("CREATE TABLE t(x)", Params::None).unwrap(); - const CNT: usize = 32; - for _ in 0..CNT { - conn1 - .execute( - "INSERT INTO t VALUES (randomblob(1024 * 1024))", - Params::None, - ) - .unwrap(); - } - let handle = conn2.wal_insert_handle(); - handle.begin().unwrap(); - - let frame_count = conn1.wal_frame_count(); - for frame_no in 0..frame_count { - let frame = conn1.wal_get_frame(frame_no + 1, 4096).unwrap(); - handle.insert_at(frame_no as u32 + 1, &frame).unwrap(); - } - let result = conn2.query("SELECT COUNT(*) FROM t", Params::None).unwrap(); - let row = result.unwrap().next().unwrap().unwrap(); - let column = row.get_value(0).unwrap(); - let cnt = *column.as_integer().unwrap(); - assert_eq!(cnt, 32 as i64); - } - - #[tokio::test] - pub async fn test_reserved_bytes() { - let temp_dir = tempfile::tempdir().unwrap(); - let db_path = temp_dir.path().join("local1.db"); - let reserved_bytes = 28; - - { - let db = Database::new(db_path.to_str().unwrap().to_string(), OpenFlags::default()); - let conn = Connection::connect(&db).unwrap(); - conn.query("PRAGMA journal_mode = WAL", Params::None) - .unwrap(); - conn.set_reserved_bytes(reserved_bytes).unwrap(); - conn.query("VACUUM", Params::None).unwrap(); - let reserved = conn.get_reserved_bytes().unwrap(); - assert_eq!(reserved, reserved_bytes); - } - - // let's verify we can see this from another connection - { - let db = Database::new(db_path.to_str().unwrap().to_string(), OpenFlags::default()); - let conn = Connection::connect(&db).unwrap(); - let reserved = conn.get_reserved_bytes().unwrap(); - assert_eq!(reserved, reserved_bytes); - } - - // lets make some inserts, checkpoint and verify again - { - let db = Database::new(db_path.to_str().unwrap().to_string(), OpenFlags::default()); - let conn = Connection::connect(&db).unwrap(); - conn.execute("CREATE TABLE t(x)", Params::None).unwrap(); - const CNT: usize = 8; - for _ in 0..CNT { - conn.execute( - "INSERT INTO t VALUES (randomblob(1024 * 1024))", - Params::None, - ) - .unwrap(); - } - conn.wal_checkpoint(true).unwrap(); - let reserved = conn.get_reserved_bytes().unwrap(); - assert_eq!(reserved, reserved_bytes); - } - } -} diff --git a/vendor/libsql/src/local/database.rs b/vendor/libsql/src/local/database.rs deleted file mode 100644 index 5d5eda0b..00000000 --- a/vendor/libsql/src/local/database.rs +++ /dev/null @@ -1,494 +0,0 @@ -use std::sync::Once; - -cfg_replication!( - use http::uri::InvalidUri; - use crate::database::{EncryptionConfig, FrameNo}; - - use crate::replication::client::Client; - use crate::replication::local_client::LocalClient; - use crate::replication::remote_client::RemoteClient; - use crate::replication::EmbeddedReplicator; - pub use crate::replication::Frames; - pub use crate::replication::SyncUsageStats; - - pub struct ReplicationContext { - pub(crate) replicator: EmbeddedReplicator, - client: Option, - read_your_writes: bool, - } -); - -cfg_sync! { - use crate::sync::SyncContext; - use tokio::sync::Mutex; - use std::sync::Arc; -} - -use crate::{database::OpenFlags, local::connection::Connection, Error::ConnectionFailed, Result}; -use libsql_sys::ffi; - -// A libSQL database. -pub struct Database { - pub db_path: String, - pub flags: OpenFlags, - #[cfg(feature = "replication")] - pub replication_ctx: Option, - #[cfg(feature = "sync")] - pub sync_ctx: Option>>, -} - -impl Database { - /// Open a local database file. - pub fn open>(db_path: S, flags: OpenFlags) -> Result { - let db_path = db_path.into(); - - if db_path.starts_with("libsql:") - || db_path.starts_with("http:") - || db_path.starts_with("https:") - { - Err(ConnectionFailed(format!( - "Unable to open local database {db_path} with Database::open()" - ))) - } else { - Ok(Database::new(db_path, flags)) - } - } - - /// Safety: this is like `open` but does not enfoce that sqlite_config has THREADSAFE set to - /// `SQLITE_CONFIG_SERIALIZED`, calling - pub unsafe fn open_raw>(db_path: S, flags: OpenFlags) -> Result { - let db_path = db_path.into(); - - if db_path.starts_with("libsql:") - || db_path.starts_with("http:") - || db_path.starts_with("https:") - { - Err(ConnectionFailed(format!( - "Unable to open local database {db_path} with Database::open()" - ))) - } else { - Ok(Database { - db_path, - flags, - #[cfg(feature = "replication")] - replication_ctx: None, - #[cfg(feature = "sync")] - sync_ctx: None, - }) - } - } - - #[cfg(feature = "replication")] - pub async fn open_http_sync( - connector: crate::util::ConnectorService, - db_path: String, - endpoint: String, - auth_token: String, - encryption_config: Option, - sync_interval: Option, - ) -> Result { - Self::open_http_sync_internal( - connector, - db_path, - endpoint, - auth_token, - None, - false, - encryption_config, - sync_interval, - None, - None, - ) - .await - } - - #[cfg(feature = "replication")] - #[doc(hidden)] - pub async fn open_http_sync_internal( - connector: crate::util::ConnectorService, - db_path: String, - endpoint: String, - auth_token: String, - version: Option, - read_your_writes: bool, - encryption_config: Option, - sync_interval: Option, - http_request_callback: Option, - namespace: Option, - ) -> Result { - use std::path::PathBuf; - - use crate::util::coerce_url_scheme; - - let mut db = Database::open(&db_path, OpenFlags::default())?; - - let endpoint = coerce_url_scheme(endpoint); - let remote = crate::replication::client::Client::new( - connector.clone(), - endpoint - .as_str() - .try_into() - .map_err(|e: InvalidUri| crate::Error::Replication(e.into()))?, - auth_token.clone(), - version.as_deref(), - http_request_callback.clone(), - namespace, - ) - .map_err(|e| crate::Error::Replication(e.into()))?; - let path = PathBuf::from(db_path); - let client = RemoteClient::new(remote.clone(), &path) - .await - .map_err(|e| crate::errors::Error::ConnectionFailed(e.to_string()))?; - - let replicator = - EmbeddedReplicator::with_remote(client, path, 1000, encryption_config, sync_interval) - .await?; - - db.replication_ctx = Some(ReplicationContext { - replicator, - client: Some(remote), - read_your_writes, - }); - - Ok(db) - } - - #[cfg(feature = "replication")] - #[doc(hidden)] - pub async unsafe fn open_http_sync_internal2( - connector: crate::util::ConnectorService, - db_path: String, - endpoint: String, - auth_token: String, - version: Option, - read_your_writes: bool, - encryption_config: Option, - sync_interval: Option, - http_request_callback: Option, - namespace: Option, - ) -> Result { - use std::path::PathBuf; - - use crate::util::coerce_url_scheme; - - let mut db = Database::open_raw(&db_path, OpenFlags::default())?; - - let endpoint = coerce_url_scheme(endpoint); - let remote = crate::replication::client::Client::new( - connector.clone(), - endpoint - .as_str() - .try_into() - .map_err(|e: InvalidUri| crate::Error::Replication(e.into()))?, - auth_token.clone(), - version.as_deref(), - http_request_callback.clone(), - namespace, - ) - .map_err(|e| crate::Error::Replication(e.into()))?; - let path = PathBuf::from(db_path); - let client = RemoteClient::new(remote.clone(), &path) - .await - .map_err(|e| crate::errors::Error::ConnectionFailed(e.to_string()))?; - - let replicator = - EmbeddedReplicator::with_remote(client, path, 1000, encryption_config, sync_interval) - .await?; - - db.replication_ctx = Some(ReplicationContext { - replicator, - client: Some(remote), - read_your_writes, - }); - - Ok(db) - } - - #[cfg(feature = "sync")] - #[doc(hidden)] - pub async fn open_local_with_offline_writes( - connector: crate::util::ConnectorService, - db_path: impl Into, - flags: OpenFlags, - endpoint: String, - auth_token: String, - remote_encryption: Option, - ) -> Result { - let db_path = db_path.into(); - let endpoint = if endpoint.starts_with("libsql:") { - endpoint.replace("libsql:", "https:") - } else { - endpoint - }; - let mut db = Database::open(&db_path, flags)?; - - let sync_ctx = SyncContext::new( - connector, - db_path.into(), - endpoint, - Some(auth_token), - remote_encryption, - ) - .await?; - db.sync_ctx = Some(Arc::new(Mutex::new(sync_ctx))); - - Ok(db) - } - - #[cfg(feature = "replication")] - pub async fn open_local_sync( - db_path: impl Into, - flags: OpenFlags, - encryption_config: Option, - ) -> Result { - use std::path::PathBuf; - - let db_path = db_path.into(); - let mut db = Database::open(&db_path, flags)?; - - let path = PathBuf::from(db_path); - let client = LocalClient::new(&path) - .await - .map_err(|e| crate::Error::Replication(e.into()))?; - - let replicator = - EmbeddedReplicator::with_local(client, path, 1000, encryption_config).await?; - - db.replication_ctx = Some(ReplicationContext { - replicator, - client: None, - read_your_writes: false, - }); - - Ok(db) - } - - #[cfg(feature = "replication")] - pub async fn open_local_sync_remote_writes( - connector: crate::util::ConnectorService, - db_path: impl Into, - endpoint: String, - auth_token: String, - version: Option, - flags: OpenFlags, - encryption_config: Option, - http_request_callback: Option, - namespace: Option, - ) -> Result { - use std::path::PathBuf; - - let db_path = db_path.into(); - let mut db = Database::open(&db_path, flags)?; - - use crate::util::coerce_url_scheme; - - let endpoint = coerce_url_scheme(endpoint); - let remote = crate::replication::client::Client::new( - connector, - endpoint - .as_str() - .try_into() - .map_err(|e: InvalidUri| crate::Error::Replication(e.into()))?, - auth_token, - version.as_deref(), - http_request_callback, - namespace, - ) - .map_err(|e| crate::Error::Replication(e.into()))?; - - let path = PathBuf::from(db_path); - let client = LocalClient::new(&path) - .await - .map_err(|e| crate::Error::Replication(e.into()))?; - - let replicator = - EmbeddedReplicator::with_local(client, path, 1000, encryption_config).await?; - - db.replication_ctx = Some(ReplicationContext { - replicator, - client: Some(remote), - read_your_writes: false, - }); - - Ok(db) - } - - pub fn new(db_path: String, flags: OpenFlags) -> Database { - static LIBSQL_INIT: Once = Once::new(); - - LIBSQL_INIT.call_once(|| { - // Ensure that we are configured with the correct threading model - // if this config is not set correctly the entire api is unsafe. - unsafe { - assert_eq!( - ffi::sqlite3_config(ffi::SQLITE_CONFIG_SERIALIZED), - ffi::SQLITE_OK, - "libsql was configured with an incorrect threading configuration and - the api is not safe to use. Please check that no multi-thread options have - been set. If nothing was configured then please open an issue at: - https://github.com/libsql/libsql" - ); - - assert_eq!( - ffi::sqlite3_initialize(), - ffi::SQLITE_OK, - "libsql failed to initialize" - ); - } - }); - - Database { - db_path, - flags, - #[cfg(feature = "replication")] - replication_ctx: None, - #[cfg(feature = "sync")] - sync_ctx: None, - } - } - - pub fn connect(&self) -> Result { - Connection::connect(self) - } - - #[cfg(feature = "replication")] - pub(crate) fn writer(&self) -> Result> { - use crate::replication::Writer; - if let Some(ReplicationContext { - client: Some(ref client), - replicator, - read_your_writes, - }) = &self.replication_ctx - { - Ok(Some(Writer { - client: client.clone(), - replicator: if *read_your_writes { - Some(replicator.clone()) - } else { - None - }, - })) - } else { - Ok(None) - } - } - - #[cfg(feature = "replication")] - /// Perform a sync step, returning the new replication index, or None, if the nothing was - /// replicated yet - pub async fn sync_oneshot(&self) -> Result { - if let Some(ctx) = &self.replication_ctx { - ctx.replicator.sync_oneshot().await - } else { - Err(crate::errors::Error::Misuse( - "No replicator available. Use Database::with_replicator() to enable replication" - .to_string(), - )) - } - } - - #[cfg(feature = "replication")] - /// Sync with primary - pub async fn sync(&self) -> Result { - Ok(self.sync_oneshot().await?) - } - - #[cfg(feature = "replication")] - /// Return detailed logs about bytes synced with primary - pub async fn get_sync_usage_stats(&self) -> Result { - if let Some(ctx) = &self.replication_ctx { - let sync_stats = ctx.replicator.get_sync_usage_stats().await?; - Ok(sync_stats) - } else { - Err(crate::errors::Error::Misuse( - "No replicator available. Use Database::with_replicator() to enable replication" - .to_string(), - )) - } - } - - #[cfg(feature = "replication")] - /// Sync with primary at least to a given replication index - pub async fn sync_until( - &self, - replication_index: FrameNo, - ) -> Result { - if let Some(ctx) = &self.replication_ctx { - let mut frame_no: Option = ctx.replicator.committed_frame_no().await; - let mut frames_synced: usize = 0; - while frame_no.unwrap_or(0) < replication_index { - let res = ctx.replicator.sync_oneshot().await?; - frame_no = res.frame_no(); - frames_synced += res.frames_synced(); - } - Ok(crate::database::Replicated { - frame_no, - frames_synced, - }) - } else { - Err(crate::errors::Error::Misuse( - "No replicator available. Use Database::with_replicator() to enable replication" - .to_string(), - )) - } - } - - #[cfg(feature = "replication")] - pub async fn sync_frames(&self, frames: Frames) -> Result> { - if let Some(ref ctx) = self.replication_ctx { - ctx.replicator.sync_frames(frames).await - } else { - Err(crate::errors::Error::Misuse( - "No replicator available. Use Database::with_replicator() to enable replication" - .to_string(), - )) - } - } - - #[cfg(feature = "replication")] - pub async fn flush_replicator(&self) -> Result> { - if let Some(ref ctx) = self.replication_ctx { - ctx.replicator.flush().await - } else { - Err(crate::errors::Error::Misuse( - "No replicator available. Use Database::with_replicator() to enable replication" - .to_string(), - )) - } - } - - #[cfg(feature = "replication")] - pub async fn replication_index(&self) -> Result> { - if let Some(ref ctx) = self.replication_ctx { - Ok(ctx.replicator.committed_frame_no().await) - } else { - Err(crate::errors::Error::Misuse( - "No replicator available. Use Database::with_replicator() to enable replication" - .to_string(), - )) - } - } - - #[cfg(feature = "sync")] - /// Sync WAL frames to remote. - pub async fn sync_offline(&self) -> Result { - let mut sync_ctx = self.sync_ctx.as_ref().unwrap().lock().await; - // it is important we call `bootstrap` before we `sync`. Because sync uses a connection - // to the db and during bootstrap we replace the sqlite db file. This can lead to - // inconsistencies and data corruption. - crate::sync::bootstrap_db(&mut sync_ctx).await?; - let conn = self.connect()?; - crate::sync::sync_offline(&mut sync_ctx, &conn).await - } - - #[cfg(feature = "sync")] - /// Brings the .db file from server, if required. - pub async fn bootstrap_db(&self) -> Result<()> { - let mut sync_ctx = self.sync_ctx.as_ref().unwrap().lock().await; - crate::sync::bootstrap_db(&mut sync_ctx).await - } - - pub(crate) fn path(&self) -> &str { - &self.db_path - } -} diff --git a/vendor/libsql/src/local/impls.rs b/vendor/libsql/src/local/impls.rs deleted file mode 100644 index 10180c03..00000000 --- a/vendor/libsql/src/local/impls.rs +++ /dev/null @@ -1,234 +0,0 @@ -use std::sync::Arc; -use std::time::Duration; -use std::{fmt, path::Path}; - -use crate::{ - connection::{AuthHook, BatchRows, Conn, UpdateHook}, - params::Params, - rows::{ColumnsInner, RowInner, RowsInner}, - statement::Stmt, - transaction::Tx, - Column, Connection, Result, Row, Rows, Statement, Transaction, TransactionBehavior, Value, - ValueType, -}; - -#[derive(Clone)] -pub(crate) struct LibsqlConnection { - pub(crate) conn: super::Connection, -} - -#[async_trait::async_trait] -impl Conn for LibsqlConnection { - async fn execute(&self, sql: &str, params: Params) -> Result { - self.conn.execute(sql, params) - } - - async fn execute_batch(&self, sql: &str) -> Result { - self.conn.execute_batch(sql) - } - - async fn execute_transactional_batch(&self, sql: &str) -> Result { - self.conn.execute_transactional_batch(sql)?; - Ok(BatchRows::empty()) - } - - async fn prepare(&self, sql: &str) -> Result { - let sql = sql.to_string(); - - let stmt = self.conn.prepare(sql)?; - - Ok(Statement { - inner: Box::new(LibsqlStmt(stmt)), - }) - } - - async fn transaction(&self, tx_behavior: TransactionBehavior) -> Result { - let tx = crate::local::Transaction::begin(self.conn.clone(), tx_behavior)?; - // TODO(lucio): Can we just use the conn passed to the transaction? - Ok(Transaction { - inner: Box::new(LibsqlTx(Some(tx))), - conn: Connection { - conn: Arc::new(self.clone()), - }, - close: None, - }) - } - - fn interrupt(&self) -> Result<()> { - self.conn.interrupt() - } - - fn busy_timeout(&self, timeout: Duration) -> Result<()> { - self.conn.busy_timeout(timeout) - } - - fn is_autocommit(&self) -> bool { - self.conn.is_autocommit() - } - - fn changes(&self) -> u64 { - self.conn.changes() - } - - fn total_changes(&self) -> u64 { - self.conn.total_changes() - } - - fn last_insert_rowid(&self) -> i64 { - self.conn.last_insert_rowid() - } - - async fn reset(&self) {} - - fn set_reserved_bytes(&self, reserved_bytes: i32) -> Result<()> { - self.conn.set_reserved_bytes(reserved_bytes) - } - - fn get_reserved_bytes(&self) -> Result { - self.conn.get_reserved_bytes() - } - - fn enable_load_extension(&self, onoff: bool) -> Result<()> { - self.conn.enable_load_extension(onoff) - } - - fn load_extension(&self, dylib_path: &Path, entry_point: Option<&str>) -> Result<()> { - self.conn.load_extension(dylib_path, entry_point) - } - - fn authorizer(&self, hook: Option) -> Result<()> { - self.conn.authorizer(hook) - } - - fn add_update_hook(&self, cb: Box) -> Result<()> { - Ok(self.conn.add_update_hook(cb)) - } -} - -pub(crate) struct LibsqlStmt(pub crate::local::Statement); - -#[async_trait::async_trait] -impl Stmt for LibsqlStmt { - fn finalize(&mut self) { - self.0.finalize(); - } - - async fn execute(&self, params: &Params) -> Result { - let params = params.clone(); - let stmt = self.0.clone(); - - stmt.execute(¶ms).map(|i| i as usize) - } - - async fn query(&self, params: &Params) -> Result { - let params = params.clone(); - let stmt = self.0.clone(); - - stmt.query(¶ms).map(LibsqlRows).map(Rows::new) - } - - async fn run(&self, params: &Params) -> Result<()> { - let params = params.clone(); - let stmt = self.0.clone(); - - stmt.run(¶ms) - } - - fn interrupt(&self) -> Result<()> { - self.0.interrupt() - } - - fn reset(&self) { - self.0.reset(); - } - - fn parameter_count(&self) -> usize { - self.0.parameter_count() - } - - fn parameter_name(&self, idx: i32) -> Option<&str> { - self.0.parameter_name(idx) - } - - fn column_count(&self) -> usize { - self.0.column_count() - } - - fn columns(&self) -> Vec { - self.0.columns() - } -} - -pub(super) struct LibsqlTx(pub(super) Option); - -#[async_trait::async_trait] -impl Tx for LibsqlTx { - async fn commit(&mut self) -> Result<()> { - let tx = self.0.take().expect("Tx already dropped"); - tx.commit() - } - - async fn rollback(&mut self) -> Result<()> { - let tx = self.0.take().expect("Tx already dropped"); - tx.rollback() - } -} - -pub(crate) struct LibsqlRows(pub(crate) crate::local::Rows); - -#[async_trait::async_trait] -impl RowsInner for LibsqlRows { - async fn next(&mut self) -> Result> { - let row = self.0.next()?.map(|r| Row { - inner: Box::new(LibsqlRow(r)), - }); - - Ok(row) - } -} - -impl ColumnsInner for LibsqlRows { - fn column_count(&self) -> i32 { - self.0.column_count() - } - - fn column_name(&self, idx: i32) -> Option<&str> { - self.0.column_name(idx) - } - - fn column_type(&self, idx: i32) -> Result { - self.0.column_type(idx).map(ValueType::from) - } -} - -struct LibsqlRow(crate::local::Row); - -impl RowInner for LibsqlRow { - fn column_value(&self, idx: i32) -> Result { - self.0.get_value(idx) - } - - fn column_str(&self, idx: i32) -> Result<&str> { - self.0.get::<&str>(idx) - } -} - -impl ColumnsInner for LibsqlRow { - fn column_name(&self, idx: i32) -> Option<&str> { - self.0.column_name(idx) - } - - fn column_type(&self, idx: i32) -> Result { - self.0.column_type(idx).map(ValueType::from) - } - - fn column_count(&self) -> i32 { - self.0.stmt.column_count() as i32 - } -} - -impl fmt::Debug for LibsqlRow { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> { - self.0.fmt(f) - } -} diff --git a/vendor/libsql/src/local/mod.rs b/vendor/libsql/src/local/mod.rs deleted file mode 100644 index 3c0b03f8..00000000 --- a/vendor/libsql/src/local/mod.rs +++ /dev/null @@ -1,36 +0,0 @@ -// TODO: Remove this once we have decided what we want to keep -// from the old api. -#![allow(dead_code)] - -pub mod connection; -pub mod database; -pub mod rows; -pub mod statement; -pub mod transaction; - -pub(crate) mod impls; - -pub use libsql_sys::ffi; - -pub use crate::{Error, Result}; -pub use connection::Connection; -pub use database::Database; -pub use rows::Row; -pub use rows::Rows; -pub use rows::RowsFuture; -pub use statement::Statement; -pub use transaction::Transaction; - -/// Return the version of the underlying SQLite library as a number. -pub fn version_number() -> i32 { - unsafe { ffi::sqlite3_libversion_number() } -} - -/// Return the version of the underlying SQLite library as a string. -pub fn version() -> &'static str { - unsafe { - std::ffi::CStr::from_ptr(ffi::sqlite3_libversion()) - .to_str() - .unwrap() - } -} diff --git a/vendor/libsql/src/local/rows.rs b/vendor/libsql/src/local/rows.rs deleted file mode 100644 index 4d4e622c..00000000 --- a/vendor/libsql/src/local/rows.rs +++ /dev/null @@ -1,396 +0,0 @@ -use crate::local::{Connection, Statement}; -use crate::params::Params; -use crate::rows::{ColumnsInner, RowInner, RowsInner}; -use crate::{errors, Error, Result}; -use crate::{Value, ValueRef}; -use libsql_sys::ValueType; - -use std::cell::RefCell; -use std::collections::VecDeque; -use std::ffi::c_char; -use std::fmt; -use std::sync::Arc; -/// Query result rows. -#[derive(Debug, Clone)] -pub struct Rows { - stmt: Statement, - err: RefCell>, -} - -unsafe impl Send for Rows {} // TODO: is this safe? -unsafe impl Sync for Rows {} // TODO: is this safe? - -impl Rows { - pub fn new(stmt: Statement) -> Rows { - Rows { - stmt, - err: RefCell::new(None), - } - } - - pub fn new2(stmt: Statement, err: RefCell>) -> Rows { - Rows { stmt, err } - } - - pub fn next(&self) -> Result> { - let err; - let err_code; - let err_msg; - if let Some((e, code, msg)) = self.err.take() { - err = e; - err_code = code; - err_msg = msg; - } else { - err = self.stmt.inner.step(); - err_code = errors::extended_error_code(self.stmt.conn.raw); - err_msg = errors::error_from_handle(self.stmt.conn.raw); - } - match err { - libsql_sys::ffi::SQLITE_OK => Ok(None), - libsql_sys::ffi::SQLITE_DONE => Ok(None), - libsql_sys::ffi::SQLITE_ROW => Ok(Some(Row { - stmt: self.stmt.clone(), - })), - _ => Err(Error::SqliteFailure(err_code, err_msg)), - } - } - - pub fn column_count(&self) -> i32 { - self.stmt.inner.column_count() - } - - pub fn column_name(&self, idx: i32) -> Option<&str> { - self.stmt.inner.column_name(idx) - } - - pub fn column_type(&self, idx: i32) -> Result { - let val = self.stmt.inner.column_type(idx); - match val { - libsql_sys::ffi::SQLITE_INTEGER => Ok(ValueType::Integer), - libsql_sys::ffi::SQLITE_FLOAT => Ok(ValueType::Real), - libsql_sys::ffi::SQLITE_BLOB => Ok(ValueType::Blob), - libsql_sys::ffi::SQLITE_TEXT => Ok(ValueType::Text), - libsql_sys::ffi::SQLITE_NULL => Ok(ValueType::Null), - _ => unreachable!("unknown column type {} at index {}", val, idx), - } - } -} - -impl AsRef for Rows { - fn as_ref(&self) -> &Statement { - &self.stmt - } -} - -pub struct RowsFuture { - pub(crate) conn: Connection, - pub(crate) sql: String, - pub(crate) params: Params, -} - -impl RowsFuture { - pub fn wait(&mut self) -> Result> { - futures::executor::block_on(self) - } -} - -impl futures::Future for RowsFuture { - type Output = Result>; - - fn poll( - self: std::pin::Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - ) -> std::task::Poll { - let stmt = self.conn.prepare(&self.sql)?; - let ret = stmt.query(&self.params)?; - std::task::Poll::Ready(Ok(Some(ret))) - } -} - -pub struct Row { - pub(crate) stmt: Statement, -} - -impl AsRef for Row { - fn as_ref(&self) -> &Statement { - &self.stmt - } -} - -impl Row { - pub fn get(&self, idx: i32) -> Result - where - T: FromValue, - { - let val = self.stmt.inner.column_value(idx); - T::from_sql(val) - } - - pub fn get_value(&self, idx: i32) -> Result { - let val = self.stmt.inner.column_value(idx); - ::from_sql(val) - } - - pub fn column_type(&self, idx: i32) -> Result { - let val = self.stmt.inner.column_type(idx); - match val { - libsql_sys::ffi::SQLITE_INTEGER => Ok(ValueType::Integer), - libsql_sys::ffi::SQLITE_FLOAT => Ok(ValueType::Real), - libsql_sys::ffi::SQLITE_BLOB => Ok(ValueType::Blob), - libsql_sys::ffi::SQLITE_TEXT => Ok(ValueType::Text), - libsql_sys::ffi::SQLITE_NULL => Ok(ValueType::Null), - _ => unreachable!("unknown column type: {} at index {}", val, idx), - } - } - - pub fn column_name(&self, idx: i32) -> Option<&str> { - self.stmt.inner.column_name(idx) - } - - pub fn get_ref(&self, idx: i32) -> Result> { - Ok(crate::local::Statement::value_ref( - &self.stmt.inner, - idx as usize, - )) - } -} - -impl fmt::Debug for Row { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> { - let mut dbg_map = f.debug_map(); - for column in 0..self.stmt.column_count() { - dbg_map.key(&self.stmt.column_name(column)); - let value = self.get_ref(column as i32); - match value { - Ok(value_ref) => { - let value_type = value_ref.data_type(); - match value_ref { - ValueRef::Null => dbg_map.value(&(value_type, ())), - ValueRef::Integer(i) => dbg_map.value(&(value_type, i)), - ValueRef::Real(f) => dbg_map.value(&(value_type, f)), - ValueRef::Text(s) => { - dbg_map.value(&(value_type, String::from_utf8_lossy(s))) - } - ValueRef::Blob(b) => dbg_map.value(&(value_type, b.len())), - }; - } - Err(_) => { - dbg_map.value(&value); - } - } - } - dbg_map.finish() - } -} - -#[derive(Debug)] -pub(crate) struct BatchedRows { - /// Colname, decl_type - cols: Arc>, - rows: VecDeque>, -} - -impl BatchedRows { - pub fn new(cols: Vec<(String, crate::value::ValueType)>, rows: Vec>) -> Self { - Self { - cols: Arc::new(cols), - rows: rows.into(), - } - } -} - -#[async_trait::async_trait] -impl RowsInner for BatchedRows { - async fn next(&mut self) -> Result> { - let cols = self.cols.clone(); - let row = self.rows.pop_front(); - - if let Some(row) = row { - Ok(Some(crate::Row { - inner: Box::new(BatchedRow { cols, row }), - })) - } else { - Ok(None) - } - } -} - -impl ColumnsInner for BatchedRows { - fn column_count(&self) -> i32 { - self.cols.len() as i32 - } - - fn column_name(&self, idx: i32) -> Option<&str> { - self.cols.get(idx as usize).map(|s| s.0.as_str()) - } - - fn column_type(&self, idx: i32) -> Result { - self.cols - .get(idx as usize) - .ok_or(Error::InvalidColumnIndex) - .map(|(_, vt)| vt.clone()) - } -} - -#[derive(Debug)] -pub(crate) struct BatchedRow { - cols: Arc>, - row: Vec, -} - -impl RowInner for BatchedRow { - fn column_value(&self, idx: i32) -> Result { - self.row - .get(idx as usize) - .cloned() - .ok_or(Error::InvalidColumnIndex) - } - - fn column_str(&self, idx: i32) -> Result<&str> { - self.row - .get(idx as usize) - .ok_or(Error::InvalidColumnIndex) - .and_then(|v| { - v.as_text() - .map(String::as_str) - .ok_or(Error::InvalidColumnType) - }) - } -} - -impl ColumnsInner for BatchedRow { - fn column_name(&self, idx: i32) -> Option<&str> { - self.cols.get(idx as usize).map(|c| c.0.as_str()) - } - - fn column_count(&self) -> i32 { - self.cols.len() as i32 - } - - fn column_type(&self, idx: i32) -> Result { - self.cols - .get(idx as usize) - .ok_or(Error::InvalidColumnIndex) - .map(|(_, vt)| vt.clone()) - } -} - -pub trait FromValue { - fn from_sql(val: libsql_sys::Value) -> Result - where - Self: Sized; -} - -impl FromValue for crate::Value { - fn from_sql(val: libsql_sys::Value) -> Result { - Ok(val.into()) - } -} - -impl FromValue for i32 { - fn from_sql(val: libsql_sys::Value) -> Result { - let ret = val.int(); - Ok(ret) - } -} - -impl FromValue for u32 { - fn from_sql(val: libsql_sys::Value) -> Result { - let ret = val.int() as u32; - Ok(ret) - } -} - -impl FromValue for i64 { - fn from_sql(val: libsql_sys::Value) -> Result { - let ret = val.int64(); - Ok(ret) - } -} - -impl FromValue for u64 { - fn from_sql(val: libsql_sys::Value) -> Result { - let ret = val.int64() as u64; - Ok(ret) - } -} - -impl FromValue for f64 { - fn from_sql(val: libsql_sys::Value) -> Result { - let ret = val.double(); - Ok(ret) - } -} - -impl FromValue for Vec { - fn from_sql(val: libsql_sys::Value) -> Result { - let ret = val.blob(); - if ret.is_null() { - return Err(Error::NullValue); - } - let ret = unsafe { std::slice::from_raw_parts(ret as *const u8, val.bytes() as usize) }; - Ok(ret.to_vec()) - } -} - -impl FromValue for String { - fn from_sql(val: libsql_sys::Value) -> Result { - let ret = val.text(); - if ret.is_null() { - return Err(Error::NullValue); - } - let ret = unsafe { std::ffi::CStr::from_ptr(ret as *const c_char) }; - let ret = ret.to_str().unwrap(); - Ok(ret.to_string()) - } -} - -impl FromValue for &[u8] { - fn from_sql(val: libsql_sys::Value) -> Result { - let ret = val.blob(); - if ret.is_null() { - return Err(Error::NullValue); - } - let ret = unsafe { std::slice::from_raw_parts(ret as *const u8, val.bytes() as usize) }; - Ok(ret) - } -} - -impl FromValue for &str { - fn from_sql(val: libsql_sys::Value) -> Result { - let ret = val.text(); - if ret.is_null() { - return Err(Error::NullValue); - } - let ret = unsafe { std::ffi::CStr::from_ptr(ret as *const c_char) }; - let ret = ret.to_str().unwrap(); - Ok(ret) - } -} - -pub struct MappedRows { - rows: Rows, - map: F, -} - -impl MappedRows { - pub fn new(rows: Rows, map: F) -> Self { - Self { rows, map } - } -} - -impl Iterator for MappedRows -where - F: FnMut(Row) -> Result, -{ - type Item = Result; - - #[inline] - fn next(&mut self) -> Option { - let map = &mut self.map; - self.rows - .next() - .transpose() - .map(|row_result| row_result.and_then(map)) - } -} diff --git a/vendor/libsql/src/local/statement.rs b/vendor/libsql/src/local/statement.rs deleted file mode 100644 index c31e7517..00000000 --- a/vendor/libsql/src/local/statement.rs +++ /dev/null @@ -1,361 +0,0 @@ -use crate::local::rows::{MappedRows, Row}; -use crate::local::{Connection, Rows}; -use crate::params::Params; -use crate::{errors, Column, Error, Result, ValueRef}; - -use std::cell::RefCell; -use std::ffi::c_int; -use std::sync::Arc; - -/// A prepared statement. -#[derive(Debug, Clone)] -pub struct Statement { - pub(crate) conn: Connection, - pub(crate) inner: Arc, - sql: String, -} - -impl Statement { - pub(crate) fn finalize(&self) { - self.inner.finalize(); - } - - pub(crate) fn prepare( - conn: Connection, - raw: *mut libsql_sys::ffi::sqlite3, - sql: &str, - ) -> Result { - match unsafe { libsql_sys::prepare_stmt(raw, sql) } { - Ok(stmt) => Ok(Statement { - conn, - inner: Arc::new(stmt), - sql: sql.to_string(), - }), - Err(libsql_sys::Error::LibError(_err)) => Err(Error::SqliteFailure( - errors::extended_error_code(raw), - errors::error_from_handle(raw), - )), - Err(err) => Err(Error::Misuse(format!( - "Unexpected error while preparing statement: {err}" - ))), - } - } - - pub fn query_map(&self, params: &Params, f: F) -> Result> - where - F: FnMut(Row) -> Result, - { - let rows = self.query(params)?; - - Ok(MappedRows::new(rows, f)) - } - - pub fn run(&self, params: &Params) -> Result<()> { - self.bind(params); - let err = self.inner.step(); - match err { - crate::ffi::SQLITE_DONE => Ok(()), - crate::ffi::SQLITE_ROW => Ok(()), - _ => Err(Error::SqliteFailure( - errors::extended_error_code(self.conn.raw), - errors::error_from_handle(self.conn.raw), - )), - } - } - - pub fn query(&self, params: &Params) -> Result { - self.bind(params); - let err = self.inner.step(); - Ok(Rows::new2( - self.clone(), - RefCell::new(Some(( - err, - errors::extended_error_code(self.conn.raw), - errors::error_from_handle(self.conn.raw), - ))), - )) - } - - pub fn query_row(&self, params: &Params) -> Result { - let rows = self.query(params)?; - - let row = rows.next()?.ok_or(Error::QueryReturnedNoRows)?; - - Ok(row) - } - - pub fn bind(&self, params: &Params) { - match params { - Params::None => {} - Params::Positional(params) => { - for (i, param) in params.iter().enumerate() { - let i = i as i32 + 1; - - self.bind_value(i, param.into()); - } - } - - Params::Named(params) => { - for (name, param) in params { - let i = self.inner.bind_parameter_index(name); - - self.bind_value(i, param.into()); - } - } - } - } - - pub fn parameter_count(&self) -> usize { - self.inner.bind_parameter_count() - } - - pub fn parameter_name(&self, index: i32) -> Option<&str> { - self.inner.bind_parameter_name(index) - } - - pub fn is_explain(&self) -> i32 { - self.inner.is_explain() - } - - pub fn readonly(&self) -> bool { - self.inner.readonly() - } - - pub fn execute(&self, params: &Params) -> Result { - self.bind(params); - let err = self.inner.step(); - match err { - crate::ffi::SQLITE_DONE => Ok(self.conn.changes()), - crate::ffi::SQLITE_ROW => Err(Error::ExecuteReturnedRows), - _ => Err(Error::SqliteFailure( - errors::extended_error_code(self.conn.raw), - errors::error_from_handle(self.conn.raw), - )), - } - } - - /// Interrupt the statement. - pub fn interrupt(&self) -> Result<()> { - self.inner.interrupt(); - Ok(()) - } - - /// Reset the prepared statement to initial state for reuse. - pub fn reset(&self) { - self.inner.reset(); - } - - pub fn bind_value(&self, i: i32, param: ValueRef<'_>) { - match param { - ValueRef::Null => { - self.inner.bind_null(i); - } - ValueRef::Integer(value) => { - self.inner.bind_int64(i, value); - } - ValueRef::Real(value) => { - self.inner.bind_double(i, value); - } - ValueRef::Text(value) => { - self.inner.bind_text(i, value); - } - ValueRef::Blob(value) => { - self.inner.bind_blob(i, value); - } - } - } - - pub fn get_status(&self, status: i32) -> i32 { - self.inner.get_status(status) - } - - pub fn value_ref(inner: &libsql_sys::Statement, col: usize) -> ValueRef<'_> { - let raw = inner.raw_stmt; - - match inner.column_type(col as i32) { - crate::ffi::SQLITE_NULL => ValueRef::Null, - crate::ffi::SQLITE_INTEGER => { - ValueRef::Integer(unsafe { crate::ffi::sqlite3_column_int64(raw, col as c_int) }) - } - crate::ffi::SQLITE_FLOAT => { - ValueRef::Real(unsafe { crate::ffi::sqlite3_column_double(raw, col as c_int) }) - } - crate::ffi::SQLITE_TEXT => { - let s = unsafe { - // Quoting from "Using SQLite" book: - // To avoid problems, an application should first extract the desired type using - // a sqlite3_column_xxx() function, and then call the - // appropriate sqlite3_column_bytes() function. - let text = crate::ffi::sqlite3_column_text(raw, col as c_int); - let len = crate::ffi::sqlite3_column_bytes(raw, col as c_int); - assert!( - !text.is_null(), - "unexpected SQLITE_TEXT column type with NULL data" - ); - std::slice::from_raw_parts(text.cast::(), len as usize) - }; - - ValueRef::Text(s) - } - crate::ffi::SQLITE_BLOB => { - let (blob, len) = unsafe { - ( - crate::ffi::sqlite3_column_blob(raw, col as c_int), - crate::ffi::sqlite3_column_bytes(raw, col as c_int), - ) - }; - - assert!( - len >= 0, - "unexpected negative return from sqlite3_column_bytes" - ); - if len > 0 { - assert!( - !blob.is_null(), - "unexpected SQLITE_BLOB column type with NULL data" - ); - ValueRef::Blob(unsafe { - std::slice::from_raw_parts(blob.cast::(), len as usize) - }) - } else { - // The return value from sqlite3_column_blob() for a zero-length BLOB - // is a NULL pointer. - ValueRef::Blob(&[]) - } - } - _ => unreachable!("sqlite3_column_type returned invalid value"), - } - } - - /// Returns true if this statement has rows ready to be read. - pub(crate) fn step(&self) -> Result { - let err = self.inner.step(); - match err { - crate::ffi::SQLITE_DONE => Ok(false), - crate::ffi::SQLITE_ROW => Ok(true), - _ => Err(Error::SqliteFailure( - errors::extended_error_code(self.conn.raw), - errors::error_from_handle(self.conn.raw), - )), - } - } - - pub(crate) fn tail(&self) -> usize { - self.inner.tail() - } - - pub(crate) fn is_readonly(&self) -> bool { - self.inner.readonly() - } -} - -impl Statement { - /// Get all the column names in the result set of the prepared statement. - /// - /// If associated DB schema can be altered concurrently, you should make - /// sure that current statement has already been stepped once before - /// calling this method. - pub fn column_names(&self) -> Vec<&str> { - let n = self.column_count(); - let mut cols = Vec::with_capacity(n); - for i in 0..n { - let s = self.column_name(i); - if let Some(s) = s { - cols.push(s); - } - } - cols - } - - /// Return the number of columns in the result set returned by the prepared - /// statement. - /// - /// If associated DB schema can be altered concurrently, you should make - /// sure that current statement has already been stepped once before - /// calling this method. - pub fn column_count(&self) -> usize { - self.inner.column_count() as usize - } - - /// Returns the name assigned to a particular column in the result set - /// returned by the prepared statement. - /// - /// If associated DB schema can be altered concurrently, you should make - /// sure that current statement has already been stepped once before - /// calling this method. - /// - /// Returns `None` if there is no column at the provided index. - pub fn column_name(&self, col: usize) -> Option<&str> { - self.inner.column_name(col as i32) - } - - pub fn column_origin_name(&self, col: usize) -> Option<&str> { - self.inner.column_origin_name(col as i32) - } - - pub fn column_table_name(&self, col: usize) -> Option<&str> { - self.inner.column_table_name(col as i32) - } - - pub fn column_database_name(&self, col: usize) -> Option<&str> { - self.inner.column_database_name(col as i32) - } - - pub fn column_decltype(&self, col: usize) -> Option<&str> { - self.inner.column_decltype(col as i32) - } - - /// Returns the column index in the result set for a given column name. - /// - /// If there is no AS clause then the name of the column is unspecified and - /// may change from one release of SQLite to the next. - /// - /// If associated DB schema can be altered concurrently, you should make - /// sure that current statement has already been stepped once before - /// calling this method. - /// - /// # Failure - /// - /// Will return an `Error::InvalidColumnName` when there is no column with - /// the specified `name`. - pub fn column_index(&self, name: &str) -> Result { - let bytes = name.as_bytes(); - let n = self.column_count(); - for i in 0..n { - // Note: `column_name` is only fallible if `i` is out of bounds, - // which we've already checked. - let col_name = self - .column_name(i) - .ok_or_else(|| Error::InvalidColumnName(name.to_string()))?; - if bytes.eq_ignore_ascii_case(col_name.as_bytes()) { - return Ok(i as usize); - } - } - Err(Error::InvalidColumnName(name.to_string())) - } - - /// Returns a slice describing the columns of the result of the query. - /// - /// If associated DB schema can be altered concurrently, you should make - /// sure that current statement has already been stepped once before - /// calling this method. - pub fn columns(&self) -> Vec { - let n = self.column_count(); - let mut cols = Vec::with_capacity(n); - for i in 0..n { - let name = self.column_name(i).expect("Column idx should be valid"); - let origin_name = self.column_origin_name(i); - let table_name = self.column_table_name(i); - let database_name = self.column_database_name(i); - let decl_type = self.column_decltype(i); - cols.push(Column { - name, - origin_name, - table_name, - database_name, - decl_type, - }); - } - cols - } -} diff --git a/vendor/libsql/src/local/transaction.rs b/vendor/libsql/src/local/transaction.rs deleted file mode 100644 index a813c482..00000000 --- a/vendor/libsql/src/local/transaction.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::local::Connection; -use crate::TransactionBehavior; -use crate::{params::Params, Result}; -use std::ops::Deref; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum DropBehavior { - Rollback, - Commit, - Ignore, - Panic, -} - -pub struct Transaction { - conn: Connection, - drop_behavior: DropBehavior, -} - -impl Drop for Transaction { - fn drop(&mut self) { - if self.conn.is_autocommit() { - return; - } - match self.drop_behavior { - DropBehavior::Rollback => { - self.do_rollback().unwrap(); - } - DropBehavior::Commit => { - self.do_commit().unwrap(); - } - DropBehavior::Ignore => {} - DropBehavior::Panic => { - if !std::thread::panicking() { - panic!("Transaction dropped without being committed or rolled back"); - } - } - } - } -} - -impl Transaction { - pub fn drop_behavior(&self) -> DropBehavior { - self.drop_behavior - } - - pub fn set_drop_behavior(&mut self, drop_behavior: DropBehavior) { - self.drop_behavior = drop_behavior - } - - /// Begin a new transaction in the given mode. - pub(crate) fn begin(conn: Connection, tx_behavior: TransactionBehavior) -> Result { - let begin_stmt = match tx_behavior { - TransactionBehavior::Deferred => "BEGIN DEFERRED", - TransactionBehavior::Immediate => "BEGIN IMMEDIATE", - TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE", - TransactionBehavior::ReadOnly => "BEGIN READONLY", - }; - let _ = conn.execute(begin_stmt, Params::None)?; - Ok(Self { - conn, - drop_behavior: DropBehavior::Rollback, - }) - } - - /// Commit the transaction. - pub fn commit(self) -> Result<()> { - self.do_commit() - } - - fn do_commit(&self) -> Result<()> { - let _ = self.conn.execute("COMMIT", Params::None)?; - Ok(()) - } - - /// Rollback the transaction. - pub fn rollback(self) -> Result<()> { - self.do_rollback() - } - - fn do_rollback(&self) -> Result<()> { - let _ = self.conn.execute("ROLLBACK", Params::None)?; - Ok(()) - } -} - -impl Deref for Transaction { - type Target = Connection; - - #[inline] - fn deref(&self) -> &Connection { - &self.conn - } -} diff --git a/vendor/libsql/src/macros.rs b/vendor/libsql/src/macros.rs deleted file mode 100644 index b7d0df31..00000000 --- a/vendor/libsql/src/macros.rs +++ /dev/null @@ -1,111 +0,0 @@ -#![allow(unused_macros)] - -macro_rules! cfg_core { - ($($item:item)*) => { - $( - #[cfg(feature = "core")] - #[cfg_attr(docsrs, doc(cfg(feature = "core")))] - $item - )* - } -} - -macro_rules! cfg_replication_or_remote_or_sync { - ($($item:item)*) => { - $( - #[cfg(any(feature = "replication", feature = "sync", feature = "remote"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "replication", feature = "sync", feature = "remote"))))] - $item - )* - } -} - -macro_rules! cfg_replication_or_remote_or_hrana { - ($($item:item)*) => { - $( - #[cfg(any(feature = "replication", feature = "remote", feature = "hrana"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "replication", feature = "remote", feature = "hrana"))))] - $item - )* - } -} - -macro_rules! cfg_replication { - ($($item:item)*) => { - $( - #[cfg(feature = "replication")] - #[cfg_attr(docsrs, doc(cfg(feature = "replication")))] - $item - )* - } -} - -macro_rules! cfg_sync { - ($($item:item)*) => { - $( - #[cfg(feature = "sync")] - #[cfg_attr(docsrs, doc(cfg(feature = "sync")))] - $item - )* - } -} - -macro_rules! cfg_replication_or_sync { - ($($item:item)*) => { - $( - #[cfg(any(feature = "replication", feature = "sync"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "replication", feature = "sync"))))] - $item - )* - } -} - -macro_rules! cfg_parser { - ($($item:item)*) => { - $( - #[cfg(feature = "parser")] - #[cfg_attr(docsrs, doc(cfg(feature = "parser")))] - $item - )* - } -} - -macro_rules! cfg_hrana { - ($($item:item)*) => { - $( - #[cfg(feature = "hrana")] - #[cfg_attr(docsrs, doc(cfg(feature = "hrana")))] - $item - )* - } -} - -macro_rules! cfg_remote { - ($($item:item)*) => { - $( - #[cfg(feature = "remote")] - #[cfg_attr(docsrs, doc(cfg(feature = "remote")))] - $item - )* - } -} - -macro_rules! cfg_cloudflare { - ($($item:item)*) => { - $( - #[cfg(feature = "cloudflare")] - #[cfg_attr(docsrs, doc(cfg(feature = "cloudflare")))] - $item - )* - } -} - -macro_rules! cfg_wasm { - ($($item:item)*) => { - $( - #[cfg(feature = "wasm")] - #[cfg_attr(docsrs, doc(cfg(feature = "wasm")))] - $item - )* - } -} diff --git a/vendor/libsql/src/params.rs b/vendor/libsql/src/params.rs deleted file mode 100644 index 6921e014..00000000 --- a/vendor/libsql/src/params.rs +++ /dev/null @@ -1,351 +0,0 @@ -//! This module contains all `Param` related utilities and traits. - -use crate::{Error, Result, Value}; - -mod sealed { - pub trait Sealed {} -} - -use sealed::Sealed; - -/// Converts some type into parameters that can be passed -/// to libsql. -/// -/// The trait is sealed and not designed to be implemented by hand -/// but instead provides a few ways to use it. -/// -/// # Passing parameters to libsql -/// -/// Many functions in this library let you pass parameters to libsql. Doing this -/// lets you avoid any risk of SQL injection, and is simpler than escaping -/// things manually. These functions generally contain some paramter that generically -/// accepts some implementation this trait. -/// -/// # Positional parameters -/// -/// These can be supplied in a few ways: -/// -/// - For heterogeneous parameter lists of 16 or less items a tuple syntax is supported -/// by doing `(1, "foo")`. -/// - For hetergeneous parameter lists of 16 or greater, the [`libsql::params!`] is supported -/// by doing `libsql::params![1, "foo"]`. -/// - For homogeneous paramter types (where they are all the same type), const arrays are -/// supported by doing `[1, 2, 3]`. -/// -/// # Example (positional) -/// -/// ```rust,no_run -/// # use libsql::{Connection, params}; -/// # async fn run(conn: Connection) -> libsql::Result<()> { -/// let mut stmt = conn.prepare("INSERT INTO test (a, b) VALUES (?1, ?2)").await?; -/// -/// // Using a tuple: -/// stmt.execute((0, "foobar")).await?; -/// -/// // Using `libsql::params!`: -/// stmt.execute(params![1i32, "blah"]).await?; -/// -/// // array literal — non-references -/// stmt.execute([2i32, 3i32]).await?; -/// -/// // array literal — references -/// stmt.execute(["foo", "bar"]).await?; -/// -/// // Slice literal, references: -/// stmt.execute([2i32, 3i32]).await?; -/// -/// # Ok(()) -/// # } -/// ``` -/// -/// # Named paramters -/// -/// - For heterogeneous parameter lists of 16 or less items a tuple syntax is supported -/// by doing `(("key1", 1), ("key2", "foo"))`. -/// - For hetergeneous parameter lists of 16 or greater, the [`libsql::params!`] is supported -/// by doing `libsql::named_params!["key1": 1, "key2": "foo"]`. -/// - For homogeneous paramter types (where they are all the same type), const arrays are -/// supported by doing `[("key1", 1), ("key2, 2), ("key3", 3)]`. -/// -/// # Example (named) -/// -/// ```rust,no_run -/// # use libsql::{Connection, named_params}; -/// # async fn run(conn: Connection) -> libsql::Result<()> { -/// let mut stmt = conn.prepare("INSERT INTO test (a, b) VALUES (:key1, :key2)").await?; -/// -/// // Using a tuple: -/// stmt.execute(((":key1", 0), (":key2", "foobar"))).await?; -/// -/// // Using `libsql::named_params!`: -/// stmt.execute(named_params! {":key1": 1i32, ":key2": "blah" }).await?; -/// -/// // const array: -/// stmt.execute([(":key1", 2i32), (":key2", 3i32)]).await?; -/// -/// # Ok(()) -/// # } -/// ``` -pub trait IntoParams: Sealed { - // Hide this because users should not be implementing this - // themselves. We should consider sealing this trait. - #[doc(hidden)] - fn into_params(self) -> Result; -} - -#[derive(Debug, Clone)] -#[doc(hidden)] -pub enum Params { - None, - Positional(Vec), - Named(Vec<(String, Value)>), -} - -/// Convert an owned iterator into Params. -/// -/// # Example -/// -/// ```rust -/// # use libsql::{Connection, params_from_iter, Rows}; -/// # async fn run(conn: &Connection) { -/// -/// let iter = vec![1, 2, 3]; -/// -/// conn.query( -/// "SELECT * FROM users WHERE id IN (?1, ?2, ?3)", -/// params_from_iter(iter) -/// ) -/// .await -/// .unwrap(); -/// # } -/// ``` -pub fn params_from_iter(iter: I) -> impl IntoParams -where - I: IntoIterator, - I::Item: IntoValue, -{ - iter.into_iter().collect::>() -} - -impl Sealed for () {} -impl IntoParams for () { - fn into_params(self) -> Result { - Ok(Params::None) - } -} - -impl Sealed for Params {} -impl IntoParams for Params { - fn into_params(self) -> Result { - Ok(self) - } -} - -impl Sealed for &Params {} -impl IntoParams for &Params { - fn into_params(self) -> Result { - Ok(self.clone()) - } -} - -impl Sealed for Vec {} -impl IntoParams for Vec { - fn into_params(self) -> Result { - let values = self - .into_iter() - .map(|i| i.into_value()) - .collect::>>()?; - - Ok(Params::Positional(values)) - } -} - -impl Sealed for Vec<(String, T)> {} -impl IntoParams for Vec<(String, T)> { - fn into_params(self) -> Result { - let values = self - .into_iter() - .map(|(k, v)| Ok((k, v.into_value()?))) - .collect::>>()?; - - Ok(Params::Named(values)) - } -} - -impl Sealed for [T; N] {} -impl IntoParams for [T; N] { - fn into_params(self) -> Result { - self.into_iter().collect::>().into_params() - } -} - -impl Sealed for [(&str, T); N] {} -impl IntoParams for [(&str, T); N] { - fn into_params(self) -> Result { - self.into_iter() - // TODO: Pretty unfortunate that we need to allocate here when we know - // the str is likely 'static. Maybe we should convert our param names - // to be `Cow<'static, str>`? - .map(|(k, v)| Ok((k.to_string(), v.into_value()?))) - .collect::>>()? - .into_params() - } -} - -impl Sealed for &[T; N] {} -impl IntoParams for &[T; N] { - fn into_params(self) -> Result { - self.iter().cloned().collect::>().into_params() - } -} - -// NOTICE: heavily inspired by rusqlite -macro_rules! tuple_into_params { - ($count:literal : $(($field:tt $ftype:ident)),* $(,)?) => { - impl<$($ftype,)*> Sealed for ($($ftype,)*) where $($ftype: IntoValue,)* {} - impl<$($ftype,)*> IntoParams for ($($ftype,)*) where $($ftype: IntoValue,)* { - fn into_params(self) -> Result { - let params = Params::Positional(vec![$(self.$field.into_value()?),*]); - Ok(params) - } - } - } -} - -macro_rules! named_tuple_into_params { - ($count:literal : $(($field:tt $ftype:ident)),* $(,)?) => { - impl<$($ftype,)*> Sealed for ($((&str, $ftype),)*) where $($ftype: IntoValue,)* {} - impl<$($ftype,)*> IntoParams for ($((&str, $ftype),)*) where $($ftype: IntoValue,)* { - fn into_params(self) -> Result { - let params = Params::Named(vec![$((self.$field.0.to_string(), self.$field.1.into_value()?)),*]); - Ok(params) - } - } - } -} - -named_tuple_into_params!(2: (0 A), (1 B)); -named_tuple_into_params!(3: (0 A), (1 B), (2 C)); -named_tuple_into_params!(4: (0 A), (1 B), (2 C), (3 D)); -named_tuple_into_params!(5: (0 A), (1 B), (2 C), (3 D), (4 E)); -named_tuple_into_params!(6: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F)); -named_tuple_into_params!(7: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G)); -named_tuple_into_params!(8: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H)); -named_tuple_into_params!(9: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I)); -named_tuple_into_params!(10: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J)); -named_tuple_into_params!(11: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K)); -named_tuple_into_params!(12: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L)); -named_tuple_into_params!(13: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M)); -named_tuple_into_params!(14: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N)); -named_tuple_into_params!(15: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O)); -named_tuple_into_params!(16: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O), (15 P)); - -tuple_into_params!(2: (0 A), (1 B)); -tuple_into_params!(3: (0 A), (1 B), (2 C)); -tuple_into_params!(4: (0 A), (1 B), (2 C), (3 D)); -tuple_into_params!(5: (0 A), (1 B), (2 C), (3 D), (4 E)); -tuple_into_params!(6: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F)); -tuple_into_params!(7: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G)); -tuple_into_params!(8: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H)); -tuple_into_params!(9: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I)); -tuple_into_params!(10: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J)); -tuple_into_params!(11: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K)); -tuple_into_params!(12: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L)); -tuple_into_params!(13: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M)); -tuple_into_params!(14: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N)); -tuple_into_params!(15: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O)); -tuple_into_params!(16: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O), (15 P)); - -// TODO: Should we rename this to `ToSql` which makes less sense but -// matches the error variant we have in `Error`. Or should we change the -// error variant to match this breaking the few people that currently use -// this error variant. -pub trait IntoValue { - fn into_value(self) -> Result; -} - -impl IntoValue for T -where - T: TryInto, - T::Error: Into, -{ - fn into_value(self) -> Result { - self.try_into() - .map_err(|e| Error::ToSqlConversionFailure(e.into())) - } -} - -impl IntoValue for Result { - fn into_value(self) -> Result { - self - } -} - -#[cfg(feature = "replication")] -impl From for libsql_replication::rpc::proxy::query::Params { - fn from(params: Params) -> Self { - use libsql_replication::rpc::proxy; - - match params { - Params::None => proxy::query::Params::Positional(proxy::Positional::default()), - Params::Positional(values) => { - let values = values - .iter() - .map(|v| bincode::serialize(v).unwrap()) - .map(|data| proxy::Value { data }) - .collect::>(); - proxy::query::Params::Positional(proxy::Positional { values }) - } - Params::Named(values) => { - let (names, values) = values - .into_iter() - .map(|(name, value)| { - let data = bincode::serialize(&value).unwrap(); - let value = proxy::Value { data }; - (name, value) - }) - .unzip(); - - proxy::query::Params::Named(proxy::Named { names, values }) - } - } - } -} - -/// Construct positional params from a hetergeneous set of params types. -#[macro_export] -macro_rules! params { - () => { - () - }; - ($($value:expr),* $(,)?) => {{ - use $crate::params::IntoValue; - [$($value.into_value()),*] - - }}; -} - -/// Construct named params from a hetergeneous set of params types. -#[macro_export] -macro_rules! named_params { - () => { - () - }; - ($($param_name:literal: $value:expr),* $(,)?) => {{ - use $crate::params::IntoValue; - [$(($param_name, $value.into_value())),*] - }}; -} - -#[cfg(test)] -mod tests { - use crate::Value; - - #[test] - fn test_serialize_array() { - assert_eq!( - params!([0; 16])[0].as_ref().unwrap(), - &Value::Blob(vec![0; 16]) - ); - } -} diff --git a/vendor/libsql/src/parser.rs b/vendor/libsql/src/parser.rs deleted file mode 100644 index afce19af..00000000 --- a/vendor/libsql/src/parser.rs +++ /dev/null @@ -1,301 +0,0 @@ -#![allow(dead_code)] - -use crate::{Error, Result}; -use fallible_iterator::FallibleIterator; -use sqlite3_parser::ast::{Cmd, PragmaBody, QualifiedName, Stmt, TransactionType}; -use sqlite3_parser::lexer::sql::{Parser, ParserError}; - -/// A group of statements to be executed together. -#[derive(Debug, Clone)] -pub struct Statement { - pub stmt: String, - pub kind: StmtKind, -} - -impl Default for Statement { - fn default() -> Self { - Self::empty() - } -} - -/// Classify statement in categories of interest. -#[derive(Debug, PartialEq, Clone, Copy)] -pub enum StmtKind { - /// The begining of a transaction - TxnBegin, - TxnBeginReadOnly, - /// The end of a transaction - TxnEnd, - Read, - Write, - Savepoint, - Release, - Attach, - Detach, - Other, -} - -fn is_temp(name: &QualifiedName) -> bool { - name.db_name.as_ref().map(|n| n.0.as_str()) == Some("TEMP") -} - -fn is_reserved_tbl(name: &QualifiedName) -> bool { - let n = name.name.0.to_lowercase(); - n == "_litestream_seq" || n == "_litestream_lock" || n == "libsql_wasm_func_table" -} - -fn write_if_not_reserved(name: &QualifiedName) -> Option { - (!is_reserved_tbl(name)).then_some(StmtKind::Write) -} - -impl StmtKind { - fn kind(cmd: &Cmd) -> Option { - match cmd { - Cmd::Explain(Stmt::Pragma(name, body)) => Self::pragma_kind(name, body.as_ref()), - Cmd::Explain(_) => Some(Self::Other), - Cmd::ExplainQueryPlan(_) => Some(Self::Other), - Cmd::Stmt(Stmt::Begin(Some(TransactionType::ReadOnly), _)) => { - Some(Self::TxnBeginReadOnly) - } - Cmd::Stmt(Stmt::Begin { .. }) => Some(Self::TxnBegin), - Cmd::Stmt( - Stmt::Commit { .. } - | Stmt::Rollback { - savepoint_name: None, - .. - }, - ) => Some(Self::TxnEnd), - Cmd::Stmt( - Stmt::CreateVirtualTable { tbl_name, .. } - | Stmt::CreateTable { - tbl_name, - temporary: false, - .. - }, - ) if !is_temp(tbl_name) => Some(Self::Write), - Cmd::Stmt( - Stmt::Insert { - with: _, - or_conflict: _, - tbl_name, - .. - } - | Stmt::Update { - with: _, - or_conflict: _, - tbl_name, - .. - }, - ) => write_if_not_reserved(tbl_name), - - Cmd::Stmt(Stmt::Delete { - with: _, tbl_name, .. - }) => write_if_not_reserved(tbl_name), - Cmd::Stmt(Stmt::DropTable { - if_exists: _, - tbl_name, - }) => write_if_not_reserved(tbl_name), - Cmd::Stmt(Stmt::AlterTable(tbl_name, _)) => write_if_not_reserved(tbl_name), - Cmd::Stmt( - Stmt::DropIndex { .. } - | Stmt::DropTrigger { .. } - | Stmt::CreateTrigger { - temporary: false, .. - } - | Stmt::CreateIndex { .. }, - ) => Some(Self::Write), - Cmd::Stmt(Stmt::Select { .. }) => Some(Self::Read), - Cmd::Stmt(Stmt::Pragma(name, body)) => Self::pragma_kind(name, body.as_ref()), - // Creating regular views is OK, temporary views are bound to a connection - // and thus disallowed in sqld. - Cmd::Stmt(Stmt::CreateView { - temporary: false, .. - }) => Some(Self::Write), - Cmd::Stmt(Stmt::DropView { .. }) => Some(Self::Write), - Cmd::Stmt(Stmt::Savepoint(_)) => Some(Self::Savepoint), - Cmd::Stmt(Stmt::Release(_)) - | Cmd::Stmt(Stmt::Rollback { - savepoint_name: Some(_), - .. - }) => Some(Self::Release), - Cmd::Stmt(Stmt::Attach { .. }) => Some(Self::Attach), - Cmd::Stmt(Stmt::Detach(_)) => Some(Self::Detach), - Cmd::Stmt(Stmt::Reindex { .. }) => Some(Self::Write), - _ => None, - } - } - - fn pragma_kind(name: &QualifiedName, body: Option<&PragmaBody>) -> Option { - let name = name.name.0.as_str(); - match name { - // always ok to be served by primary or replicas - pure readonly pragmas - "table_list" | "index_list" | "table_info" | "table_xinfo" | "index_info" | "index_xinfo" - | "pragma_list" | "compile_options" | "database_list" | "function_list" - | "module_list" => Some(Self::Read), - // special case for `encoding` - it's effectively readonly for connections - // that already created a database, which is always the case for sqld - "encoding" => Some(Self::Read), - // always ok to be served by primary - "defer_foreign_keys" | "foreign_keys" | "foreign_key_list" | "foreign_key_check" | "collation_list" - | "data_version" | "freelist_count" | "integrity_check" | "legacy_file_format" - | "page_count" | "quick_check" | "stats" | "user_version" => Some(Self::Write), - // ok to be served by primary without args - "analysis_limit" - | "application_id" - | "auto_vacuum" - | "automatic_index" - | "busy_timeout" - | "cache_size" - | "cache_spill" - | "cell_size_check" - | "checkpoint_fullfsync" - | "fullfsync" - | "hard_heap_limit" - | "journal_mode" - | "journal_size_limit" - | "legacy_alter_table" - | "locking_mode" - | "max_page_count" - | "mmap_size" - | "page_size" - | "query_only" - | "read_uncommitted" - | "recursive_triggers" - | "reverse_unordered_selects" - | "schema_version" - | "secure_delete" - | "soft_heap_limit" - | "synchronous" - | "temp_store" - | "threads" - | "trusted_schema" - | "wal_autocheckpoint" => { - match body { - Some(_) => None, - None => Some(Self::Write), - } - } - // changes the state of the connection, and can't be allowed rn: - "case_sensitive_like" | "ignore_check_constraints" | "incremental_vacuum" - // TODO: check if optimize can be safely performed - | "optimize" - | "parser_trace" - | "shrink_memory" - | "wal_checkpoint" => None, - _ => { - tracing::debug!("Unknown pragma: {name}"); - None - }, - } - } -} - -impl Statement { - pub fn empty() -> Self { - Self { - stmt: String::new(), - // empty statement is arbitrarely made of the read kind so it is not send to a writer - kind: StmtKind::Read, - } - } - - pub fn parse(s: &str) -> impl Iterator> + '_ { - fn parse_inner( - original: &str, - stmt_count: u64, - has_more_stmts: bool, - c: Cmd, - ) -> Result { - let kind = StmtKind::kind(&c).ok_or_else(|| Error::Sqlite3UnsupportedStatement)?; - - if stmt_count == 1 && !has_more_stmts { - // XXX: Temporary workaround for integration with Atlas - if let Cmd::Stmt(Stmt::CreateTable { .. }) = &c { - return Ok(Statement { - stmt: original.to_string(), - kind, - }); - } - } - - Ok(Statement { - stmt: c.to_string(), - kind, - }) - } - // The parser needs to be boxed because it's large, and you don't want it on the stack. - // There's upstream work to make it smaller, but in the meantime the parser should remain - // on the heap: - // - https://github.com/gwenn/lemon-rs/issues/8 - // - https://github.com/gwenn/lemon-rs/pull/19 - let mut parser = Box::new(Parser::new(s.as_bytes()).peekable()); - let mut stmt_count = 0; - std::iter::from_fn(move || { - stmt_count += 1; - match parser.next() { - Ok(Some(cmd)) => Some(parse_inner( - s, - stmt_count, - parser.peek().map_or(true, |o| o.is_some()), - cmd, - )), - Ok(None) => None, - Err(sqlite3_parser::lexer::sql::Error::ParserError( - ParserError::SyntaxError { - token_type: _, - found: Some(found), - }, - Some((line, col)), - )) => Some(Err(crate::Error::Sqlite3SyntaxError(line, col, found))), - Err(e) => Some(Err(Error::Sqlite3ParserError(e.into()))), - } - }) - } - - pub fn is_read_only(&self) -> bool { - matches!( - self.kind, - StmtKind::Read | StmtKind::TxnBeginReadOnly | StmtKind::TxnEnd - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn test_attach_same_db() { - let input = "ATTACH test AS test;"; - let mut result = Statement::parse(input); - - let stmt = result.next().unwrap().unwrap(); - assert_eq!(stmt.kind, StmtKind::Attach); - } - - #[test] - fn test_attach_database() { - let input = "ATTACH DATABASE test AS test;"; - let mut result = Statement::parse(input); - - let stmt = result.next().unwrap().unwrap(); - assert_eq!(stmt.kind, StmtKind::Attach); - } - - #[test] - fn test_attach_diff_db() { - let input = "ATTACH \"random\" AS test;"; - let mut result = Statement::parse(input); - - let stmt = result.next().unwrap().unwrap(); - assert_eq!(stmt.kind, StmtKind::Attach); - } - - #[test] - fn test_attach_database_diff_db() { - let input = "ATTACH DATABASE \"random\" AS test;"; - let mut result = Statement::parse(input); - - let stmt = result.next().unwrap().unwrap(); - assert_eq!(stmt.kind, StmtKind::Attach); - } -} diff --git a/vendor/libsql/src/replication/client.rs b/vendor/libsql/src/replication/client.rs deleted file mode 100644 index 90a7ab45..00000000 --- a/vendor/libsql/src/replication/client.rs +++ /dev/null @@ -1,217 +0,0 @@ -use std::pin::Pin; -use std::task::{Context, Poll}; - -use anyhow::Context as _; -use http::Uri; -use libsql_replication::rpc::proxy::{ - proxy_client::ProxyClient, DescribeRequest, DescribeResult, ExecuteResults, ProgramReq, -}; -use libsql_replication::rpc::replication::replication_log_client::ReplicationLogClient; -use tonic::{ - body::BoxBody, - codegen::InterceptedService, - metadata::{AsciiMetadataValue, BinaryMetadataValue}, - service::Interceptor, -}; -use tonic_web::{GrpcWebCall, GrpcWebClientService}; -use tower::{Service, ServiceBuilder}; -use tower_http::{ - classify::{self, GrpcCode, GrpcErrorsAsFailures, SharedClassifier}, - trace::{self, TraceLayer}, -}; -use uuid::Uuid; - -use crate::util::{ConnectorService, HttpRequestCallback}; - -use crate::util::box_clone_service::BoxCloneService; - -type ResponseBody = trace::ResponseBody< - GrpcWebCall, - classify::GrpcEosErrorsAsFailures, - trace::DefaultOnBodyChunk, - trace::DefaultOnEos, - trace::DefaultOnFailure, ->; - -#[derive(Debug, Clone)] -pub struct Client { - client_id: Uuid, - pub(crate) replication: ReplicationLogClient>, - proxy: ProxyClient>, -} - -impl Client { - pub fn new( - connector: ConnectorService, - origin: Uri, - auth_token: impl AsRef, - version: Option<&str>, - http_request_callback: Option, - maybe_namespace: Option, - ) -> anyhow::Result { - let ver = version.unwrap_or(env!("CARGO_PKG_VERSION")); - - let version: AsciiMetadataValue = format!("libsql-rpc-{ver}") - .try_into() - .context("Invalid client version")?; - - let auth_token: AsciiMetadataValue = format!("Bearer {}", auth_token.as_ref()) - .try_into() - .context("Invalid auth token must be ascii")?; - - let ns = if let Some(ns_from_arg) = maybe_namespace { - ns_from_arg - } else if let Ok(ns_from_host) = split_namespace(origin.host().unwrap()) { - ns_from_host - } else { - "default".to_string() - }; - - let namespace = BinaryMetadataValue::from_bytes(ns.as_bytes()); - - let channel = GrpcChannel::new(connector, http_request_callback); - - let interceptor = GrpcInterceptor { - auth_token, - namespace, - version, - }; - - let replication = ReplicationLogClient::with_origin( - InterceptedService::new(channel.clone(), interceptor.clone()), - origin.clone(), - ); - - let proxy = ProxyClient::with_origin(InterceptedService::new(channel, interceptor), origin); - - // Remove default tonic `8mb` message limits since fly may buffer - // messages causing the msg len to be longer. - let replication = replication.max_decoding_message_size(usize::MAX); - let proxy = proxy.max_decoding_message_size(usize::MAX); - - let client_id = Uuid::new_v4(); - - Ok(Self { - client_id, - replication, - proxy, - }) - } - - pub fn new_client_id(&mut self) { - self.client_id = Uuid::new_v4(); - } - - pub fn client_id(&self) -> String { - self.client_id.to_string() - } - - pub async fn execute_program(&self, program: ProgramReq) -> anyhow::Result { - // TODO(lucio): Map errors correctly - self.proxy - .clone() - .execute(program) - .await - .map(|r| r.into_inner()) - .map_err(Into::into) - } - - pub async fn describe(&self, describe_req: DescribeRequest) -> anyhow::Result { - self.proxy - .clone() - .describe(describe_req) - .await - .map(|r| r.into_inner()) - .map_err(Into::into) - } -} - -#[derive(Debug, Clone)] -pub struct GrpcChannel { - client: BoxCloneService, http::Response, hyper::Error>, -} - -impl GrpcChannel { - pub fn new( - connector: ConnectorService, - http_request_callback: Option, - ) -> Self { - let client = hyper::Client::builder() - .pool_idle_timeout(None) - .pool_max_idle_per_host(3) - .build(connector); - let client = GrpcWebClientService::new(client); - - let classifier = GrpcErrorsAsFailures::new().with_success(GrpcCode::FailedPrecondition); - - let svc = ServiceBuilder::new() - .layer(TraceLayer::new(SharedClassifier::new(classifier))) - .map_request(move |request: http::Request| { - if let Some(cb) = &http_request_callback { - let (parts, body) = request.into_parts(); - let mut req_copy = http::Request::from_parts(parts, ()); - cb(&mut req_copy); - - let (parts, _) = req_copy.into_parts(); - - http::Request::from_parts(parts, body) - } else { - request - } - }) - .service(client); - - let client = BoxCloneService::new(svc); - - Self { client } - } -} - -impl Service> for GrpcChannel { - type Response = http::Response; - type Error = hyper::Error; - type Future = - Pin> + Send>>; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: http::Request) -> Self::Future { - let fut = self.client.call(req); - Box::pin(fut) - } -} - -#[derive(Clone)] -/// Contains token and namespace headers to append to every request. -pub struct GrpcInterceptor { - auth_token: AsciiMetadataValue, - namespace: BinaryMetadataValue, - version: AsciiMetadataValue, -} - -impl Interceptor for GrpcInterceptor { - fn call(&mut self, mut req: tonic::Request<()>) -> Result, tonic::Status> { - req.metadata_mut() - .insert("x-authorization", self.auth_token.clone()); - req.metadata_mut() - .insert_bin("x-namespace-bin", self.namespace.clone()); - req.metadata_mut() - .insert("x-libsql-client-version", self.version.clone()); - Ok(req) - } -} - -fn split_namespace(host: &str) -> anyhow::Result { - let (ns, _) = host - .split_once('.') - .ok_or_else(|| anyhow::anyhow!("host header should be in the format .<...>"))?; - - if ns.is_empty() { - anyhow::bail!("Invalid namespace as its empty"); - } - - let ns = ns.to_owned(); - Ok(ns) -} diff --git a/vendor/libsql/src/replication/connection.rs b/vendor/libsql/src/replication/connection.rs deleted file mode 100644 index 418ae034..00000000 --- a/vendor/libsql/src/replication/connection.rs +++ /dev/null @@ -1,1036 +0,0 @@ -// TODO(lucio): Move this to `remote/mod.rs` - -use libsql_replication::rpc::proxy::{ - describe_result, query_result::RowResult, Cond, DescribeResult, ExecuteResults, NotCond, - OkCond, Positional, Query, ResultRows, State as RemoteState, Step, -}; -use parking_lot::Mutex; -use std::str::FromStr; -use std::sync::atomic::AtomicU64; -use std::sync::Arc; -use std::time::Duration; - -use crate::parser; -use crate::parser::StmtKind; -use crate::rows::{ColumnsInner, RowInner, RowsInner}; -use crate::statement::Stmt; -use crate::transaction::Tx; -use crate::{ - params::Params, replication::Writer, Error, Result, Statement, Transaction, - TransactionBehavior, ValueType, -}; -use crate::{Column, Row, Rows, Value}; - -use crate::connection::{BatchRows, Conn}; -use crate::local::impls::LibsqlConnection; - -#[derive(Clone)] -pub struct RemoteConnection { - pub(self) local: LibsqlConnection, - writer: Option, - inner: Arc>, - max_write_replication_index: Arc, -} - -#[derive(Default, Debug)] -struct Inner { - state: State, - changes: u64, - total_changes: u64, - last_insert_rowid: i64, -} - -#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] -pub enum State { - #[default] - Init, - Invalid, - Txn, - TxnReadOnly, -} - -impl State { - pub fn step(&self, kind: StmtKind) -> State { - use State; - - tracing::trace!("parser step: {:?} to {:?}", self, kind); - - match (*self, kind) { - (State::TxnReadOnly, StmtKind::TxnBegin) - | (State::Txn, StmtKind::TxnBegin) - | (State::Init, StmtKind::TxnEnd) => State::Invalid, - - (State::TxnReadOnly, StmtKind::TxnEnd) | (State::Txn, StmtKind::TxnEnd) => State::Init, - - // Savepoint only makes sense within a transaction and doesn't change the transaction kind - (State::TxnReadOnly, StmtKind::Savepoint) => State::TxnReadOnly, - (State::Txn, StmtKind::Savepoint) => State::Txn, - (_, StmtKind::Savepoint) => State::Invalid, - // Releasing a savepoint only makes sense inside a transaction and it doesn't change its state - (State::TxnReadOnly, StmtKind::Release) => State::TxnReadOnly, - (State::Txn, StmtKind::Release) => State::Txn, - (_, StmtKind::Release) => State::Invalid, - - ( - state, - StmtKind::Other - | StmtKind::Write - | StmtKind::Read - | StmtKind::Attach - | StmtKind::Detach, - ) => state, - (State::Invalid, _) => State::Invalid, - - (State::Init, StmtKind::TxnBegin) => State::Txn, - (State::Init, StmtKind::TxnBeginReadOnly) => State::TxnReadOnly, - - (State::Txn, StmtKind::TxnBeginReadOnly) - | (State::TxnReadOnly, StmtKind::TxnBeginReadOnly) => State::Invalid, - } - } -} - -/// Given an initial state and an array of queries, attempts to predict what the final state will -/// be -fn predict_final_state<'a>( - mut state: State, - stmts: impl Iterator, -) -> State { - for stmt in stmts { - state = state.step(stmt.kind); - } - state -} - -/// Determines if a set of statements should be executed locally or remotely. It takes into -/// account the current state of the connection and the potential final state of the statements -/// parsed. This means that we only take into account the entire passed sql statement set and -/// for example will reject writes if we are in a readonly txn to start with even if we commit -/// and start a new transaction with the write in it. -pub fn should_execute_local(state: &mut State, stmts: &[parser::Statement]) -> Result { - let predicted_end_state = predict_final_state(*state, stmts.iter()); - - let should_execute_local = match (*state, predicted_end_state) { - (State::Init, State::Init) => { - *state = State::Init; - stmts.iter().all(parser::Statement::is_read_only) - } - - (State::Init, State::TxnReadOnly) | (State::TxnReadOnly, State::TxnReadOnly) => { - let is_read_only = stmts.iter().all(parser::Statement::is_read_only); - - if !is_read_only { - return Err(Error::Misuse( - "Invalid write in a readonly transaction".into(), - )); - } - - *state = State::TxnReadOnly; - true - } - - (State::TxnReadOnly, State::Init) => { - let is_read_only = stmts.iter().all(parser::Statement::is_read_only); - - if !is_read_only { - return Err(Error::Misuse( - "Invalid write in a readonly transaction".into(), - )); - } - - *state = State::Init; - true - } - - (init, State::Invalid) => { - let err = Err(Error::InvalidParserState(format!("{:?}", init))); - - // Reset state always back to init so the user can start over - *state = State::Init; - - return err; - } - - _ => false, - }; - - Ok(should_execute_local) -} - -impl From for State { - fn from(value: RemoteState) -> Self { - match value { - RemoteState::Init => State::Init, - RemoteState::Invalid => State::Invalid, - RemoteState::Txn => State::Txn, - } - } -} - -impl RemoteConnection { - pub(crate) fn new( - local: LibsqlConnection, - writer: Option, - max_write_replication_index: Arc, - ) -> Self { - let state = Arc::new(Mutex::new(Inner::default())); - Self { - local, - writer, - inner: state, - max_write_replication_index, - } - } - - fn update_max_write_replication_index(&self, index: Option) { - if let Some(index) = index { - let mut current = self - .max_write_replication_index - .load(std::sync::atomic::Ordering::SeqCst); - while index > current { - match self.max_write_replication_index.compare_exchange( - current, - index, - std::sync::atomic::Ordering::SeqCst, - std::sync::atomic::Ordering::SeqCst, - ) { - Ok(_) => break, - Err(new_current) => current = new_current, - } - } - } - } - - fn is_state_init(&self) -> bool { - matches!(self.inner.lock().state, State::Init) - } - - pub(self) async fn execute_remote( - &self, - stmts: Vec, - params: Params, - ) -> Result { - let Some(ref writer) = self.writer else { - return Err(Error::Misuse( - "Cannot delegate write in local replica mode.".into(), - )); - }; - let res = writer - .execute_program(stmts, params) - .await - .map_err(|e| Error::WriteDelegation(e.into()))?; - - { - let mut inner = self.inner.lock(); - inner.state = RemoteState::try_from(res.state) - .expect("Invalid state enum") - .into(); - } - - self.update_max_write_replication_index(res.current_frame_no); - - if let Some(replicator) = writer.replicator() { - replicator.sync_oneshot().await?; - } - - Ok(res) - } - - pub(self) async fn execute_steps_remote(&self, steps: Vec) -> Result { - let Some(ref writer) = self.writer else { - return Err(Error::Misuse( - "Cannot delegate write in local replica mode.".into(), - )); - }; - let res = writer - .execute_steps(steps) - .await - .map_err(|e| Error::WriteDelegation(e.into()))?; - - { - let mut inner = self.inner.lock(); - inner.state = RemoteState::try_from(res.state) - .expect("Invalid state enum") - .into(); - } - - self.update_max_write_replication_index(res.current_frame_no); - - if let Some(replicator) = writer.replicator() { - replicator.sync_oneshot().await?; - } - - Ok(res) - } - - pub(self) async fn describe(&self, stmt: impl Into) -> Result { - let Some(ref writer) = self.writer else { - return Err(Error::Misuse( - "Cannot describe in local replica mode.".into(), - )); - }; - let res = writer - .describe(stmt) - .await - .map_err(|e| Error::WriteDelegation(e.into()))?; - - Ok(res) - } - - pub(self) fn update_state(&self, row: &ResultRows) { - let mut state = self.inner.lock(); - - if let Some(rowid) = &row.last_insert_rowid { - state.last_insert_rowid = *rowid; - } - - state.total_changes += row.affected_row_count; - state.changes = row.affected_row_count; - } - - pub(self) fn should_execute_local(&self, stmts: &[parser::Statement]) -> Result { - let mut inner = self.inner.lock(); - - should_execute_local(&mut inner.state, stmts) - } - - // Will execute a rollback if the local conn is in TXN state - // and will return false if no rollback happened and the - // execute was valid. - pub(self) async fn maybe_execute_rollback(&self) -> Result { - if self.inner.lock().state != State::TxnReadOnly && !self.local.is_autocommit() { - self.local.execute("ROLLBACK", Params::None).await?; - Ok(true) - } else { - Ok(false) - } - } -} - -#[async_trait::async_trait] -impl Conn for RemoteConnection { - async fn execute(&self, sql: &str, params: Params) -> Result { - let stmts = parser::Statement::parse(sql).collect::>>()?; - - if self.should_execute_local(&stmts[..])? { - // TODO(lucio): See if we can arc the params here to cheaply clone - // or convert the inner bytes type to an Arc<[u8]> - let changes = self.local.execute(sql, params.clone()).await?; - - if !self.maybe_execute_rollback().await? { - return Ok(changes); - } - } - - let res = self.execute_remote(stmts, params).await?; - - let result = res - .results - .into_iter() - .next() - .expect("Expected at least one result"); - - let affected_row_count = match result.row_result { - Some(RowResult::Row(row)) => { - self.update_state(&row); - row.affected_row_count - } - Some(RowResult::Error(e)) => { - return Err(Error::RemoteSqliteFailure( - e.code, - e.extended_code, - e.message, - )) - } - None => panic!("unexpected empty result row"), - }; - - Ok(affected_row_count) - } - - async fn execute_batch(&self, sql: &str) -> Result { - let stmts = parser::Statement::parse(sql).collect::>>()?; - - if self.should_execute_local(&stmts[..])? { - self.local.execute_batch(sql).await?; - - if !self.maybe_execute_rollback().await? { - return Ok(BatchRows::empty()); - } - } - - let res = self.execute_remote(stmts, Params::None).await?; - - for result in res.results { - match result.row_result { - Some(RowResult::Row(row)) => self.update_state(&row), - Some(RowResult::Error(e)) => { - return Err(Error::RemoteSqliteFailure( - e.code, - e.extended_code, - e.message, - )) - } - None => panic!("unexpected empty result row"), - }; - } - - Ok(BatchRows::empty()) - } - - async fn execute_transactional_batch(&self, sql: &str) -> Result { - let mut stmts = Vec::new(); - let parse = crate::parser::Statement::parse(sql); - for s in parse { - let s = s?; - if s.kind == StmtKind::TxnBegin - || s.kind == StmtKind::TxnBeginReadOnly - || s.kind == StmtKind::TxnEnd - { - return Err(Error::TransactionalBatchError( - "Transactions forbidden inside transactional batch".to_string(), - )); - } - stmts.push(s); - } - - if self.should_execute_local(&stmts[..])? { - self.local.execute_transactional_batch(sql).await?; - - if !self.maybe_execute_rollback().await? { - return Ok(BatchRows::empty()); - } - } - - let mut steps = Vec::with_capacity(stmts.len() + 3); - steps.push(Step { - query: Some(Query { - stmt: "BEGIN TRANSACTION".to_string(), - params: Some(libsql_replication::rpc::proxy::query::Params::Positional( - Positional::default(), - )), - ..Default::default() - }), - ..Default::default() - }); - let count = stmts.len() as i64; - for (idx, stmt) in stmts.into_iter().enumerate() { - let step = Step { - cond: Some(Cond { - cond: Some(libsql_replication::rpc::proxy::cond::Cond::Ok(OkCond { - step: idx as i64, - ..Default::default() - })), - }), - query: Some(Query { - stmt: stmt.stmt, - params: Some(libsql_replication::rpc::proxy::query::Params::Positional( - Positional::default(), - )), - ..Default::default() - }), - ..Default::default() - }; - steps.push(step); - } - steps.push(Step { - cond: Some(Cond { - cond: Some(libsql_replication::rpc::proxy::cond::Cond::Ok(OkCond { - step: count, - ..Default::default() - })), - ..Default::default() - }), - query: Some(Query { - stmt: "COMMIT".to_string(), - params: Some(libsql_replication::rpc::proxy::query::Params::Positional( - Positional::default(), - )), - ..Default::default() - }), - ..Default::default() - }); - steps.push(Step { - cond: Some(Cond { - cond: Some(libsql_replication::rpc::proxy::cond::Cond::Not(Box::new( - NotCond { - cond: Some(Box::new(Cond { - cond: Some(libsql_replication::rpc::proxy::cond::Cond::Ok(OkCond { - step: count + 1, - ..Default::default() - })), - ..Default::default() - })), - ..Default::default() - }, - ))), - ..Default::default() - }), - query: Some(Query { - stmt: "ROLLBACK".to_string(), - params: Some(libsql_replication::rpc::proxy::query::Params::Positional( - Positional::default(), - )), - ..Default::default() - }), - ..Default::default() - }); - - let res = self.execute_steps_remote(steps).await?; - - for result in res.results { - match result.row_result { - Some(RowResult::Row(row)) => self.update_state(&row), - Some(RowResult::Error(e)) => { - return Err(Error::RemoteSqliteFailure( - e.code, - e.extended_code, - e.message, - )) - } - None => panic!("unexpected empty result row"), - }; - } - - Ok(BatchRows::empty()) - } - - async fn prepare(&self, sql: &str) -> Result { - let stmt = RemoteStatement::prepare(self.clone(), sql).await?; - - Ok(crate::Statement { - inner: Box::new(stmt), - }) - } - - async fn transaction(&self, tx_behavior: TransactionBehavior) -> Result { - let tx = RemoteTx::begin(self.clone(), tx_behavior).await?; - - Ok(Transaction { - inner: Box::new(tx), - conn: crate::Connection { - conn: Arc::new(self.clone()), - }, - close: None, - }) - } - - fn interrupt(&self) -> Result<()> { - // Interrupt is a no-op for remote connections. - Ok(()) - } - - fn busy_timeout(&self, _timeout: Duration) -> Result<()> { - // Busy timeout is a no-op for remote connections. - Ok(()) - } - - fn is_autocommit(&self) -> bool { - self.is_state_init() - } - - fn changes(&self) -> u64 { - self.inner.lock().changes - } - - fn total_changes(&self) -> u64 { - self.inner.lock().total_changes - } - - fn last_insert_rowid(&self) -> i64 { - self.inner.lock().last_insert_rowid - } - - async fn reset(&self) {} - - fn set_reserved_bytes(&self, reserved_bytes: i32) -> Result<()> { - self.local.set_reserved_bytes(reserved_bytes) - } - - fn get_reserved_bytes(&self) -> Result { - self.local.get_reserved_bytes() - } -} - -pub struct ColumnMeta { - name: String, - origin_name: Option, - table_name: Option, - database_name: Option, - decl_type: Option, -} - -impl From for ColumnMeta { - fn from(col: libsql_replication::rpc::proxy::Column) -> Self { - Self { - name: col.name.clone(), - origin_name: None, - table_name: None, - database_name: None, - decl_type: col.decltype, - } - } -} - -impl<'a> From<&'a ColumnMeta> for Column<'a> { - fn from(col: &'a ColumnMeta) -> Self { - Self { - name: col.name.as_str(), - origin_name: col.origin_name.as_deref(), - table_name: col.table_name.as_deref(), - database_name: col.database_name.as_deref(), - decl_type: col.decl_type.as_deref(), - } - } -} - -pub struct StatementMeta { - columns: Vec, - param_names: Vec, - param_count: u64, -} - -pub struct RemoteStatement { - conn: RemoteConnection, - stmts: Vec, - /// Empty if we should execute locally - metas: Vec, - /// Set to `Some` when we should execute this locally - local_statement: Option, -} - -impl RemoteStatement { - pub async fn prepare(conn: RemoteConnection, sql: &str) -> Result { - let stmts = parser::Statement::parse(sql).collect::>>()?; - - if conn.should_execute_local(&stmts[..])? { - tracing::trace!("Preparing {sql} locally"); - let stmt = conn.local.prepare(sql).await?; - return Ok(Self { - conn, - stmts, - local_statement: Some(stmt), - metas: vec![], - }); - } - - let metas = fetch_metas(&conn, &stmts).await?; - Ok(Self { - conn, - stmts, - local_statement: None, - metas, - }) - } -} - -async fn fetch_meta(conn: &RemoteConnection, stmt: &parser::Statement) -> Result { - tracing::trace!("Fetching metadata of statement {}", stmt.stmt); - match conn.describe(&stmt.stmt).await? { - DescribeResult { - describe_result: Some(describe_result::DescribeResult::Description(d)), - } => Ok(StatementMeta { - columns: d - .column_descriptions - .into_iter() - .map(|c| c.into()) - .collect(), - param_names: d.param_names.into_iter().collect(), - param_count: d.param_count, - }), - DescribeResult { - describe_result: Some(describe_result::DescribeResult::Error(e)), - } => Err(Error::SqliteFailure(e.code, e.message)), - _ => Err(Error::Misuse("unexpected describe result".into())), - } -} - -// FIXME(sarna): do we ever want to fetch metadata about multiple statements at one go? -async fn fetch_metas( - conn: &RemoteConnection, - stmts: &[parser::Statement], -) -> Result> { - let mut metas = vec![]; - for stmt in stmts { - let meta = fetch_meta(conn, stmt).await?; - metas.push(meta); - } - Ok(metas) -} - -#[async_trait::async_trait] -impl Stmt for RemoteStatement { - fn finalize(&mut self) {} - - async fn execute(&self, params: &Params) -> Result { - if let Some(stmt) = &self.local_statement { - return stmt.execute(params.clone()).await; - } - - let res = self - .conn - .execute_remote(self.stmts.clone(), params.clone()) - .await?; - - let result = res - .results - .into_iter() - .next() - .expect("Expected at least one result"); - - let affected_row_count = match result.row_result { - Some(RowResult::Row(row)) => { - self.conn.update_state(&row); - row.affected_row_count - } - Some(RowResult::Error(e)) => { - return Err(Error::RemoteSqliteFailure( - e.code, - e.extended_code, - e.message, - )) - } - None => panic!("unexpected empty result row"), - }; - - Ok(affected_row_count as usize) - } - - async fn query(&self, params: &Params) -> Result { - if let Some(stmt) = &self.local_statement { - return stmt.query(params.clone()).await; - } - - let res = self - .conn - .execute_remote(self.stmts.clone(), params.clone()) - .await?; - - let result = res - .results - .into_iter() - .next() - .expect("Expected at least one result"); - - let rows = match result.row_result { - Some(RowResult::Row(row)) => { - self.conn.update_state(&row); - row - } - Some(RowResult::Error(e)) => { - return Err(Error::RemoteSqliteFailure( - e.code, - e.extended_code, - e.message, - )) - } - None => panic!("unexpected empty result row"), - }; - - Ok(Rows::new(RemoteRows(rows, 0))) - } - - async fn run(&self, params: &Params) -> Result<()> { - if let Some(stmt) = &self.local_statement { - return stmt.run(params.clone()).await; - } - - let res = self - .conn - .execute_remote(self.stmts.clone(), params.clone()) - .await?; - - for result in res.results { - match result.row_result { - Some(RowResult::Row(row)) => self.conn.update_state(&row), - Some(RowResult::Error(e)) => { - return Err(Error::RemoteSqliteFailure( - e.code, - e.extended_code, - e.message, - )) - } - None => panic!("unexpected empty result row"), - }; - } - - Ok(()) - } - - fn interrupt(&self) -> Result<()> { - Err(Error::Misuse( - "interrupt is not supported for remote connections".to_string(), - )) - } - - fn reset(&self) {} - - fn parameter_count(&self) -> usize { - if let Some(stmt) = self.local_statement.as_ref() { - return stmt.parameter_count(); - } - // FIXME: we need to decide if we keep RemoteStatement as a single statement, or else how to handle this - match self.metas.first() { - Some(meta) => meta.param_count as usize, - None => 0, - } - } - - fn parameter_name(&self, idx: i32) -> Option<&str> { - if let Some(stmt) = self.local_statement.as_ref() { - return stmt.parameter_name(idx); - } - // FIXME: we need to decide if we keep RemoteStatement as a single statement, or else how to handle this - match self.metas.first() { - Some(meta) => meta.param_names.get(idx as usize).map(|s| s.as_str()), - None => None, - } - } - - fn column_count(&self) -> usize { - if let Some(stmt) = self.local_statement.as_ref() { - return stmt.column_count(); - } - match self.metas.first() { - Some(meta) => meta.columns.len(), - None => 0, - } - } - - fn columns(&self) -> Vec { - if let Some(stmt) = self.local_statement.as_ref() { - return stmt.columns(); - } - // FIXME: we need to decide if we keep RemoteStatement as a single statement, or else how to handle this - match self.metas.first() { - Some(meta) => meta - .columns - .iter() - .map(|c| Column { - name: &c.name, - origin_name: c.origin_name.as_deref(), - database_name: c.database_name.as_deref(), - table_name: c.table_name.as_deref(), - decl_type: c.decl_type.as_deref(), - }) - .collect(), - None => vec![], - } - } -} - -pub(crate) struct RemoteRows(pub(crate) ResultRows, pub(crate) usize); - -#[async_trait::async_trait] -impl RowsInner for RemoteRows { - async fn next(&mut self) -> Result> { - // TODO(lucio): Switch to a vecdeque and reduce allocations - let cursor = self.1; - self.1 += 1; - let row = self.0.rows.get(cursor); - - if row.is_none() { - return Ok(None); - } - - let row = row.unwrap(); - - let values = row - .values - .iter() - .map(Value::try_from) - .collect::>>()?; - - let row = RemoteRow(values, self.0.column_descriptions.clone()); - Ok(Some(row).map(Box::new).map(|inner| Row { inner })) - } -} - -impl ColumnsInner for RemoteRows { - fn column_count(&self) -> i32 { - self.0.column_descriptions.len() as i32 - } - - fn column_name(&self, idx: i32) -> Option<&str> { - self.0 - .column_descriptions - .get(idx as usize) - .map(|s| s.name.as_str()) - } - - fn column_type(&self, idx: i32) -> Result { - let col = self.0.column_descriptions.get(idx as usize).unwrap(); - col.decltype - .as_deref() - .and_then(|v| ValueType::from_str(v).ok()) - .map(ValueType::from) - .ok_or(Error::InvalidColumnType) - } -} - -#[derive(Debug)] -struct RemoteRow(Vec, Vec); - -impl RowInner for RemoteRow { - fn column_value(&self, idx: i32) -> Result { - self.0 - .get(idx as usize) - .cloned() - .ok_or(Error::InvalidColumnIndex) - } - - fn column_str(&self, idx: i32) -> Result<&str> { - let value = self.0.get(idx as usize).ok_or(Error::InvalidColumnIndex)?; - - match &value { - Value::Text(s) => Ok(s.as_str()), - _ => Err(Error::InvalidColumnType), - } - } -} - -impl ColumnsInner for RemoteRow { - fn column_name(&self, idx: i32) -> Option<&str> { - self.1.get(idx as usize).map(|s| s.name.as_str()) - } - - fn column_type(&self, idx: i32) -> Result { - let col = self.1.get(idx as usize).unwrap(); - col.decltype - .as_deref() - .and_then(|v| ValueType::from_str(v).ok()) - .map(ValueType::from) - .ok_or(Error::InvalidColumnType) - } - - fn column_count(&self) -> i32 { - self.1.len() as i32 - } -} - -pub(super) struct RemoteTx(pub(super) Option); - -impl RemoteTx { - pub(crate) async fn begin( - conn: RemoteConnection, - tx_behavior: TransactionBehavior, - ) -> Result { - let begin_stmt = match tx_behavior { - TransactionBehavior::Deferred => "BEGIN DEFERRED", - TransactionBehavior::Immediate => "BEGIN IMMEDIATE", - TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE", - TransactionBehavior::ReadOnly => "BEGIN READONLY", - }; - - let _ = conn.execute(begin_stmt, Params::None).await?; - Ok(Self(Some(conn))) - } -} - -#[async_trait::async_trait] -impl Tx for RemoteTx { - async fn commit(&mut self) -> Result<()> { - let conn = self.0.take().expect("Tx already dropped"); - conn.execute("COMMIT", Params::None).await?; - Ok(()) - } - - async fn rollback(&mut self) -> Result<()> { - let conn = self.0.take().expect("Tx already dropped"); - conn.execute("ROLLBACK", Params::None).await?; - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use crate::parser::Statement; - - use super::{should_execute_local, State}; - - #[track_caller] - fn assert_should_execute_local( - sql: &str, - mut state: State, - expected_final_state: State, - expected_final_output: Result, - ) { - let stmts = Statement::parse(sql) - .collect::, _>>() - .unwrap(); - - let out = should_execute_local(&mut state, &stmts[..]); - assert_eq!(state, expected_final_state); - assert_eq!(out.map_err(|_| ()), expected_final_output); - } - - #[test] - #[should_panic] - fn invalid() { - assert_should_execute_local( - " - BEGIN READONLY; - SELECT 1; - COMMIT; - ", - State::Txn, - State::Invalid, - Err(()), - ); - } - - #[test] - fn valid() { - assert_should_execute_local( - " - BEGIN READONLY; - SELECT 1; - COMMIT; - ", - State::Init, - State::Init, - Ok(true), - ); - - assert_should_execute_local( - " - BEGIN READONLY; - ", - State::Init, - State::TxnReadOnly, - Ok(true), - ); - - assert_should_execute_local( - " - SELECT 1; - ", - State::TxnReadOnly, - State::TxnReadOnly, - Ok(true), - ); - - assert_should_execute_local( - " - COMMIT; - ", - State::TxnReadOnly, - State::Init, - Ok(true), - ); - - assert_should_execute_local( - " - BEGIN READONLY; - SELECT 1; - COMMIT; - BEGIN IMMEDIATE; - SELECT 1; - COMMIT; - ", - State::Init, - State::Init, - Ok(false), - ); - } -} diff --git a/vendor/libsql/src/replication/local_client.rs b/vendor/libsql/src/replication/local_client.rs deleted file mode 100644 index 421210ff..00000000 --- a/vendor/libsql/src/replication/local_client.rs +++ /dev/null @@ -1,121 +0,0 @@ -use std::path::Path; -use std::pin::Pin; - -use futures::{StreamExt, TryStreamExt}; -use libsql_replication::{ - rpc::replication::Frame as RpcFrame, - frame::{Frame, FrameNo}, - meta::WalIndexMeta, - replicator::{Error, ReplicatorClient}, -}; -use tokio_stream::Stream; - -use crate::replication::Frames; - -/// A replicator client that doesn't perform networking, and is instead _loaded_ with the frames to -/// inject -pub struct LocalClient { - frames: Option, - meta: WalIndexMeta, -} - -impl LocalClient { - pub(crate) async fn new(path: &Path) -> anyhow::Result { - let mut meta = WalIndexMeta::open_prefixed(path).await?; - meta.init_default(); - Ok(Self { frames: None, meta }) - } - - /// Load `frames` into the client. The caller must ensure that client was flushed before - /// calling this function again. - pub(crate) fn load_frames(&mut self, frames: Frames) { - assert!(self.frames.is_none(), "frames not flushed before loading"); - self.frames.replace(frames); - } -} - -#[async_trait::async_trait] -impl ReplicatorClient for LocalClient { - type FrameStream = Pin> + Send + 'static>>; - - /// Perform handshake with remote - async fn handshake(&mut self) -> Result<(), Error> { - Ok(()) - } - - /// Return a stream of frames to apply to the database - async fn next_frames(&mut self) -> Result { - match self.frames.take() { - Some(Frames::Vec(f)) => { - let iter = f.into_iter().map(|f| RpcFrame { data: f.bytes(), timestamp: None, durable_frame_no: None }).map(Ok); - Ok(Box::pin(tokio_stream::iter(iter))) - } - Some(f @ Frames::Snapshot(_)) => { - self.frames.replace(f); - Err(Error::NeedSnapshot) - } - None => Ok(Box::pin(tokio_stream::empty())), - } - } - - /// Return a snapshot for the current replication index. Called after next_frame has returned a - /// NeedSnapshot error - async fn snapshot(&mut self) -> Result { - match self.frames.take() { - Some(Frames::Snapshot(frames)) => { - let size_after = frames.header().size_after.get(); - let stream = async_stream::try_stream! { - let s = frames.into_stream_mut().map_err(|e| Error::Client(Box::new(e))).peekable(); - tokio::pin!(s); - while let Some(mut next) = s.as_mut().next().await.transpose()? { - if s.as_mut().peek().await.is_none() { - next.header_mut().size_after = size_after.into(); - } - let frame = Frame::from(next); - yield RpcFrame { data: frame.bytes(), timestamp: None, durable_frame_no: None }; - } - }; - - Ok(Box::pin(stream)) - } - Some(Frames::Vec(_)) | None => Ok(Box::pin(tokio_stream::empty())), - } - } - - /// set the new commit frame_no - async fn commit_frame_no(&mut self, frame_no: FrameNo) -> Result<(), Error> { - self.meta.set_commit_frame_no(frame_no).await?; - Ok(()) - } - - fn committed_frame_no(&self) -> Option { - self.meta.current_frame_no() - } - - fn rollback(&mut self) {} -} - -#[cfg(test)] -mod test { - use libsql_replication::{frame::FrameHeader, snapshot::SnapshotFile}; - use tempfile::tempdir; - use zerocopy::FromBytes; - - use super::*; - - #[tokio::test] - async fn snapshot_stream_commited() { - let tmp = tempdir().unwrap(); - let snapshot = SnapshotFile::open("assets/test/snapshot.snap", None) - .await - .unwrap(); - let mut client = LocalClient::new(&tmp.path().join("data")).await.unwrap(); - client.load_frames(Frames::Snapshot(snapshot)); - let mut s = client.snapshot().await.unwrap(); - assert!(matches!(s.next().await, Some(Ok(_)))); - let last = s.next().await.unwrap().unwrap(); - let header: FrameHeader = FrameHeader::read_from_prefix(&last.data[..]).unwrap(); - assert_eq!(header.size_after.get(), 2); - assert!(s.next().await.is_none()); - } -} diff --git a/vendor/libsql/src/replication/mod.rs b/vendor/libsql/src/replication/mod.rs deleted file mode 100644 index 57ef8fc6..00000000 --- a/vendor/libsql/src/replication/mod.rs +++ /dev/null @@ -1,396 +0,0 @@ -//! Utilities used when using a replicated version of libsql. - -use std::path::PathBuf; -use std::sync::atomic::AtomicUsize; -use std::sync::Arc; -use std::time::Duration; - -pub use libsql_replication::frame::{Frame, FrameNo}; -use libsql_replication::injector::SqliteInjector; -use libsql_replication::replicator::{Either, Replicator}; -pub use libsql_replication::snapshot::SnapshotFile; - -use libsql_replication::rpc::proxy::{ - query::Params, DescribeRequest, DescribeResult, ExecuteResults, Positional, Program, - ProgramReq, Query, Step, -}; -use tokio::sync::Mutex; -use tokio::task::AbortHandle; -use tracing::Instrument; - -use crate::database::EncryptionConfig; -use crate::parser::Statement; -use crate::{errors, Result}; - -use libsql_replication::replicator::ReplicatorClient; - -pub(crate) use connection::RemoteConnection; - -use self::local_client::LocalClient; -use self::remote_client::RemoteClient; - -pub(crate) mod client; -pub(crate) mod connection; -pub(crate) mod local_client; -pub(crate) mod remote_client; - -pub use crate::database::Replicated; - -/// A set of rames to be injected via `sync_frames`. -pub enum Frames { - /// A set of frames, in increasing frame_no. - Vec(Vec), - /// A stream of snapshot frames. The frames must be in reverse frame_no, and the pages - /// deduplicated. The snapshot is expected to be a single commit unit. - Snapshot(SnapshotFile), -} - -/// Detailed logs about bytes synced with primary -pub struct SyncUsageStats { - prefetched_bytes: u64, - prefetched_bytes_discarded_due_to_new_session: u64, - prefetched_bytes_discarded_due_to_consecutive_handshake: u64, - prefetched_bytes_discarded_due_to_invalid_frame_header: u64, - synced_bytes_discarded_due_to_invalid_frame_header: u64, - prefetched_bytes_used: u64, - synced_bytes_used: u64, - snapshot_bytes: u64, -} - -impl SyncUsageStats { - /// Number of bytes prefetched while doing handshake - pub fn prefetched_bytes(&self) -> u64 { - self.prefetched_bytes - } - - /// Number of bytes prefetched and discarded due to the change of the client session - pub fn prefetched_bytes_discarded_due_to_new_session(&self) -> u64 { - self.prefetched_bytes_discarded_due_to_new_session - } - - /// Number of bytes prefetched and discarded due to consecutive handshake with new prefetch - pub fn prefetched_bytes_discarded_due_to_consecutive_handshake(&self) -> u64 { - self.prefetched_bytes_discarded_due_to_consecutive_handshake - } - - /// Number of bytes prefetched and discarded due to invalid frame header in received frames - pub fn prefetched_bytes_discarded_due_to_invalid_frame_header(&self) -> u64 { - self.prefetched_bytes_discarded_due_to_invalid_frame_header - } - - /// Number of bytes synced and discarded due to invalid frame header in received frames - pub fn synced_bytes_discarded_due_to_invalid_frame_header(&self) -> u64 { - self.synced_bytes_discarded_due_to_invalid_frame_header - } - - /// Number of bytes prefetched and used - pub fn prefetched_bytes_used(&self) -> u64 { - self.prefetched_bytes_used - } - - /// Number of bytes synced and used - pub fn synced_bytes_used(&self) -> u64 { - self.synced_bytes_used - } - - /// Number of bytes downloaded as snapshots - pub fn snapshot_bytes(&self) -> u64 { - self.snapshot_bytes - } -} - -#[derive(Clone)] -pub(crate) struct Writer { - pub(crate) client: client::Client, - pub(crate) replicator: Option, -} - -impl Writer { - pub(crate) async fn execute_program( - &self, - steps: Vec, - params: impl Into, - ) -> anyhow::Result { - let mut params = Some(params.into()); - - let steps = steps - .into_iter() - .map(|stmt| Step { - query: Some(Query { - stmt: stmt.stmt, - // TODO(lucio): Pass params - params: Some( - params - .take() - .unwrap_or(Params::Positional(Positional::default())), - ), - ..Default::default() - }), - ..Default::default() - }) - .collect(); - - self.execute_steps(steps).await - } - - pub(crate) async fn execute_steps(&self, steps: Vec) -> anyhow::Result { - self.client - .execute_program(ProgramReq { - client_id: self.client.client_id(), - pgm: Some(Program { steps }), - }) - .await - } - - pub(crate) async fn describe(&self, stmt: impl Into) -> anyhow::Result { - let stmt = stmt.into(); - - self.client - .describe(DescribeRequest { - client_id: self.client.client_id(), - stmt, - }) - .await - } - - pub(crate) fn replicator(&self) -> Option<&EmbeddedReplicator> { - self.replicator.as_ref() - } - - pub(crate) fn new_client_id(&mut self) { - self.client.new_client_id() - } -} - -#[derive(Clone)] -pub(crate) struct EmbeddedReplicator { - replicator: Arc, SqliteInjector>>>, - bg_abort: Option>, - last_frames_synced: Arc, -} - -impl From for errors::Error { - fn from(err: libsql_replication::replicator::Error) -> Self { - errors::Error::Replication(err.into()) - } -} - -impl EmbeddedReplicator { - pub async fn with_remote( - client: RemoteClient, - db_path: PathBuf, - auto_checkpoint: u32, - encryption_config: Option, - perodic_sync: Option, - ) -> Result { - let mut replicator = - Replicator::new_sqlite( - Either::Left(client), - db_path, - auto_checkpoint, - encryption_config, - ).await?; - replicator.set_primary_handshake_retries(3); - let replicator = Arc::new(Mutex::new(replicator)); - - let mut replicator = Self { - replicator, - bg_abort: None, - last_frames_synced: Arc::new(AtomicUsize::new(0)), - }; - - if let Some(sync_duration) = perodic_sync { - let replicator2 = replicator.clone(); - - let jh = tokio::spawn( - async move { - loop { - if let Err(e) = replicator2.sync_oneshot().await { - tracing::error!("replicator sync error: {}", e); - } - - tokio::time::sleep(sync_duration).await; - } - } - .instrument(tracing::info_span!("sync_interval")), - ); - - replicator.bg_abort = Some(Arc::new(DropAbort(jh.abort_handle()))); - } - - Ok(replicator) - } - - pub async fn with_local( - client: LocalClient, - db_path: PathBuf, - auto_checkpoint: u32, - encryption_config: Option, - ) -> Result { - let replicator = Arc::new(Mutex::new( - Replicator::new_sqlite( - Either::Right(client), - db_path, - auto_checkpoint, - encryption_config, - ) - .await?, - )); - - Ok(Self { - replicator, - bg_abort: None, - last_frames_synced: Arc::new(AtomicUsize::new(0)), - }) - } - - pub async fn get_sync_usage_stats(&self) -> Result { - let mut replicator = self.replicator.lock().await; - match replicator.client_mut() { - Either::Right(_) => { - Err(crate::errors::Error::Misuse( - "Trying to get sync usage stats, but this is a local replicator".into(), - )) - } - Either::Left(c) => { - let stats = c.sync_stats(); - Ok(SyncUsageStats { - prefetched_bytes: stats.prefetched_bytes.load(std::sync::atomic::Ordering::SeqCst), - prefetched_bytes_discarded_due_to_new_session: stats - .prefetched_bytes_discarded_due_to_new_session.load(std::sync::atomic::Ordering::SeqCst), - prefetched_bytes_discarded_due_to_consecutive_handshake: stats - .prefetched_bytes_discarded_due_to_consecutive_handshake.load(std::sync::atomic::Ordering::SeqCst), - prefetched_bytes_discarded_due_to_invalid_frame_header: stats - .prefetched_bytes_discarded_due_to_invalid_frame_header.load(std::sync::atomic::Ordering::SeqCst), - synced_bytes_discarded_due_to_invalid_frame_header: stats - .synced_bytes_discarded_due_to_invalid_frame_header.load(std::sync::atomic::Ordering::SeqCst), - prefetched_bytes_used: stats.prefetched_bytes_used.load(std::sync::atomic::Ordering::SeqCst), - synced_bytes_used: stats.synced_bytes_used.load(std::sync::atomic::Ordering::SeqCst), - snapshot_bytes: stats.snapshot_bytes.load(std::sync::atomic::Ordering::SeqCst), - }) - } - } - - } - - pub async fn sync_oneshot(&self) -> Result { - use libsql_replication::replicator::ReplicatorClient; - - let mut replicator = self.replicator.lock().await; - if !matches!(replicator.client_mut(), Either::Left(_)) { - return Err(crate::errors::Error::Misuse( - "Trying to replicate from HTTP, but this is a local replicator".into(), - )); - } - - // we force a handshake to get the most up to date replication index from the primary. - replicator.force_handshake(); - - loop { - match replicator.replicate().await { - Err(libsql_replication::replicator::Error::Meta( - libsql_replication::meta::Error::LogIncompatible, - )) => { - // The meta must have been marked as dirty, replicate again from scratch - // this time. - tracing::debug!("re-replicating database after LogIncompatible error"); - replicator - .replicate() - .await - .map_err(|e| crate::Error::Replication(e.into()))?; - } - Err(e) => return Err(crate::Error::Replication(e.into())), - Ok(_) => { - let Either::Left(client) = replicator.client_mut() else { - unreachable!() - }; - let Some(primary_index) = client.last_handshake_replication_index() else { - return Ok(Replicated { - frame_no: None, - frames_synced: 0, - }); - }; - if let Some(replica_index) = replicator.client_mut().committed_frame_no() { - if replica_index >= primary_index { - break; - } - } - } - } - } - - let current_frames_synced = replicator.frames_synced(); - - let mut last_frames_synced = self - .last_frames_synced - .load(std::sync::atomic::Ordering::Relaxed); - - while current_frames_synced > last_frames_synced { - match self.last_frames_synced.compare_exchange( - last_frames_synced, - current_frames_synced, - std::sync::atomic::Ordering::Relaxed, - std::sync::atomic::Ordering::Relaxed, - ) { - Ok(_) => break, - Err(current_value) => last_frames_synced = current_value, - } - } - - let frames_synced = - ((current_frames_synced as i64 - last_frames_synced as i64).abs()) as usize; - - let replicated = Replicated { - frame_no: replicator.client_mut().committed_frame_no(), - frames_synced, - }; - - Ok(replicated) - } - - pub async fn sync_frames(&self, frames: Frames) -> Result> { - let mut replicator = self.replicator.lock().await; - - match replicator.client_mut() { - Either::Right(c) => { - c.load_frames(frames); - } - Either::Left(_) => { - return Err(crate::errors::Error::Misuse( - "Trying to call sync_frames with an HTTP replicator".into(), - )) - } - } - replicator - .replicate() - .await - .map_err(|e| crate::Error::Replication(e.into()))?; - - Ok(replicator.client_mut().committed_frame_no()) - } - - pub async fn flush(&self) -> Result> { - let mut replicator = self.replicator.lock().await; - replicator - .flush() - .await - .map_err(|e| crate::Error::Replication(e.into()))?; - Ok(replicator.client_mut().committed_frame_no()) - } - - pub async fn committed_frame_no(&self) -> Option { - self.replicator - .lock() - .await - .client_mut() - .committed_frame_no() - } -} - -struct DropAbort(AbortHandle); - -impl Drop for DropAbort { - fn drop(&mut self) { - self.0.abort(); - } -} diff --git a/vendor/libsql/src/replication/remote_client.rs b/vendor/libsql/src/replication/remote_client.rs deleted file mode 100644 index d55f68ec..00000000 --- a/vendor/libsql/src/replication/remote_client.rs +++ /dev/null @@ -1,403 +0,0 @@ -use std::future::Future; -use std::path::Path; -use std::pin::Pin; -use std::sync::Arc; -use std::sync::atomic::AtomicU64; -use std::time::{Duration, Instant}; - -use bytes::Bytes; -use futures::{StreamExt as _, TryStreamExt}; -use libsql_replication::frame::{FrameHeader, FrameNo}; -use libsql_replication::meta::WalIndexMeta; -use libsql_replication::replicator::{Error, ReplicatorClient}; -use libsql_replication::rpc::replication::{ - Frame as RpcFrame, verify_session_token, Frames, HelloRequest, HelloResponse, LogOffset, SESSION_TOKEN_KEY, -}; -use tokio_stream::Stream; -use tonic::metadata::AsciiMetadataValue; -use tonic::{Response, Status}; -use zerocopy::FromBytes; - -pub(crate) async fn time(fut: impl Future) -> (O, Duration) { - let before = Instant::now(); - let out = fut.await; - (out, before.elapsed()) -} - -pub(crate) struct SyncStats { - pub prefetched_bytes: AtomicU64, - pub prefetched_bytes_discarded_due_to_new_session: AtomicU64, - pub prefetched_bytes_discarded_due_to_consecutive_handshake: AtomicU64, - pub prefetched_bytes_discarded_due_to_invalid_frame_header: AtomicU64, - pub synced_bytes_discarded_due_to_invalid_frame_header: AtomicU64, - pub prefetched_bytes_used: AtomicU64, - pub synced_bytes_used: AtomicU64, - pub snapshot_bytes: AtomicU64, -} - -impl SyncStats { - fn new() -> Self { - Self { - prefetched_bytes: AtomicU64::new(0), - prefetched_bytes_discarded_due_to_new_session: AtomicU64::new(0), - prefetched_bytes_discarded_due_to_consecutive_handshake: AtomicU64::new(0), - prefetched_bytes_discarded_due_to_invalid_frame_header: AtomicU64::new(0), - synced_bytes_discarded_due_to_invalid_frame_header: AtomicU64::new(0), - prefetched_bytes_used: AtomicU64::new(0), - synced_bytes_used: AtomicU64::new(0), - snapshot_bytes: AtomicU64::new(0), - } - } - - fn add_prefetched_bytes(&self, bytes: u64) { - self.prefetched_bytes.fetch_add(bytes, std::sync::atomic::Ordering::SeqCst); - } - - fn add_prefetched_bytes_discarded_due_to_new_session(&self, bytes: u64) { - self.prefetched_bytes_discarded_due_to_new_session.fetch_add(bytes, std::sync::atomic::Ordering::SeqCst); - } - - fn add_prefetched_bytes_discarded_due_to_consecutive_handshake(&self, bytes: u64) { - self.prefetched_bytes_discarded_due_to_consecutive_handshake.fetch_add(bytes, std::sync::atomic::Ordering::SeqCst); - } - - fn add_prefetched_bytes_discarded_due_to_invalid_frame_header(&self, bytes: u64) { - self.prefetched_bytes_discarded_due_to_invalid_frame_header.fetch_add(bytes, std::sync::atomic::Ordering::SeqCst); - } - - fn add_synced_bytes_discarded_due_to_invalid_frame_headear(&self, bytes: u64) { - self.synced_bytes_discarded_due_to_invalid_frame_header.fetch_add(bytes, std::sync::atomic::Ordering::SeqCst); - } - - fn add_prefetched_bytes_used(&self, bytes: u64) { - self.prefetched_bytes_used.fetch_add(bytes, std::sync::atomic::Ordering::SeqCst); - } - - fn add_synced_bytes_used(&self, bytes: u64) { - self.synced_bytes_used.fetch_add(bytes, std::sync::atomic::Ordering::SeqCst); - } - fn add_snapshot_bytes(&self, bytes: u64) { - self.snapshot_bytes.fetch_add(bytes, std::sync::atomic::Ordering::SeqCst); - } -} - -/// A remote replicator client, that pulls frames over RPC -pub struct RemoteClient { - remote: super::client::Client, - meta: WalIndexMeta, - last_received: Option, - session_token: Option, - last_handshake_replication_index: Option, - // the replication log is dirty, reset the meta on next handshake - dirty: bool, - prefetched_batch_log_entries: Option<(Result, Status>, Duration)>, - handshake_latency_sum: Duration, - handshake_latency_count: u128, - frames_latency_sum: Duration, - frames_latency_count: u128, - snapshot_latency_sum: Duration, - snapshot_latency_count: u128, - sync_stats: Arc, -} - -impl RemoteClient { - pub(crate) async fn new(remote: super::client::Client, path: &Path) -> anyhow::Result { - let meta = WalIndexMeta::open_prefixed(path).await?; - Ok(Self { - remote, - last_received: meta.current_frame_no(), - meta, - session_token: None, - dirty: false, - last_handshake_replication_index: None, - prefetched_batch_log_entries: None, - handshake_latency_sum: Duration::default(), - handshake_latency_count: 0, - frames_latency_sum: Duration::default(), - frames_latency_count: 0, - snapshot_latency_sum: Duration::default(), - snapshot_latency_count: 0, - sync_stats: Arc::new(SyncStats::new()), - }) - } - - pub(crate) fn sync_stats(&self) -> Arc { - self.sync_stats.clone() - } - - fn next_offset(&self) -> FrameNo { - match self.last_received { - Some(fno) => fno + 1, - None => 0, - } - } - - fn make_request(&self, req: T) -> tonic::Request { - let mut req = tonic::Request::new(req); - if let Some(token) = self.session_token.clone() { - // SAFETY: we always validate the token - req.metadata_mut().insert(SESSION_TOKEN_KEY, unsafe { - AsciiMetadataValue::from_shared_unchecked(token) - }); - } - - req - } - - pub fn last_handshake_replication_index(&self) -> Option { - self.last_handshake_replication_index - } - - async fn handle_handshake_response( - &mut self, - hello: Result, Status>, - ) -> Result { - let hello = hello?.into_inner(); - verify_session_token(&hello.session_token).map_err(Error::Client)?; - let new_session = self.session_token.as_ref() != Some(&hello.session_token); - self.session_token = Some(hello.session_token.clone()); - let current_replication_index = hello.current_replication_index; - if let Err(e) = self.meta.init_from_hello(hello) { - // set the meta as dirty. The caller should catch the error and clean the db - // file. On the next call to replicate, the db will be replicated from the new - // log. - if let libsql_replication::meta::Error::LogIncompatible = e { - self.dirty = true; - } - - Err(e)?; - } - self.last_handshake_replication_index = current_replication_index; - self.meta.flush().await?; - Ok(new_session) - } - - async fn do_handshake_with_prefetch(&mut self) -> (Result<(), Error>, Duration) { - tracing::info!("Attempting to perform handshake with primary."); - if let Some((Ok(frames), _)) = &self.prefetched_batch_log_entries { - // TODO: check if it's ok to just do 4096 * frames.len() - let bytes = frames.get_ref().frames.iter().map(|f| f.data.len() as u64).sum(); - self.sync_stats.add_prefetched_bytes_discarded_due_to_consecutive_handshake(bytes); - } - if self.dirty { - self.prefetched_batch_log_entries = None; - self.meta.reset(); - self.last_received = self.meta.current_frame_no(); - self.dirty = false; - } - let prefetch = self.session_token.is_some(); - let hello_req = self.make_request(HelloRequest::new()); - let log_offset_req = self.make_request(LogOffset { - next_offset: self.next_offset(), - wal_flavor: None, - }); - let mut client_clone = self.remote.clone(); - let hello_fut = time(async { - let res = self.remote.replication.hello(hello_req).await; - self.handle_handshake_response(res).await - }); - let (hello, frames) = if prefetch { - let (hello, frames) = tokio::join!( - hello_fut, - time(client_clone.replication.batch_log_entries(log_offset_req)) - ); - (hello, Some(frames)) - } else { - (hello_fut.await, None) - }; - let mut prefetched_bytes = None; - if let Some((Ok(frames), _)) = &frames { - let bytes = frames.get_ref().frames.iter().map(|f| f.data.len() as u64).sum(); - self.sync_stats.add_prefetched_bytes(bytes); - prefetched_bytes = Some(bytes); - } - self.prefetched_batch_log_entries = if let Ok(true) = hello.0 { - tracing::debug!( - "Frames prefetching failed because of new session token returned by handshake" - ); - if let Some(bytes) = prefetched_bytes { - self.sync_stats.add_prefetched_bytes_discarded_due_to_new_session(bytes); - } - None - } else { - frames - }; - - (hello.0.map(|_| ()), hello.1) - } - - async fn handle_next_frames_response( - &mut self, - frames: Result, Status>, - prefetched: bool, - ) -> Result<::FrameStream, Error> { - let frames = frames?.into_inner().frames; - let bytes = frames.iter().map(|f| f.data.len() as u64).sum(); - - if let Some(f) = frames.last() { - let header_result = FrameHeader::read_from_prefix(&f.data); - if header_result.is_none() { - if prefetched { - self.sync_stats.add_prefetched_bytes_discarded_due_to_invalid_frame_header(bytes); - } else { - self.sync_stats.add_synced_bytes_discarded_due_to_invalid_frame_headear(bytes); - } - } - let header: FrameHeader = header_result - .ok_or_else(|| Error::Internal("invalid frame header".into()))?; - self.last_received = Some(header.frame_no.get()); - } - - if prefetched { - self.sync_stats.add_prefetched_bytes_used(bytes); - } else { - self.sync_stats.add_synced_bytes_used(bytes); - } - - let frames_iter = frames - .into_iter() - .map(Ok); - - let stream = tokio_stream::iter(frames_iter); - - Ok(Box::pin(stream)) - } - - async fn do_next_frames( - &mut self, - ) -> ( - Result<::FrameStream, Error>, - Duration, - ) { - let ((frames, time), prefetched) = match self.prefetched_batch_log_entries.take() { - Some((result, time)) => ((result, time), true), - None => { - let req = self.make_request(LogOffset { - next_offset: self.next_offset(), - wal_flavor: None, - }); - let result = time(self.remote.replication.batch_log_entries(req)).await; - (result, false) - } - }; - let res = self.handle_next_frames_response(frames, prefetched).await; - (res, time) - } - - async fn do_snapshot(&mut self) -> Result<::FrameStream, Error> { - let req = self.make_request(LogOffset { - next_offset: self.next_offset(), - wal_flavor: None, - }); - let sync_stats = self.sync_stats.clone(); - let mut frames = self - .remote - .replication - .snapshot(req) - .await? - .into_inner() - .map_err(|e| e.into()) - .map_ok(move |f| { - sync_stats.add_snapshot_bytes(f.data.len() as u64); - f - }) - .peekable(); - - { - let frames = Pin::new(&mut frames); - - // the first frame is the one with the highest frame_no in the snapshot - if let Some(Ok(f)) = frames.peek().await { - let header: FrameHeader = FrameHeader::read_from_prefix(&f.data[..]).unwrap(); - self.last_received = Some(header.frame_no.get()); - } - } - - - Ok(Box::pin(frames)) - } -} - -fn maybe_log( - time: Duration, - sum: &mut Duration, - count: &mut u128, - result: &Result, - op_name: &str, -) { - if let Err(e) = &result { - tracing::warn!("Failed {} in {} ms: {:?}", op_name, time.as_millis(), e); - } else { - *sum += time; - *count += 1; - let avg = (*sum).as_millis() / *count; - let time = time.as_millis(); - if *count > 10 && time > 2 * avg { - tracing::warn!( - "Unusually long {}. Took {} ms, average {} ms", - op_name, - time, - avg - ); - } - } -} - -#[async_trait::async_trait] -impl ReplicatorClient for RemoteClient { - type FrameStream = Pin> + Send + 'static>>; - - /// Perform handshake with remote - async fn handshake(&mut self) -> Result<(), Error> { - let (result, time) = self.do_handshake_with_prefetch().await; - maybe_log( - time, - &mut self.handshake_latency_sum, - &mut self.handshake_latency_count, - &result, - "handshake", - ); - result - } - - /// Return a stream of frames to apply to the database - async fn next_frames(&mut self) -> Result { - let (result, time) = self.do_next_frames().await; - maybe_log( - time, - &mut self.frames_latency_sum, - &mut self.frames_latency_count, - &result, - "frames fetch", - ); - result - } - - /// Return a snapshot for the current replication index. Called after next_frame has returned a - /// NeedSnapshot error - async fn snapshot(&mut self) -> Result { - let (snapshot, time) = time(self.do_snapshot()).await; - maybe_log( - time, - &mut self.snapshot_latency_sum, - &mut self.snapshot_latency_count, - &snapshot, - "snapshot fetch", - ); - snapshot - } - - /// set the new commit frame_no - async fn commit_frame_no(&mut self, frame_no: FrameNo) -> Result<(), Error> { - self.meta.set_commit_frame_no(frame_no).await?; - Ok(()) - } - - fn committed_frame_no(&self) -> Option { - self.meta.current_frame_no() - } - - fn rollback(&mut self) { - self.last_received = self.committed_frame_no() - } -} diff --git a/vendor/libsql/src/rows.rs b/vendor/libsql/src/rows.rs deleted file mode 100644 index 39411b83..00000000 --- a/vendor/libsql/src/rows.rs +++ /dev/null @@ -1,302 +0,0 @@ -use crate::{Result, Value, ValueType}; -use std::fmt; - -/// Represents a libsql column. -pub struct Column<'stmt> { - pub(crate) name: &'stmt str, - pub(crate) origin_name: Option<&'stmt str>, - pub(crate) table_name: Option<&'stmt str>, - pub(crate) database_name: Option<&'stmt str>, - pub(crate) decl_type: Option<&'stmt str>, -} - -impl Column<'_> { - /// Returns the name assigned to the column in the result set. - pub fn name(&self) -> &str { - self.name - } - - /// Returns the name of the column in the origin table. - pub fn origin_name(&self) -> Option<&str> { - self.origin_name - } - - /// Returns the name of the origin table. - pub fn table_name(&self) -> Option<&str> { - self.table_name - } - - /// Returns the name of the origin database. - pub fn database_name(&self) -> Option<&str> { - self.database_name - } - - /// Returns the type of the column (`None` for expression). - pub fn decl_type(&self) -> Option<&str> { - self.decl_type - } -} - -#[async_trait::async_trait] -pub(crate) trait RowsInner: ColumnsInner { - async fn next(&mut self) -> Result>; -} - -/// A set of rows returned from a connection. -pub struct Rows { - inner: Box, -} - -impl Rows { - pub(crate) fn new(inner: impl RowsInner + Send + Sync + 'static) -> Self { - Self { - inner: Box::new(inner), - } - } - - /// Get the next [`Row`] returning an error if it failed and - /// `None` if there are no more rows. - #[allow(clippy::should_implement_trait)] - pub async fn next(&mut self) -> Result> { - self.inner.next().await - } - - /// Get the count of columns in this set of rows. - pub fn column_count(&self) -> i32 { - self.inner.column_count() - } - - /// Fetch the name of the column for the provided column index. - pub fn column_name(&self, idx: i32) -> Option<&str> { - self.inner.column_name(idx) - } - - /// Fetch the column type from the provided column index. - pub fn column_type(&self, idx: i32) -> Result { - self.inner.column_type(idx) - } - - /// Converts current [Rows] into asynchronous stream, fetching rows - /// one by one. This stream can be further used with [futures::StreamExt] - /// operators. - #[cfg(feature = "stream")] - pub fn into_stream(mut self) -> impl futures::Stream> { - async_stream::try_stream! { - while let Some(row) = self.next().await? { - yield row - } - } - } -} - -impl fmt::Debug for Rows { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Rows").finish() - } -} - -/// A libsql row. -pub struct Row { - pub(crate) inner: Box, -} - -impl Row { - /// Fetch the value at the provided column index and attempt to - /// convert the value into the provided type `T`. - /// - /// # Example - /// - /// ```rust,no_run - /// # async fn run(row: &libsql::Row) { - /// row.get::(0).unwrap(); - /// # } - /// ``` - pub fn get(&self, idx: i32) -> Result - where - T: FromValue, - { - let val = self.inner.column_value(idx)?; - T::from_sql(val) - } - - /// Fetch the value at the provided column index. - pub fn get_value(&self, idx: i32) -> Result { - self.inner.column_value(idx) - } - - /// Get a `&str` column at the provided index, errors out if the column - /// is not of the `TEXT`. - pub fn get_str(&self, idx: i32) -> Result<&str> { - self.inner.column_str(idx) - } - - /// Get the count of columns in this set of rows. - pub fn column_count(&self) -> i32 { - self.inner.column_count() - } - - /// Fetch the name of the column at the provided index. - pub fn column_name(&self, idx: i32) -> Option<&str> { - self.inner.column_name(idx) - } - - /// Fetch the column type from the provided index. - pub fn column_type(&self, idx: i32) -> Result { - self.inner.column_type(idx) - } -} - -impl fmt::Debug for Row { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> { - self.inner.fmt(f) - } -} - -/// Convert a `Value` into the implementors type. -pub trait FromValue: Sealed { - fn from_sql(val: Value) -> Result - where - Self: Sized; -} - -impl FromValue for crate::Value { - fn from_sql(val: Value) -> Result { - Ok(val) - } -} -impl Sealed for crate::Value {} - -impl FromValue for i32 { - fn from_sql(val: Value) -> Result { - match val { - Value::Null => Err(crate::Error::NullValue), - Value::Integer(i) => Ok(i as i32), - _ => unreachable!("invalid value type"), - } - } -} -impl Sealed for i32 {} - -impl FromValue for u32 { - fn from_sql(val: Value) -> Result { - match val { - Value::Null => Err(crate::Error::NullValue), - Value::Integer(i) => Ok(i as u32), - _ => unreachable!("invalid value type"), - } - } -} -impl Sealed for u32 {} - -impl FromValue for i64 { - fn from_sql(val: Value) -> Result { - match val { - Value::Null => Err(crate::Error::NullValue), - Value::Integer(i) => Ok(i), - _ => unreachable!("invalid value type"), - } - } -} -impl Sealed for i64 {} - -impl FromValue for u64 { - fn from_sql(val: Value) -> Result { - match val { - Value::Null => Err(crate::Error::NullValue), - Value::Integer(i) => Ok(i as u64), - _ => unreachable!("invalid value type"), - } - } -} -impl Sealed for u64 {} - -impl FromValue for f64 { - fn from_sql(val: Value) -> Result { - match val { - Value::Null => Err(crate::Error::NullValue), - Value::Real(f) => Ok(f), - _ => unreachable!("invalid value type"), - } - } -} -impl Sealed for f64 {} - -impl FromValue for Vec { - fn from_sql(val: Value) -> Result { - match val { - Value::Null => Err(crate::Error::NullValue), - Value::Blob(blob) => Ok(blob), - _ => unreachable!("invalid value type"), - } - } -} -impl Sealed for Vec {} - -impl FromValue for [u8; N] { - fn from_sql(val: Value) -> Result { - match val { - Value::Null => Err(crate::Error::NullValue), - Value::Blob(blob) => blob - .try_into() - .map_err(|_| crate::Error::InvalidBlobSize(N)), - _ => unreachable!("invalid value type"), - } - } -} -impl Sealed for [u8; N] {} - -impl FromValue for String { - fn from_sql(val: Value) -> Result { - match val { - Value::Null => Err(crate::Error::NullValue), - Value::Text(s) => Ok(s), - _ => unreachable!("invalid value type"), - } - } -} -impl Sealed for String {} - -impl FromValue for bool { - fn from_sql(val: Value) -> Result { - match val { - Value::Null => Err(crate::Error::NullValue), - Value::Integer(i) => match i { - 0 => Ok(false), - 1 => Ok(true), - _ => Err(crate::Error::InvalidColumnType), - }, - _ => unreachable!("invalid value type"), - } - } -} -impl Sealed for bool {} - -impl FromValue for Option -where - T: FromValue, -{ - fn from_sql(val: Value) -> Result { - match val { - Value::Null => Ok(None), - _ => T::from_sql(val).map(Some), - } - } -} -impl Sealed for Option {} - -pub(crate) trait ColumnsInner { - fn column_name(&self, idx: i32) -> Option<&str>; - fn column_type(&self, idx: i32) -> Result; - fn column_count(&self) -> i32; -} - -pub(crate) trait RowInner: ColumnsInner + fmt::Debug { - fn column_value(&self, idx: i32) -> Result; - fn column_str(&self, idx: i32) -> Result<&str>; -} - -mod sealed { - pub trait Sealed {} -} - -use sealed::Sealed; diff --git a/vendor/libsql/src/statement.rs b/vendor/libsql/src/statement.rs deleted file mode 100644 index 861fdf80..00000000 --- a/vendor/libsql/src/statement.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::params::IntoParams; -use crate::params::Params; -pub use crate::Column; -use crate::{Error, Result}; -use crate::{Row, Rows}; - -#[async_trait::async_trait] -pub(crate) trait Stmt { - fn finalize(&mut self); - - async fn execute(&self, params: &Params) -> Result; - - async fn query(&self, params: &Params) -> Result; - - async fn run(&self, params: &Params) -> Result<()>; - - fn interrupt(&self) -> Result<()>; - - fn reset(&self); - - fn parameter_count(&self) -> usize; - - fn parameter_name(&self, idx: i32) -> Option<&str>; - - fn column_count(&self) -> usize; - - fn columns(&self) -> Vec; -} - -/// A cached prepared statement. -pub struct Statement { - pub(crate) inner: Box, -} - -impl Statement { - /// Finalize the cached statement. - pub fn finalize(&mut self) { - self.inner.finalize(); - } - - /// Execute queries on the statement, check [`Connection::execute`] for usage. - pub async fn execute(&self, params: impl IntoParams) -> Result { - tracing::trace!("execute for prepared statement"); - self.inner.execute(¶ms.into_params()?).await - } - - /// Execute a query on the statement, check [`Connection::query`] for usage. - pub async fn query(&self, params: impl IntoParams) -> Result { - tracing::trace!("query for prepared statement"); - self.inner.query(¶ms.into_params()?).await - } - - /// Run a query on the statement. - /// - /// The `execute()` method returns an error if the query returns rows, which makes - /// it unsuitable for running any type of SQL queries. Similarly, the `query()` method - /// only works on SQL statements that return rows. Therefore, the `run()` method is - /// provided to execute any type of SQL statement. - /// - /// Note: This is an extension to the Rusqlite API. - pub async fn run(&self, params: impl IntoParams) -> Result<()> { - tracing::trace!("run for prepared statement"); - self.inner.run(¶ms.into_params()?).await?; - Ok(()) - } - - /// Interrupt the statement. - pub fn interrupt(&self) -> Result<()> { - self.inner.interrupt() - } - - /// Execute a query that returns the first [`Row`]. - /// - /// # Errors - /// - /// - Returns `QueryReturnedNoRows` if no rows were returned. - pub async fn query_row(&mut self, params: impl IntoParams) -> Result { - let mut rows = self.query(params).await?; - - let row = rows.next().await?.ok_or(Error::QueryReturnedNoRows)?; - - Ok(row) - } - - /// Reset the state of this prepared statement. - pub fn reset(&self) { - self.inner.reset(); - } - - /// Fetch the amount of parameters in the prepared statement. - pub fn parameter_count(&self) -> usize { - self.inner.parameter_count() - } - - /// Fetch the parameter name at the provided index. - pub fn parameter_name(&self, idx: i32) -> Option<&str> { - self.inner.parameter_name(idx) - } - - /// Fetch the number of columns for the prepared statement. - pub fn column_count(&self) -> usize { - self.inner.column_count() - } - - /// Fetch the list of columns for the prepared statement. - pub fn columns(&self) -> Vec { - self.inner.columns() - } -} diff --git a/vendor/libsql/src/sync.rs b/vendor/libsql/src/sync.rs deleted file mode 100644 index 9f91779e..00000000 --- a/vendor/libsql/src/sync.rs +++ /dev/null @@ -1,1057 +0,0 @@ -use crate::{local::Connection, util::ConnectorService, Error, Result}; - -use crate::database::EncryptionContext; -use bytes::Bytes; -use chrono::Utc; -use http::{HeaderValue, StatusCode}; -use hyper::Body; -use std::path::Path; -use tokio::io::AsyncWriteExt as _; -use uuid::Uuid; -use zerocopy::big_endian; - -#[cfg(test)] -mod test; - -pub mod connection; -pub mod statement; -pub mod transaction; - -const METADATA_VERSION: u32 = 0; - -const DEFAULT_MAX_RETRIES: usize = 5; -const DEFAULT_PUSH_BATCH_SIZE: u32 = 128; -const DEFAULT_PULL_BATCH_SIZE: u32 = 128; - -#[derive(thiserror::Error, Debug)] -#[non_exhaustive] -pub enum SyncError { - #[error("io: msg={msg}, err={err}")] - Io { - msg: &'static str, - #[source] - err: std::io::Error, - }, - #[error("invalid auth header: {0}")] - InvalidAuthHeader(http::header::InvalidHeaderValue), - #[error("http dispatch error: {0}")] - HttpDispatch(hyper::Error), - #[error("body error: {0}")] - HttpBody(hyper::Error), - #[error("json decode error: {0}")] - JsonDecode(serde_json::Error), - #[error("json value error, unexpected value: {0}")] - JsonValue(serde_json::Value), - #[error("json encode error: {0}")] - JsonEncode(serde_json::Error), - #[error("failed to push frame: status={0}, error={1}")] - PushFrame(StatusCode, String), - #[error("failed to verify metadata file version: expected={0}, got={1}")] - VerifyVersion(u32, u32), - #[error("failed to verify metadata file hash: expected={0}, got={1}")] - VerifyHash(u32, u32), - #[error("server returned a lower frame_no: sent={0}, got={1}")] - InvalidPushFrameNoLow(u32, u32), - #[error("server returned a higher frame_no: sent={0}, got={1}")] - InvalidPushFrameNoHigh(u32, u32), - #[error("server returned a conflict: sent={0}, got={1}")] - InvalidPushFrameConflict(u32, u32), - #[error("failed to pull frame: status={0}, error={1}")] - PullFrame(StatusCode, String), - #[error("failed to get location header for redirect: {0}")] - RedirectHeader(http::header::ToStrError), - #[error("redirect response with no location header")] - NoRedirectLocationHeader, - #[error("failed to pull db export: status={0}, error={1}")] - PullDb(StatusCode, String), - #[error("server returned a lower generation than local: local={0}, remote={1}")] - InvalidLocalGeneration(u32, u32), - #[error("invalid local state: {0}")] - InvalidLocalState(String), - #[error("invalid remote state: {0}")] - InvalidRemoteState(String), - #[error("server returned invalid length of frames: {0}")] - InvalidPullFrameBytes(usize), -} - -impl SyncError { - fn io(msg: &'static str) -> impl FnOnce(std::io::Error) -> SyncError { - move |err| SyncError::Io { msg, err } - } -} - -pub struct PushResult { - status: PushStatus, - generation: u32, - max_frame_no: u32, - baton: Option, -} - -pub struct DropAbort(pub Option>); - -impl Drop for DropAbort { - fn drop(&mut self) { - tracing::debug!("aborting"); - if let Some(sender) = self.0.take() { - let _ = sender.send(()); - } - } -} - -pub enum PushStatus { - Ok, - Conflict, -} - -pub enum PullResult { - /// Frames were successfully pulled. - Frames(Bytes), - /// We've reached the end of the generation. - EndOfGeneration { max_generation: u32 }, -} - -#[derive(serde::Deserialize)] -struct InfoResult { - current_generation: u32, -} - -#[derive(Debug)] -struct PushFramesResult { - max_frame_no: u32, - baton: Option, -} - -pub struct SyncContext { - db_path: String, - client: hyper::Client, - sync_url: String, - auth_token: Option, - max_retries: usize, - push_batch_size: u32, - pull_batch_size: u32, - /// The current durable generation. - durable_generation: u32, - /// Represents the max_frame_no from the server. - durable_frame_num: u32, - /// whenever sync is called very first time, we will call the remote server - /// to get the generation information and sync the db file if needed - initial_server_sync: bool, - /// The encryption context for the sync. - remote_encryption: Option, -} - -impl SyncContext { - pub async fn new( - connector: ConnectorService, - db_path: String, - sync_url: String, - auth_token: Option, - remote_encryption: Option, - ) -> Result { - let client = hyper::client::Client::builder().build::<_, hyper::Body>(connector); - - let auth_token = match auth_token { - Some(t) => Some( - HeaderValue::try_from(format!("Bearer {}", t)) - .map_err(SyncError::InvalidAuthHeader)?, - ), - None => None, - }; - - let mut me = Self { - db_path, - sync_url, - auth_token, - max_retries: DEFAULT_MAX_RETRIES, - push_batch_size: DEFAULT_PUSH_BATCH_SIZE, - pull_batch_size: DEFAULT_PULL_BATCH_SIZE, - client, - durable_generation: 0, - durable_frame_num: 0, - initial_server_sync: false, - remote_encryption, - }; - me.read_metadata().await?; - Ok(me) - } - - pub fn set_push_batch_size(&mut self, push_batch_size: u32) { - self.push_batch_size = push_batch_size; - } - - #[tracing::instrument(skip(self))] - pub(crate) async fn pull_frames( - &mut self, - generation: u32, - frame_no: u32, - ) -> Result { - let uri = format!( - "{}/sync/{}/{}/{}", - self.sync_url, - generation, - frame_no, - // the server expects the range of [start, end) frames, i.e. end is exclusive - frame_no + self.pull_batch_size - ); - tracing::debug!("pulling frame (uri={})", uri); - self.pull_with_retry(uri, self.max_retries).await - } - - #[tracing::instrument(skip(self, frames))] - pub(crate) async fn push_frames( - &mut self, - frames: Bytes, - generation: u32, - frame_no: u32, - frames_count: u32, - baton: Option, - ) -> Result { - let uri = { - let mut uri = format!( - "{}/sync/{}/{}/{}", - self.sync_url, - generation, - frame_no, - frame_no + frames_count - ); - if let Some(ref baton) = baton { - uri.push_str(&format!("/{}", baton)); - } - uri - }; - - tracing::debug!( - "pushing frame(frame_no={} (to={}), count={}, generation={}, baton={:?})", - frame_no, - frame_no + frames_count, - frames_count, - generation, - baton - ); - - let result = self.push_with_retry(uri, frames, self.max_retries).await?; - - match result.status { - PushStatus::Conflict => { - return Err( - SyncError::InvalidPushFrameConflict(frame_no, result.max_frame_no).into(), - ); - } - _ => {} - } - let generation = result.generation; - let durable_frame_num = result.max_frame_no; - let baton = result.baton; - - if durable_frame_num > frame_no + frames_count - 1 { - tracing::error!( - "server returned durable_frame_num larger than what we sent: sent={}, got={}", - frame_no, - durable_frame_num - ); - - return Err(SyncError::InvalidPushFrameNoHigh(frame_no, durable_frame_num).into()); - } - - if durable_frame_num < frame_no + frames_count - 1 { - // Update our knowledge of where the server is at frame wise. - self.durable_frame_num = durable_frame_num; - - tracing::debug!( - "server returned durable_frame_num lower than what we sent: sent={}, got={}", - frame_no, - durable_frame_num - ); - - // Return an error and expect the caller to re-call push with the updated state. - return Err(SyncError::InvalidPushFrameNoLow(frame_no, durable_frame_num).into()); - } - - tracing::debug!(?durable_frame_num, "frame successfully pushed"); - - // Update our last known max_frame_no from the server. - tracing::debug!( - ?generation, - ?durable_frame_num, - "updating remote generation and durable_frame_num" - ); - self.durable_generation = generation; - self.durable_frame_num = durable_frame_num; - - Ok(PushFramesResult { - max_frame_no: durable_frame_num, - baton, - }) - } - - async fn push_with_retry( - &self, - mut uri: String, - body: Bytes, - max_retries: usize, - ) -> Result { - let mut nr_retries = 0; - loop { - let mut req = http::Request::post(uri.clone()); - - match &self.auth_token { - Some(auth_token) => { - req.headers_mut() - .expect("valid http request") - .insert("Authorization", auth_token.clone()); - } - None => {} - } - - if let Some(remote_encryption) = &self.remote_encryption { - req = req.header("x-turso-encryption-key", remote_encryption.key.as_string()); - } - - let req = req.body(body.clone().into()).expect("valid body"); - - let res = self - .client - .request(req) - .await - .map_err(SyncError::HttpDispatch)?; - - if res.status().is_success() { - let res_body = hyper::body::to_bytes(res.into_body()) - .await - .map_err(SyncError::HttpBody)?; - - let resp = serde_json::from_slice::(&res_body[..]) - .map_err(SyncError::JsonDecode)?; - - let status = resp - .get("status") - .ok_or_else(|| SyncError::JsonValue(resp.clone()))?; - - let status = status - .as_str() - .ok_or_else(|| SyncError::JsonValue(status.clone()))?; - - let generation = resp - .get("generation") - .ok_or_else(|| SyncError::JsonValue(resp.clone()))?; - - let generation = generation - .as_u64() - .ok_or_else(|| SyncError::JsonValue(generation.clone()))?; - - let max_frame_no = resp - .get("max_frame_no") - .ok_or_else(|| SyncError::JsonValue(resp.clone()))?; - - let max_frame_no = max_frame_no - .as_u64() - .ok_or_else(|| SyncError::JsonValue(max_frame_no.clone()))?; - - let baton = resp - .get("baton") - .and_then(|v| v.as_str()) - .map(|s| s.to_string()); - - tracing::trace!( - ?baton, - ?generation, - ?max_frame_no, - ?status, - "pushed frame to server" - ); - - let status = match status { - "ok" => PushStatus::Ok, - "conflict" => PushStatus::Conflict, - _ => return Err(SyncError::JsonValue(resp.clone()).into()), - }; - let generation = generation as u32; - let max_frame_no = max_frame_no as u32; - return Ok(PushResult { - status, - generation, - max_frame_no, - baton, - }); - } - - if res.status().is_redirection() { - uri = match res.headers().get(hyper::header::LOCATION) { - Some(loc) => loc.to_str().map_err(SyncError::RedirectHeader)?.to_string(), - None => return Err(SyncError::NoRedirectLocationHeader.into()), - }; - if nr_retries == 0 { - nr_retries += 1; - continue; - } - } - - // If we've retried too many times or the error is not a server error, - // return the error. - if nr_retries > max_retries || !res.status().is_server_error() { - let status = res.status(); - - let res_body = hyper::body::to_bytes(res.into_body()) - .await - .map_err(SyncError::HttpBody)?; - - let msg = String::from_utf8_lossy(&res_body[..]); - - return Err(SyncError::PushFrame(status, msg.to_string()).into()); - } - - let delay = std::time::Duration::from_millis(100 * (1 << nr_retries)); - tokio::time::sleep(delay).await; - nr_retries += 1; - } - } - - async fn pull_with_retry(&self, mut uri: String, max_retries: usize) -> Result { - let mut nr_retries = 0; - loop { - let mut req = http::Request::builder().method("GET").uri(uri.clone()); - - match &self.auth_token { - Some(auth_token) => { - req = req.header("Authorization", auth_token); - } - None => {} - } - - if let Some(remote_encryption) = &self.remote_encryption { - req = req.header("x-turso-encryption-key", remote_encryption.key.as_string()); - } - - let req = req.body(Body::empty()).expect("valid request"); - - let res = self - .client - .request(req) - .await - .map_err(SyncError::HttpDispatch)?; - - if res.status().is_success() { - let frames = hyper::body::to_bytes(res.into_body()) - .await - .map_err(SyncError::HttpBody)?; - // a success result should always return some frames - if frames.is_empty() { - tracing::error!("server returned empty frames in pull response"); - return Err(SyncError::InvalidPullFrameBytes(0).into()); - } - // the minimum payload size cannot be less than a single frame - if frames.len() < FRAME_SIZE { - tracing::error!( - "server returned frames with invalid length: {} < {}", - frames.len(), - FRAME_SIZE - ); - return Err(SyncError::InvalidPullFrameBytes(frames.len()).into()); - } - return Ok(PullResult::Frames(frames)); - } - // BUG ALERT: The server returns a 500 error if the remote database is empty. - // This is a bug and should be fixed. - if res.status() == StatusCode::BAD_REQUEST - || res.status() == StatusCode::INTERNAL_SERVER_ERROR - { - let status = res.status(); - let res_body = hyper::body::to_bytes(res.into_body()) - .await - .map_err(SyncError::HttpBody)?; - tracing::trace!( - "server returned: {} body: {}", - status, - String::from_utf8_lossy(&res_body[..]) - ); - let resp = serde_json::from_slice::(&res_body[..]) - .map_err(SyncError::JsonDecode)?; - - let generation = resp - .get("generation") - .ok_or_else(|| SyncError::JsonValue(resp.clone()))?; - - let generation = generation - .as_u64() - .ok_or_else(|| SyncError::JsonValue(generation.clone()))?; - return Ok(PullResult::EndOfGeneration { - max_generation: generation as u32, - }); - } - if res.status().is_redirection() { - uri = match res.headers().get(hyper::header::LOCATION) { - Some(loc) => loc.to_str().map_err(SyncError::RedirectHeader)?.to_string(), - None => return Err(SyncError::NoRedirectLocationHeader.into()), - }; - if nr_retries == 0 { - nr_retries += 1; - continue; - } - } - // If we've retried too many times or the error is not a server error, - // return the error. - if nr_retries > max_retries || !res.status().is_server_error() { - let status = res.status(); - - let res_body = hyper::body::to_bytes(res.into_body()) - .await - .map_err(SyncError::HttpBody)?; - - let msg = String::from_utf8_lossy(&res_body[..]); - - return Err(SyncError::PullFrame(status, msg.to_string()).into()); - } - - let delay = std::time::Duration::from_millis(100 * (1 << nr_retries)); - tokio::time::sleep(delay).await; - nr_retries += 1; - } - } - - pub(crate) fn next_generation(&mut self) { - self.durable_generation += 1; - self.durable_frame_num = 0; - } - - pub(crate) fn durable_frame_num(&self) -> u32 { - self.durable_frame_num - } - - pub(crate) fn durable_generation(&self) -> u32 { - self.durable_generation - } - - pub(crate) async fn write_metadata(&mut self) -> Result<()> { - let path = format!("{}-info", self.db_path); - - assert!(self.durable_generation > 0); - - let mut metadata = MetadataJson { - hash: 0, - version: METADATA_VERSION, - durable_frame_num: self.durable_frame_num, - generation: self.durable_generation, - }; - - metadata.set_hash(); - - let contents = serde_json::to_vec(&metadata).map_err(SyncError::JsonEncode)?; - - atomic_write(path, &contents[..]).await?; - - Ok(()) - } - - async fn read_metadata(&mut self) -> Result<()> { - let path = format!("{}-info", self.db_path); - - if !Path::new(&path) - .try_exists() - .map_err(SyncError::io("metadata file exists"))? - { - tracing::debug!("no metadata info file found"); - return Ok(()); - } - - let contents = tokio::fs::read(&path) - .await - .map_err(SyncError::io("metadata read"))?; - - let metadata = - serde_json::from_slice::(&contents[..]).map_err(SyncError::JsonDecode)?; - - metadata.verify_hash()?; - - if metadata.version != METADATA_VERSION { - return Err(SyncError::VerifyVersion(metadata.version, METADATA_VERSION).into()); - } - - tracing::debug!( - "read sync metadata for db_path={:?}, metadata={:?}", - self.db_path, - metadata - ); - - if metadata.generation == 0 { - return Err(SyncError::InvalidLocalState("generation is 0".to_string()).into()); - } - - self.durable_generation = metadata.generation; - self.durable_frame_num = metadata.durable_frame_num; - - Ok(()) - } - - /// get_remote_info calls the remote server to get the current generation information. - async fn get_remote_info(&self) -> Result { - let uri = format!("{}/info", self.sync_url); - let mut req = http::Request::builder().method("GET").uri(&uri); - - if let Some(auth_token) = &self.auth_token { - req = req.header("Authorization", auth_token); - } - - if let Some(remote_encryption) = &self.remote_encryption { - req = req.header("x-turso-encryption-key", remote_encryption.key.as_string()); - } - - let req = req.body(Body::empty()).expect("valid request"); - - let res = self - .client - .request(req) - .await - .map_err(SyncError::HttpDispatch)?; - - if !res.status().is_success() { - let status = res.status(); - let body = hyper::body::to_bytes(res.into_body()) - .await - .map_err(SyncError::HttpBody)?; - return Err( - SyncError::PullDb(status, String::from_utf8_lossy(&body).to_string()).into(), - ); - } - - let body = hyper::body::to_bytes(res.into_body()) - .await - .map_err(SyncError::HttpBody)?; - - let info: InfoResult = serde_json::from_slice(&body).map_err(SyncError::JsonDecode)?; - if info.current_generation == 0 { - return Err(SyncError::InvalidRemoteState("generation is 0".to_string()).into()); - } - Ok(info) - } - - async fn sync_db_if_needed(&mut self) -> Result<()> { - let db_file_exists = check_if_file_exists(&self.db_path)?; - let metadata_exists = check_if_file_exists(&format!("{}-info", self.db_path))?; - if db_file_exists && metadata_exists { - return Ok(()); - } - let info = self.get_remote_info().await?; - let generation = info.current_generation; - // somehow we are ahead of the remote in generations. following should not happen because - // we checkpoint only if the remote server tells us to do so. - if self.durable_generation > generation { - tracing::error!( - "server returned a lower generation than what we have: local={}, remote={}", - self.durable_generation, - generation - ); - return Err( - SyncError::InvalidLocalGeneration(self.durable_generation, generation).into(), - ); - } - // we use the following heuristic to determine if we need to sync the db file - // 1. if no db file or the metadata file exists, then user is starting from scratch - // and we will do the sync - // 2. if the db file exists, but the metadata file does not exist (or other way around), - // then local db is in an incorrect state. we stop and return with an error - // 3. if the db file exists and the metadata file exists, then we don't need to do the - // sync - match (metadata_exists, db_file_exists) { - (false, false) => { - // neither the db file nor the metadata file exists, lets bootstrap from remote - tracing::debug!( - "syncing db file from remote server, generation={}", - generation - ); - self.sync_db(generation).await - } - (false, true) => { - // inconsistent state: DB exists but metadata missing - tracing::error!( - "local state is incorrect, db file exists but metadata file does not" - ); - Err(SyncError::InvalidLocalState( - "db file exists but metadata file does not".to_string(), - ) - .into()) - } - (true, false) => { - // inconsistent state: Metadata exists but DB missing - tracing::error!( - "local state is incorrect, metadata file exists but db file does not" - ); - Err(SyncError::InvalidLocalState( - "metadata file exists but db file does not".to_string(), - ) - .into()) - } - (true, true) => { - // We already handled this case earlier in the function. - unreachable!(); - } - } - } - - /// sync_db will download the db file from the remote server and replace the local file. - async fn sync_db(&mut self, generation: u32) -> Result<()> { - let uri = format!("{}/export/{}", self.sync_url, generation); - let mut req = http::Request::builder().method("GET").uri(&uri); - - if let Some(auth_token) = &self.auth_token { - req = req.header("Authorization", auth_token); - } - - if let Some(remote_encryption) = &self.remote_encryption { - req = req.header("x-turso-encryption-key", remote_encryption.key.as_string()); - } - - let req = req.body(Body::empty()).expect("valid request"); - - let (res, http_duration) = - crate::replication::remote_client::time(self.client.request(req)).await; - let res = res.map_err(SyncError::HttpDispatch)?; - - if !res.status().is_success() { - let status = res.status(); - let body = hyper::body::to_bytes(res.into_body()) - .await - .map_err(SyncError::HttpBody)?; - tracing::error!( - "failed to pull db file from remote server, status={}, body={}, url={}, duration={:?}", - status, - String::from_utf8_lossy(&body), - uri, - http_duration - ); - return Err( - SyncError::PullFrame(status, String::from_utf8_lossy(&body).to_string()).into(), - ); - } - - tracing::debug!( - "pulled db file from remote server, status={}, url={}, duration={:?}", - res.status(), - uri, - http_duration - ); - - // todo: do streaming write to the disk - let bytes = hyper::body::to_bytes(res.into_body()) - .await - .map_err(SyncError::HttpBody)?; - - atomic_write(&self.db_path, &bytes).await?; - self.durable_generation = generation; - self.durable_frame_num = 0; - self.write_metadata().await?; - Ok(()) - } -} - -#[derive(serde::Serialize, serde::Deserialize, Debug)] -struct MetadataJson { - hash: u32, - version: u32, - durable_frame_num: u32, - generation: u32, -} - -impl MetadataJson { - fn calculate_hash(&self) -> u32 { - let mut hasher = crc32fast::Hasher::new(); - - // Hash each field in a consistent order - hasher.update(&self.version.to_le_bytes()); - hasher.update(&self.durable_frame_num.to_le_bytes()); - hasher.update(&self.generation.to_le_bytes()); - - hasher.finalize() - } - - fn set_hash(&mut self) { - self.hash = self.calculate_hash(); - } - - fn verify_hash(&self) -> Result<()> { - let calculated_hash = self.calculate_hash(); - - if self.hash == calculated_hash { - Ok(()) - } else { - Err(SyncError::VerifyHash(self.hash, calculated_hash).into()) - } - } -} - -async fn atomic_write>(path: P, data: &[u8]) -> Result<()> { - // Create a temporary file in the same directory as the target file - let directory = path.as_ref().parent().ok_or_else(|| { - SyncError::io("parent path")(std::io::Error::other( - "unable to get parent of the provided path", - )) - })?; - - let timestamp = Utc::now().format("%Y%m%d_%H%M%S"); - let temp_name = format!(".tmp.{}.{}", timestamp, Uuid::new_v4()); - let temp_path = directory.join(temp_name); - - // Write data to temporary file - let mut temp_file = tokio::fs::File::create(&temp_path) - .await - .map_err(SyncError::io("temp file create"))?; - - temp_file - .write_all(data) - .await - .map_err(SyncError::io("temp file write_all"))?; - - // Ensure all data is flushed to disk - temp_file - .sync_all() - .await - .map_err(SyncError::io("temp file sync_all"))?; - - // Close the file explicitly - drop(temp_file); - - // Atomically rename temporary file to target file - tokio::fs::rename(&temp_path, &path) - .await - .map_err(SyncError::io("atomic rename"))?; - - Ok(()) -} - -/// bootstrap_db brings the .db file from remote, if required. If the .db file already exists, then -/// it does nothing. Calling this function multiple times is safe. -/// However, make sure there are no existing active connections to the db file as this method can -/// replace it -pub async fn bootstrap_db(sync_ctx: &mut SyncContext) -> Result<()> { - // todo: we are checking with the remote server only during initialisation. ideally, - // we need to do this when we notice a large gap in generations, when bootstrapping is cheaper - // than pulling each frame - if !sync_ctx.initial_server_sync { - sync_ctx.sync_db_if_needed().await?; - // when sync_ctx is initialised, we set durable_generation to 0. however, once - // sync_db is called, it should be > 0. - assert!(sync_ctx.durable_generation > 0, "generation should be > 0"); - sync_ctx.initial_server_sync = true; - } - Ok(()) -} - -/// Sync WAL frames to remote. -pub async fn sync_offline( - sync_ctx: &mut SyncContext, - conn: &Connection, -) -> Result { - if is_ahead_of_remote(&sync_ctx, &conn) { - match try_push(sync_ctx, conn).await { - Ok(rep) => Ok(rep), - Err(Error::Sync(err)) => { - // Retry the sync because we are ahead of the server and we need to push some older - // frames. - if let Some(SyncError::InvalidPushFrameNoLow(_, _)) = err.downcast_ref() { - tracing::debug!("got InvalidPushFrameNo, retrying push"); - try_push(sync_ctx, conn).await - } else { - Err(Error::Sync(err)) - } - } - Err(e) => Err(e), - } - } else { - try_pull(sync_ctx, conn).await - } - .or_else(|err| { - let Error::Sync(err) = err else { - return Err(err); - }; - - // TODO(levy): upcasting should be done *only* at the API boundary, doing this in - // internal code just sucks. - let Some(SyncError::HttpDispatch(_)) = err.downcast_ref() else { - return Err(Error::Sync(err)); - }; - - Ok(crate::database::Replicated { - frame_no: None, - frames_synced: 0, - }) - }) -} - -fn is_ahead_of_remote(sync_ctx: &SyncContext, conn: &Connection) -> bool { - let max_local_frame = conn.wal_frame_count(); - max_local_frame > sync_ctx.durable_frame_num() -} - -async fn try_push( - sync_ctx: &mut SyncContext, - conn: &Connection, -) -> Result { - let page_size = { - let rows = conn - .query("PRAGMA page_size", crate::params::Params::None)? - .unwrap(); - let row = rows.next()?.unwrap(); - let page_size = row.get::(0)?; - page_size - }; - - let max_frame_no = conn.wal_frame_count(); - if max_frame_no == 0 { - return Ok(crate::database::Replicated { - frame_no: None, - frames_synced: 0, - }); - } - - let generation = sync_ctx.durable_generation(); - let start_frame_no = sync_ctx.durable_frame_num() + 1; - let end_frame_no = max_frame_no; - let mut baton = None; - - let mut frame_no = start_frame_no; - while frame_no <= end_frame_no { - let batch_size = sync_ctx.push_batch_size.min(end_frame_no - frame_no + 1); - let mut frames = conn.wal_get_frame(frame_no, page_size)?; - if batch_size > 1 { - frames.reserve((batch_size - 1) as usize * frames.len()); - } - for idx in 1..batch_size { - let frame = conn.wal_get_frame(frame_no + idx, page_size)?; - frames.extend_from_slice(frame.as_ref()) - } - - // The server returns its maximum frame number. To avoid resending - // frames the server already knows about, we need to update the - // frame number to the one returned by the server. - let result = sync_ctx - .push_frames(frames.freeze(), generation, frame_no, batch_size, baton) - .await?; - // if the server sent us a baton, then we will reuse it for the next request - baton = result.baton; - let max_frame_no = result.max_frame_no; - - if max_frame_no > frame_no { - frame_no = max_frame_no + 1; - } else { - frame_no += batch_size; - } - } - - sync_ctx.write_metadata().await?; - - // TODO(lucio): this can underflow if the server previously returned a higher max_frame_no - // than what we have stored here. - let frame_count = end_frame_no - start_frame_no + 1; - Ok(crate::database::Replicated { - frame_no: None, - frames_synced: frame_count as usize, - }) -} - -/// PAGE_SIZE used by the sync / diskless server -const PAGE_SIZE: usize = 4096; -const FRAME_HEADER_SIZE: usize = 24; -const FRAME_SIZE: usize = PAGE_SIZE + FRAME_HEADER_SIZE; - -pub async fn try_pull( - sync_ctx: &mut SyncContext, - conn: &Connection, -) -> Result { - // note, that updates of durable_frame_num are valid only after SQLite commited the WAL - // (because if WAL has uncommited suffix - it will be omitted by any other SQLite connection - for example after restart) - // so, try_pull maintains local next_frame_no during the pull operation and update durable_frame_num when it's appropriate - let mut next_frame_no = sync_ctx.durable_frame_num + 1; - - // libsql maintain consistent state about WAL sync session locally in the insert_handle - // note, that insert_handle will always close the session on drop - so we never keep active WAL session after we exit from the method - let insert_handle = conn.wal_insert_handle(); - - loop { - // get current generation (it may be updated multiple times during execution) - let generation = sync_ctx.durable_generation(); - - match sync_ctx.pull_frames(generation, next_frame_no).await { - Ok(PullResult::Frames(frames)) => { - tracing::debug!( - "pull_frames: generation={}, start_frame={} (end_frame={}, batch_size={}), frames_size={}", - generation, next_frame_no, next_frame_no + sync_ctx.pull_batch_size, sync_ctx.pull_batch_size, frames.len(), - ); - if frames.len() % FRAME_SIZE != 0 { - tracing::error!( - "frame size {} is not a multiple of the expected size {}", - frames.len(), - FRAME_SIZE, - ); - return Err(SyncError::InvalidPullFrameBytes(frames.len()).into()); - } - for chunk in frames.chunks(FRAME_SIZE) { - let mut size_after_buf = [0u8; 4]; - size_after_buf.copy_from_slice(&chunk[4..8]); - let size_after = big_endian::U32::from_bytes(size_after_buf); - // start WAL sync session if it was closed - // (this can happen if on previous iteration client received commit frame) - if !insert_handle.in_session() { - tracing::debug!( - "pull_frames: generation={}, frame={}, start wal transaction session", - generation, - next_frame_no - ); - insert_handle.begin()?; - } - let result = insert_handle.insert_at(next_frame_no, &chunk); - if let Err(e) = result { - tracing::error!("insert error (frame={}) : {:?}", next_frame_no, e); - return Err(e); - } - // if this is commit frame - we can close WAL sync session and update durable_frame_num - if size_after.get() > 0 { - tracing::debug!( - "pull_frames: generation={}, frame={}, finish wal transaction session, size_after={}", - generation, - next_frame_no, - size_after.get() - ); - insert_handle.end()?; - sync_ctx.durable_frame_num = next_frame_no; - sync_ctx.write_metadata().await?; - } - - next_frame_no += 1; - } - } - Ok(PullResult::EndOfGeneration { max_generation }) => { - // If there are no more generations to pull, we're done. - if generation >= max_generation { - break; - } - assert!( - !insert_handle.in_session(), - "WAL transaction must be finished" - ); - - tracing::debug!( - "pull_frames: generation={}, frame={}, checkpoint in order to move to next generation", - generation, - next_frame_no - ); - // TODO: Make this crash-proof. - conn.wal_checkpoint(true)?; - - sync_ctx.next_generation(); - sync_ctx.write_metadata().await?; - next_frame_no = 1; - } - Err(e) => return Err(e), - } - } - - Ok(crate::database::Replicated { - frame_no: None, - frames_synced: 1, - }) -} - -fn check_if_file_exists(path: &str) -> core::result::Result { - Path::new(&path) - .try_exists() - .map_err(SyncError::io("metadata file exists")) -} diff --git a/vendor/libsql/src/sync/connection.rs b/vendor/libsql/src/sync/connection.rs deleted file mode 100644 index 4337361c..00000000 --- a/vendor/libsql/src/sync/connection.rs +++ /dev/null @@ -1,200 +0,0 @@ -use crate::{ - connection::Conn, - hrana::{connection::HttpConnection, hyper::HttpSender}, - local::{self, impls::LibsqlStmt}, - params::Params, - parser, - replication::connection::State, - sync::SyncContext, - BatchRows, Error, Result, Statement, Transaction, TransactionBehavior, -}; -use std::sync::Arc; -use std::time::Duration; -use tokio::sync::Mutex; - -use super::{statement::SyncedStatement, transaction::SyncedTx}; - -#[derive(Clone)] -pub struct SyncedConnection { - pub remote: HttpConnection, - pub local: local::Connection, - pub read_your_writes: bool, - pub context: Arc>, - pub state: Arc>, -} - -impl SyncedConnection { - async fn should_execute_local(&self, sql: &str) -> Result { - let stmts = crate::parser::Statement::parse(sql) - .collect::>>() - .or_else(|err| match err { - Error::Sqlite3UnsupportedStatement => Ok(vec![]), - err => Err(err), - })?; - - let mut state = self.state.lock().await; - - if !self.remote.is_autocommit() { - *state = State::Txn; - } - - { - let predicted_end_state = { - let mut state = state.clone(); - - stmts.iter().for_each(|parser::Statement { kind, .. }| { - state = state.step(*kind); - }); - - state - }; - - let should_execute_local = match (*state, predicted_end_state) { - (State::Init, State::Init) => stmts.iter().all(parser::Statement::is_read_only), - - (State::Init, State::TxnReadOnly) | (State::TxnReadOnly, State::TxnReadOnly) => { - let is_read_only = stmts.iter().all(parser::Statement::is_read_only); - - if !is_read_only { - return Err(Error::Misuse( - "Invalid write in a readonly transaction".into(), - )); - } - - *state = State::TxnReadOnly; - true - } - - (State::TxnReadOnly, State::Init) => { - let is_read_only = stmts.iter().all(parser::Statement::is_read_only); - - if !is_read_only { - return Err(Error::Misuse( - "Invalid write in a readonly transaction".into(), - )); - } - - *state = State::Init; - true - } - - (init, State::Invalid) => { - let err = Err(Error::InvalidParserState(format!("{:?}", init))); - - // Reset state always back to init so the user can start over - *state = State::Init; - - return err; - } - _ => { - *state = predicted_end_state; - false - } - }; - - Ok(should_execute_local) - } - } -} - -#[async_trait::async_trait] -impl Conn for SyncedConnection { - async fn execute(&self, sql: &str, params: Params) -> Result { - let stmt = self.prepare(sql).await?; - stmt.execute(params).await.map(|v| v as u64) - } - - async fn execute_batch(&self, sql: &str) -> Result { - if self.should_execute_local(sql).await? { - self.local.execute_batch(sql) - } else { - let result = self.remote.execute_batch(sql).await; - if self.read_your_writes { - let mut context = self.context.lock().await; - crate::sync::try_pull(&mut context, &self.local).await?; - } - result - } - } - - async fn execute_transactional_batch(&self, sql: &str) -> Result { - if self.should_execute_local(sql).await? { - self.local.execute_transactional_batch(sql)?; - Ok(BatchRows::empty()) - } else { - let result = self.remote.execute_transactional_batch(sql).await; - if self.read_your_writes { - let mut context = self.context.lock().await; - crate::sync::try_pull(&mut context, &self.local).await?; - } - result - } - } - - async fn prepare(&self, sql: &str) -> Result { - if self.should_execute_local(sql).await? { - Ok(Statement { - inner: Box::new(LibsqlStmt(self.local.prepare(sql)?)), - }) - } else { - let stmt = Statement { - inner: Box::new(self.remote.prepare(sql).await?), - }; - - Ok(Statement { - inner: Box::new(SyncedStatement { - conn: self.local.clone(), - inner: stmt, - context: self.context.clone(), - read_your_writes: self.read_your_writes, - }), - }) - } - } - - async fn transaction(&self, tx_behavior: TransactionBehavior) -> Result { - let tx = SyncedTx::begin(self.clone(), tx_behavior).await?; - - Ok(Transaction { - inner: Box::new(tx), - conn: crate::Connection { - conn: Arc::new(self.clone()), - }, - close: None, - }) - } - - fn interrupt(&self) -> Result<()> { - Ok(()) - } - - fn busy_timeout(&self, timeout: Duration) -> Result<()> { - self.remote.busy_timeout(timeout) - } - - fn is_autocommit(&self) -> bool { - self.remote.is_autocommit() - } - - fn changes(&self) -> u64 { - self.remote.changes() - } - - fn total_changes(&self) -> u64 { - self.remote.total_changes() - } - - fn last_insert_rowid(&self) -> i64 { - self.remote.last_insert_rowid() - } - - async fn reset(&self) {} - - fn set_reserved_bytes(&self, reserved_bytes: i32) -> Result<()> { - self.local.set_reserved_bytes(reserved_bytes) - } - - fn get_reserved_bytes(&self) -> Result { - self.local.get_reserved_bytes() - } -} diff --git a/vendor/libsql/src/sync/statement.rs b/vendor/libsql/src/sync/statement.rs deleted file mode 100644 index b3de1338..00000000 --- a/vendor/libsql/src/sync/statement.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::{ - local::{self}, - params::Params, - statement::Stmt, - sync::SyncContext, Column, Result, Rows, Statement, -}; -use std::sync::Arc; -use tokio::sync::Mutex; - -pub struct SyncedStatement { - pub conn: local::Connection, - pub inner: Statement, - pub context: Arc>, - pub read_your_writes: bool, -} - -#[async_trait::async_trait] -impl Stmt for SyncedStatement { - fn finalize(&mut self) { - self.inner.finalize() - } - - async fn execute(&self, params: &Params) -> Result { - let result = self.inner.execute(params).await; - if self.read_your_writes { - let mut context = self.context.lock().await; - crate::sync::try_pull(&mut context, &self.conn).await?; - } - result - } - - async fn query(&self, params: &Params) -> Result { - let result = self.inner.query(params).await; - if self.read_your_writes { - let mut context = self.context.lock().await; - crate::sync::try_pull(&mut context, &self.conn).await?; - } - result - } - - async fn run(&self, params: &Params) -> Result<()> { - let result = self.inner.run(params).await; - if self.read_your_writes { - let mut context = self.context.lock().await; - crate::sync::try_pull(&mut context, &self.conn).await?; - } - result - } - - fn interrupt(&self) -> Result<()> { - self.inner.interrupt() - } - - fn reset(&self) { - self.inner.reset() - } - - fn parameter_count(&self) -> usize { - self.inner.parameter_count() - } - - fn parameter_name(&self, idx: i32) -> Option<&str> { - self.inner.parameter_name(idx) - } - - fn column_count(&self) -> usize { - self.inner.column_count() - } - - fn columns(&self) -> Vec { - self.inner.columns() - } -} - diff --git a/vendor/libsql/src/sync/test.rs b/vendor/libsql/src/sync/test.rs deleted file mode 100644 index 104df12a..00000000 --- a/vendor/libsql/src/sync/test.rs +++ /dev/null @@ -1,601 +0,0 @@ -use super::*; -use crate::util::Socket; -use std::pin::Pin; -use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; -use std::sync::Arc; -use std::task::{Context, Poll}; -use std::time::Duration; -use tempfile::tempdir; -use tokio::io::{duplex, AsyncRead, AsyncWrite, DuplexStream}; -use tower::Service; - -#[tokio::test] -async fn test_sync_context_push_frame() { - let server = MockServer::start(); - let temp_dir = tempdir().unwrap(); - let db_path = temp_dir.path().join("test.db"); - gen_metadata_file(&db_path, 3278479626, 0, 0, 1); - - let sync_ctx = SyncContext::new( - server.connector(), - db_path.to_str().unwrap().to_string(), - server.url(), - None, - None, - ) - .await - .unwrap(); - - let frame = Bytes::from("test frame data"); - let mut sync_ctx = sync_ctx; - - // Push a frame and verify the response - let durable_frame = sync_ctx.push_frames(frame, 1, 0, 1, None).await.unwrap(); - sync_ctx.write_metadata().await.unwrap(); - assert_eq!(durable_frame.max_frame_no, 0); // First frame should return max_frame_no = 0 - - // Verify internal state was updated - assert_eq!(sync_ctx.durable_frame_num(), 0); - assert_eq!(sync_ctx.durable_generation(), 1); - assert_eq!(server.frame_count(), 1); -} - -#[tokio::test] -async fn test_sync_context_with_auth() { - let server = MockServer::start(); - let temp_dir = tempdir().unwrap(); - let db_path = temp_dir.path().join("test.db"); - gen_metadata_file(&db_path, 3278479626, 0, 0, 1); - - let sync_ctx = SyncContext::new( - server.connector(), - db_path.to_str().unwrap().to_string(), - server.url(), - Some("test_token".to_string()), - None, - ) - .await - .unwrap(); - - let frame = Bytes::from("test frame with auth"); - let mut sync_ctx = sync_ctx; - - let durable_frame = sync_ctx.push_frames(frame, 1, 0, 1, None).await.unwrap(); - sync_ctx.write_metadata().await.unwrap(); - assert_eq!(durable_frame.max_frame_no, 0); - assert_eq!(server.frame_count(), 1); -} - -#[tokio::test] -async fn test_sync_context_multiple_frames() { - let server = MockServer::start(); - let temp_dir = tempdir().unwrap(); - let db_path = temp_dir.path().join("test.db"); - gen_metadata_file(&db_path, 3278479626, 0, 0, 1); - - let sync_ctx = SyncContext::new( - server.connector(), - db_path.to_str().unwrap().to_string(), - server.url(), - None, - None, - ) - .await - .unwrap(); - - let mut sync_ctx = sync_ctx; - - // Push multiple frames and verify incrementing frame numbers - for i in 0..3 { - let frame = Bytes::from(format!("frame data {}", i)); - let durable_frame = sync_ctx.push_frames(frame, 1, i, 1, None).await.unwrap(); - sync_ctx.write_metadata().await.unwrap(); - assert_eq!(durable_frame.max_frame_no, i); - assert_eq!(sync_ctx.durable_frame_num(), i); - assert_eq!(server.frame_count(), i + 1); - } -} - -#[tokio::test] -async fn test_sync_context_corrupted_metadata() { - let server = MockServer::start(); - let temp_dir = tempdir().unwrap(); - let db_path = temp_dir.path().join("test.db"); - gen_metadata_file(&db_path, 3278479626, 0, 0, 1); - - // Create initial sync context and push a frame - let sync_ctx = SyncContext::new( - server.connector(), - db_path.to_str().unwrap().to_string(), - server.url(), - None, - None, - ) - .await - .unwrap(); - - let mut sync_ctx = sync_ctx; - let frame = Bytes::from("test frame data"); - let durable_frame = sync_ctx.push_frames(frame, 1, 0, 1, None).await.unwrap(); - sync_ctx.write_metadata().await.unwrap(); - assert_eq!(durable_frame.max_frame_no, 0); - assert_eq!(server.frame_count(), 1); - - // Update metadata path to use -info instead of .meta - let metadata_path = format!("{}-info", db_path.to_str().unwrap()); - std::fs::write(&metadata_path, b"invalid json data").unwrap(); - - // Create new sync context with corrupted metadata - let sync_ctx = SyncContext::new( - server.connector(), - db_path.to_str().unwrap().to_string(), - server.url(), - None, - None, - ) - .await; - - assert!(sync_ctx.is_err()); -} - -#[tokio::test] -async fn test_sync_restarts_with_lower_max_frame_no() { - let _ = tracing_subscriber::fmt::try_init(); - - let server = MockServer::start(); - let temp_dir = tempdir().unwrap(); - let db_path = temp_dir.path().join("test.db"); - gen_metadata_file(&db_path, 3278479626, 0, 0, 1); - - // Create initial sync context and push a frame - let sync_ctx = SyncContext::new( - server.connector(), - db_path.to_str().unwrap().to_string(), - server.url(), - None, - None, - ) - .await - .unwrap(); - - let mut sync_ctx = sync_ctx; - let frame = Bytes::from("test frame data"); - let durable_frame = sync_ctx - .push_frames(frame.clone(), 1, 0, 1, None) - .await - .unwrap(); - sync_ctx.write_metadata().await.unwrap(); - assert_eq!(durable_frame.max_frame_no, 0); - assert_eq!(server.frame_count(), 1); - - // Bump the durable frame num so that the next time we call the - // server we think we are further ahead than the database we are talking to is. - sync_ctx.durable_frame_num += 3; - sync_ctx.write_metadata().await.unwrap(); - - // Create new sync context with corrupted metadata - let mut sync_ctx = SyncContext::new( - server.connector(), - db_path.to_str().unwrap().to_string(), - server.url(), - None, - None, - ) - .await - .unwrap(); - - // Verify that the context was set to new fake values. - assert_eq!(sync_ctx.durable_frame_num(), 3); - assert_eq!(sync_ctx.durable_generation(), 1); - - let frame_no = sync_ctx.durable_frame_num() + 1; - // This push should fail because we are ahead of the server and thus should get an invalid - // frame no error. - sync_ctx - .push_frames(frame.clone(), 1, frame_no, 1, None) - .await - .unwrap_err(); - - let frame_no = sync_ctx.durable_frame_num() + 1; - // This then should work because when the last one failed it updated our state of the server - // durable_frame_num and we should then start writing from there. - sync_ctx - .push_frames(frame, 1, frame_no, 1, None) - .await - .unwrap(); -} - -#[tokio::test] -async fn test_sync_context_retry_on_error() { - // Pause time to control it manually - tokio::time::pause(); - - let server = MockServer::start(); - let temp_dir = tempdir().unwrap(); - let db_path = temp_dir.path().join("test.db"); - gen_metadata_file(&db_path, 3278479626, 0, 0, 1); - - let sync_ctx = SyncContext::new( - server.connector(), - db_path.to_str().unwrap().to_string(), - server.url(), - None, - None, - ) - .await - .unwrap(); - - let mut sync_ctx = sync_ctx; - let frame = Bytes::from("test frame data"); - - // Set server to return errors - server.return_error.store(true, Ordering::SeqCst); - - // First attempt should fail but retry - let result = sync_ctx.push_frames(frame.clone(), 1, 0, 1, None).await; - assert!(result.is_err()); - - // Advance time to trigger retries faster - tokio::time::advance(Duration::from_secs(2)).await; - - // Verify multiple requests were made (retries occurred) - assert!(server.request_count() > 1); - - // Allow the server to succeed - server.return_error.store(false, Ordering::SeqCst); - - // Next attempt should succeed - let durable_frame = sync_ctx.push_frames(frame, 1, 0, 1, None).await.unwrap(); - sync_ctx.write_metadata().await.unwrap(); - assert_eq!(durable_frame.max_frame_no, 0); - assert_eq!(server.frame_count(), 1); -} - -#[tokio::test] -async fn test_bootstrap_db_downloads_export() { - let server = MockServer::start(); - let temp_dir = tempdir().unwrap(); - let db_path = temp_dir.path().join("bootstrap.db"); - - // Seed metadata so SyncContext can be constructed (generation=1) - gen_metadata_file(&db_path, 3278479626, 0, 0, 1); - - let mut sync_ctx = SyncContext::new( - server.connector(), - db_path.to_str().unwrap().to_string(), - server.url(), - None, - None, - ) - .await - .unwrap(); - - - let _ = std::fs::remove_file(&db_path); - let _ = std::fs::remove_file(format!("{}-info", db_path.to_str().unwrap())); - - // Bootstrap should fetch /info and then /export/{generation} - crate::sync::bootstrap_db(&mut sync_ctx).await.unwrap(); - - assert!(std::path::Path::new(&db_path).exists()); - assert!(std::path::Path::new(&format!("{}-info", db_path.to_str().unwrap())).exists()); - - assert_eq!(sync_ctx.durable_generation(), 1); - assert_eq!(sync_ctx.durable_frame_num(), 0); - - assert!(server.request_count() >= 2); -} - -#[tokio::test] -async fn test_bootstrap_db_is_idempotent() { - let server = MockServer::start(); - let temp_dir = tempdir().unwrap(); - let db_path = temp_dir.path().join("bootstrap2.db"); - - - gen_metadata_file(&db_path, 3278479626, 0, 0, 1); - - let mut sync_ctx = SyncContext::new( - server.connector(), - db_path.to_str().unwrap().to_string(), - server.url(), - None, - None, - ) - .await - .unwrap(); - - let _ = std::fs::remove_file(&db_path); - let _ = std::fs::remove_file(format!("{}-info", db_path.to_str().unwrap())); - - - crate::sync::bootstrap_db(&mut sync_ctx).await.unwrap(); - let first_requests = server.request_count(); - - // Second bootstrap should be a no-op (no new network calls) - crate::sync::bootstrap_db(&mut sync_ctx).await.unwrap(); - let second_requests = server.request_count(); - assert_eq!(first_requests, second_requests); -} - -#[test] -fn test_hash_verification() { - let mut metadata = MetadataJson { - hash: 0, - version: 1, - durable_frame_num: 100, - generation: 5, - }; - - assert!(metadata.verify_hash().is_err()); - - metadata.set_hash(); - - assert!(metadata.verify_hash().is_ok()); -} - -#[test] -fn test_hash_tampering() { - let mut metadata = MetadataJson { - hash: 0, - version: 1, - durable_frame_num: 100, - generation: 5, - }; - - // Create metadata with hash - metadata.set_hash(); - - // Tamper with a field - metadata.version = 2; - - // Verify should fail - assert!(metadata.verify_hash().is_err()); - - metadata.version = 1; - metadata.generation = 42; - - assert!(metadata.verify_hash().is_err()); - - metadata.generation = 5; - metadata.durable_frame_num = 42; - - assert!(metadata.verify_hash().is_err()); - - metadata.durable_frame_num = 100; - - assert!(metadata.verify_hash().is_ok()); -} - -// Mock connector service that implements tower::Service -#[derive(Clone)] -struct MockConnector { - tx: tokio::sync::mpsc::Sender, -} - -impl Service for MockConnector { - type Response = Box; - type Error = Box; - type Future = Pin< - Box< - dyn std::future::Future> - + Send, - >, - >; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, _: http::Uri) -> Self::Future { - let (stream, server_stream) = duplex(1024); - let _ = self.tx.try_send(server_stream); - let conn = MockConnection { stream }; - Box::pin(std::future::ready(Ok(Box::new(conn) as Box))) - } -} - -#[allow(dead_code)] -struct MockServer { - url: String, - frame_count: Arc, - connector: ConnectorService, - return_error: Arc, - request_count: Arc, - export_bytes: Arc>, // bytes returned by /export/{generation} -} - -impl MockServer { - fn start() -> Self { - let frame_count = Arc::new(AtomicU32::new(0)); - let return_error = Arc::new(AtomicBool::new(false)); - let request_count = Arc::new(AtomicU32::new(0)); - - let export_bytes: Arc> = { - use crate::local::Database; - use crate::database::OpenFlags; - use std::fs; - use tempfile::NamedTempFile; - - let tmp = NamedTempFile::new().expect("temp file for export db"); - let path = tmp.path().to_path_buf(); - let db = Database::open(path.to_str().unwrap().to_string(), OpenFlags::default()) - .expect("open export db"); - let conn = db.connect().expect("connect export db"); - - let _ = conn.query("CREATE TABLE IF NOT EXISTS t(x INTEGER);", crate::params::Params::None); - drop(conn); - drop(db); - let bytes = fs::read(&path).expect("read export db bytes"); - Arc::new(bytes) - }; - - // Create the mock connector with Some(client_stream) - let (tx, mut rx) = tokio::sync::mpsc::channel(1); - let mock_connector = MockConnector { tx }; - let connector = ConnectorService::new(mock_connector); - - let server = Self { - url: "http://mock.server".to_string(), - frame_count: frame_count.clone(), - connector, - return_error: return_error.clone(), - request_count: request_count.clone(), - export_bytes: export_bytes.clone(), - }; - - // Spawn the server handler - let frame_count_clone = frame_count.clone(); - let return_error_clone = return_error.clone(); - let request_count_clone = request_count.clone(); - let export_bytes_clone = export_bytes.clone(); - - tokio::spawn(async move { - while let Some(server_stream) = rx.recv().await { - let frame_count_clone = frame_count_clone.clone(); - let return_error_clone = return_error_clone.clone(); - let request_count_clone = request_count_clone.clone(); - let export_bytes_clone = export_bytes_clone.clone(); - - tokio::spawn(async move { - use hyper::server::conn::Http; - use hyper::service::service_fn; - - let frame_count_clone = frame_count_clone.clone(); - let return_error_clone = return_error_clone.clone(); - let request_count_clone = request_count_clone.clone(); - let service = service_fn(move |req: http::Request| { - let frame_count = frame_count_clone.clone(); - let return_error = return_error_clone.clone(); - let request_count = request_count_clone.clone(); - let export_bytes = export_bytes_clone.clone(); - async move { - request_count.fetch_add(1, Ordering::SeqCst); - if return_error.load(Ordering::SeqCst) { - return Ok::<_, hyper::Error>( - http::Response::builder() - .status(500) - .body(Body::from("Internal Server Error")) - .unwrap(), - ); - } - - if req.uri().path().contains("/sync/") { - // Count only sync requests as frames to keep tests stable. - let current_count = frame_count.fetch_add(1, Ordering::SeqCst); - // Return the max_frame_no that has been accepted - let response = serde_json::json!({ - "status": "ok", - "generation": 1, - "max_frame_no": current_count - }); - - Ok::<_, hyper::Error>( - http::Response::builder() - .status(200) - .body(Body::from(response.to_string())) - .unwrap(), - ) - } else if req.uri().path().eq("/info") { - let response = serde_json::json!({ - "current_generation": 1 - }); - Ok::<_, hyper::Error>( - http::Response::builder() - .status(200) - .body(Body::from(response.to_string())) - .unwrap(), - ) - } else if req.uri().path().starts_with("/export/") { - Ok::<_, hyper::Error>( - http::Response::builder() - .status(200) - .body(Body::from(export_bytes.as_ref().clone())) - .unwrap(), - ) - } else { - Ok(http::Response::builder() - .status(404) - .body(Body::empty()) - .unwrap()) - } - } - }); - - if let Err(e) = Http::new().serve_connection(server_stream, service).await { - eprintln!("Error serving connection: {}", e); - } - }); - } - }); - - server - } - - fn connector(&self) -> ConnectorService { - self.connector.clone() - } - - fn url(&self) -> String { - self.url.clone() - } - - fn frame_count(&self) -> u32 { - self.frame_count.load(Ordering::SeqCst) - } - - fn request_count(&self) -> u32 { - self.request_count.load(Ordering::SeqCst) - } -} - -// Mock connection that implements the Socket trait -struct MockConnection { - stream: DuplexStream, -} - -impl AsyncRead for MockConnection { - fn poll_read( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut tokio::io::ReadBuf<'_>, - ) -> Poll> { - Pin::new(&mut self.stream).poll_read(cx, buf) - } -} - -impl AsyncWrite for MockConnection { - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - Pin::new(&mut self.stream).poll_write(cx, buf) - } - - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.stream).poll_flush(cx) - } - - fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.stream).poll_shutdown(cx) - } -} - -impl hyper::client::connect::Connection for MockConnection { - fn connected(&self) -> hyper::client::connect::Connected { - hyper::client::connect::Connected::new() - } -} - -fn gen_metadata_file(db_path: &Path, hash: u32, version: u32, durable_frame_num: u32, generation: u32) { - let metadata_path = format!("{}-info", db_path.to_str().unwrap()); - std::fs::write( - &metadata_path, - format!( - "{{\"hash\": {hash}, \"version\": {version}, \"durable_frame_num\": {durable_frame_num}, \"generation\": {generation}}}" - ) - .as_bytes(), - ) - .unwrap(); -} diff --git a/vendor/libsql/src/sync/transaction.rs b/vendor/libsql/src/sync/transaction.rs deleted file mode 100644 index ebfa1ee8..00000000 --- a/vendor/libsql/src/sync/transaction.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::{ - connection::Conn, - params::Params, - transaction::Tx, Result, TransactionBehavior, -}; - -use super::connection::SyncedConnection; - -pub struct SyncedTx(SyncedConnection); - -impl SyncedTx { - pub(crate) async fn begin( - conn: SyncedConnection, - tx_behavior: TransactionBehavior, - ) -> Result { - conn.execute( - match tx_behavior { - TransactionBehavior::Deferred => "BEGIN DEFERRED", - TransactionBehavior::Immediate => "BEGIN IMMEDIATE", - TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE", - TransactionBehavior::ReadOnly => "BEGIN READONLY", - }, - Params::None, - ) - .await?; - Ok(Self(conn.clone())) - } -} - -#[async_trait::async_trait] -impl Tx for SyncedTx { - async fn commit(&mut self) -> Result<()> { - self.0.execute("COMMIT", Params::None).await?; - Ok(()) - } - - async fn rollback(&mut self) -> Result<()> { - self.0.execute("ROLLBACK", Params::None).await?; - Ok(()) - } -} diff --git a/vendor/libsql/src/transaction.rs b/vendor/libsql/src/transaction.rs deleted file mode 100644 index 7ad71ff2..00000000 --- a/vendor/libsql/src/transaction.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::ops::Deref; - -use crate::Result; - -use super::Connection; - -/// Transaction types that correlate to sqlite3 transactions and -/// additional ones introduced by libsql. -#[derive(Debug)] -pub enum TransactionBehavior { - Deferred, - Immediate, - Exclusive, - ReadOnly, -} - -/// A transaction on some connection. -pub struct Transaction { - pub(crate) inner: Box, - pub(crate) conn: Connection, - /// An optional action executed whenever a transaction needs to be dropped. - pub(crate) close: Option>, -} - -impl Transaction { - /// Consume this transaction and commit. - pub async fn commit(mut self) -> Result<()> { - self.inner.commit().await - } - - /// Consume this transaction and rollback. - pub async fn rollback(mut self) -> Result<()> { - self.inner.rollback().await - } -} - -impl Deref for Transaction { - type Target = Connection; - - #[inline] - fn deref(&self) -> &Connection { - &self.conn - } -} - -impl Drop for Transaction { - fn drop(&mut self) { - if let Some(close) = self.close.take() { - close(); - } - } -} - -#[async_trait::async_trait] -pub(crate) trait Tx { - async fn commit(&mut self) -> Result<()>; - async fn rollback(&mut self) -> Result<()>; -} diff --git a/vendor/libsql/src/util/box_clone_service.rs b/vendor/libsql/src/util/box_clone_service.rs deleted file mode 100644 index ed77d5fd..00000000 --- a/vendor/libsql/src/util/box_clone_service.rs +++ /dev/null @@ -1,86 +0,0 @@ -// Copied from https://docs.rs/tower/latest/tower/util/struct.BoxCloneService.html -// This is because in the tower version the trait object only implements `Send` which -// means we can't call clients from context that need `Sync` like an `async fn` that needs -// to be `Send` (must be sync as well to impl Send). - -use std::fmt; -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; -use tower::Service; -use tower::ServiceExt; - -type BoxFuture = Pin + Send>>; - -pub struct BoxCloneService( - Box< - dyn CloneService>> - + Send - + Sync, - >, -); - -impl BoxCloneService { - /// Create a new `BoxCloneService`. - pub fn new(inner: S) -> Self - where - S: Service + Clone + Sync + Send + 'static, - S::Future: Send + 'static, - { - let inner = inner.map_future(|f| Box::pin(f) as _); - BoxCloneService(Box::new(inner)) - } -} - -impl Service for BoxCloneService { - type Response = U; - type Error = E; - type Future = BoxFuture>; - - #[inline] - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.0.poll_ready(cx) - } - - #[inline] - fn call(&mut self, request: T) -> Self::Future { - self.0.call(request) - } -} - -impl Clone for BoxCloneService { - fn clone(&self) -> Self { - Self(self.0.clone_box()) - } -} - -trait CloneService: Service { - fn clone_box( - &self, - ) -> Box< - dyn CloneService - + Send - + Sync, - >; -} - -impl CloneService for T -where - T: Service + Send + Sync + Clone + 'static, -{ - fn clone_box( - &self, - ) -> Box< - dyn CloneService - + Send - + Sync, - > { - Box::new(self.clone()) - } -} - -impl fmt::Debug for BoxCloneService { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.debug_struct("BoxCloneService").finish() - } -} diff --git a/vendor/libsql/src/util/http.rs b/vendor/libsql/src/util/http.rs deleted file mode 100644 index 478318e9..00000000 --- a/vendor/libsql/src/util/http.rs +++ /dev/null @@ -1,24 +0,0 @@ -use super::box_clone_service::BoxCloneService; -use tokio::io::{AsyncRead, AsyncWrite}; - -pub trait Socket: - hyper::client::connect::Connection + AsyncRead + AsyncWrite + Send + Unpin + 'static + Sync -{ -} - -impl Socket for T where - T: hyper::client::connect::Connection + AsyncRead + AsyncWrite + Send + Unpin + 'static + Sync -{ -} - -impl hyper::client::connect::Connection for Box { - fn connected(&self) -> hyper::client::connect::Connected { - self.as_ref().connected() - } -} - -pub type ConnectorService = - BoxCloneService, Box>; - -#[cfg(feature = "replication")] -pub type HttpRequestCallback = std::sync::Arc) + Send + Sync>; diff --git a/vendor/libsql/src/util/mod.rs b/vendor/libsql/src/util/mod.rs deleted file mode 100644 index e0c3c0d2..00000000 --- a/vendor/libsql/src/util/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -cfg_replication_or_remote_or_sync! { - pub mod box_clone_service; - mod http; - pub(crate) use self::http::{ConnectorService, Socket}; -} - -cfg_replication! { - pub(crate) use self::http::HttpRequestCallback; -} - -cfg_replication_or_remote_or_hrana! { - pub(crate) fn coerce_url_scheme(url: String) -> String { - let mut url = url.replace("libsql://", "https://"); - - if !url.contains("://") { - url = format!("https://{}", url) - } - - url - } -} diff --git a/vendor/libsql/src/value.rs b/vendor/libsql/src/value.rs deleted file mode 100644 index 7d50265e..00000000 --- a/vendor/libsql/src/value.rs +++ /dev/null @@ -1,821 +0,0 @@ -use std::str::FromStr; - -use crate::{Error, Result}; - -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] -pub enum Value { - Null, - Integer(i64), - Real(f64), - Text(String), - Blob(Vec), -} - -/// The possible types a column can be in libsql. -#[derive(Debug, Copy, Clone)] -pub enum ValueType { - Integer = 1, - Real, - Text, - Blob, - Null, -} - -impl FromStr for ValueType { - type Err = (); - - fn from_str(s: &str) -> std::result::Result { - match s { - "TEXT" => Ok(ValueType::Text), - "INTEGER" => Ok(ValueType::Integer), - "BLOB" => Ok(ValueType::Blob), - "NULL" => Ok(ValueType::Null), - "REAL" => Ok(ValueType::Real), - _ => Err(()), - } - } -} - -impl Value { - /// Returns `true` if the value is [`Null`]. - /// - /// [`Null`]: Value::Null - #[must_use] - pub fn is_null(&self) -> bool { - matches!(self, Self::Null) - } - - /// Returns `true` if the value is [`Integer`]. - /// - /// [`Integer`]: Value::Integer - #[must_use] - pub fn is_integer(&self) -> bool { - matches!(self, Self::Integer(..)) - } - - /// Returns `true` if the value is [`Real`]. - /// - /// [`Real`]: Value::Real - #[must_use] - pub fn is_real(&self) -> bool { - matches!(self, Self::Real(..)) - } - - pub fn as_real(&self) -> Option<&f64> { - if let Self::Real(v) = self { - Some(v) - } else { - None - } - } - - /// Returns `true` if the value is [`Text`]. - /// - /// [`Text`]: Value::Text - #[must_use] - pub fn is_text(&self) -> bool { - matches!(self, Self::Text(..)) - } - - pub fn as_text(&self) -> Option<&String> { - if let Self::Text(v) = self { - Some(v) - } else { - None - } - } - - pub fn as_integer(&self) -> Option<&i64> { - if let Self::Integer(v) = self { - Some(v) - } else { - None - } - } - - /// Returns `true` if the value is [`Blob`]. - /// - /// [`Blob`]: Value::Blob - #[must_use] - pub fn is_blob(&self) -> bool { - matches!(self, Self::Blob(..)) - } - - pub fn as_blob(&self) -> Option<&Vec> { - if let Self::Blob(v) = self { - Some(v) - } else { - None - } - } -} - -impl From for Value { - fn from(value: i8) -> Value { - Value::Integer(value as i64) - } -} - -impl From for Value { - fn from(value: i16) -> Value { - Value::Integer(value as i64) - } -} - -impl From for Value { - fn from(value: i32) -> Value { - Value::Integer(value as i64) - } -} - -impl From for Value { - fn from(value: i64) -> Value { - Value::Integer(value) - } -} - -impl From for Value { - fn from(value: u8) -> Value { - Value::Integer(value as i64) - } -} - -impl From for Value { - fn from(value: u16) -> Value { - Value::Integer(value as i64) - } -} - -impl From for Value { - fn from(value: u32) -> Value { - Value::Integer(value as i64) - } -} - -impl TryFrom for Value { - type Error = crate::Error; - - fn try_from(value: u64) -> Result { - if value > i64::MAX as u64 { - Err(Error::ToSqlConversionFailure( - "u64 is too large to fit in an i64".into(), - )) - } else { - Ok(Value::Integer(value as i64)) - } - } -} - -impl From for Value { - fn from(value: f32) -> Value { - Value::Real(value as f64) - } -} - -impl From for Value { - fn from(value: f64) -> Value { - Value::Real(value) - } -} - -impl From<&str> for Value { - fn from(value: &str) -> Value { - Value::Text(value.to_owned()) - } -} - -impl From for Value { - fn from(value: String) -> Value { - Value::Text(value) - } -} - -impl From<&[u8]> for Value { - fn from(value: &[u8]) -> Value { - Value::Blob(value.to_owned()) - } -} - -impl From> for Value { - fn from(value: Vec) -> Value { - Value::Blob(value) - } -} - -impl From for Value { - fn from(value: bool) -> Value { - Value::Integer(value as i64) - } -} - -impl From> for Value -where - T: Into, -{ - fn from(value: Option) -> Self { - match value { - Some(inner) => inner.into(), - None => Value::Null, - } - } -} - -#[cfg(feature = "core")] -impl From for Value { - fn from(value: libsql_sys::Value) -> Value { - match value.value_type().into() { - ValueType::Null => Value::Null, - ValueType::Integer => Value::Integer(value.int64()), - ValueType::Real => Value::Real(value.double()), - ValueType::Text => { - let v = value.text(); - if v.is_null() { - Value::Null - } else { - let v = unsafe { std::ffi::CStr::from_ptr(v as *const std::ffi::c_char) }; - let v = v.to_str().unwrap(); - Value::Text(v.to_owned()) - } - } - ValueType::Blob => { - let (len, blob) = (value.bytes(), value.blob()); - - assert!(len >= 0, "unexpected negative bytes value from sqlite3"); - - let mut v = Vec::with_capacity(len as usize); - if !blob.is_null() { - let slice: &[u8] = - unsafe { std::slice::from_raw_parts(blob as *const u8, len as usize) }; - v.extend_from_slice(slice); - } - Value::Blob(v) - } - } - } -} - -/// A borrowed version of `Value`. -#[derive(Debug)] -pub enum ValueRef<'a> { - Null, - Integer(i64), - Real(f64), - Text(&'a [u8]), - Blob(&'a [u8]), -} - -impl ValueRef<'_> { - pub fn data_type(&self) -> ValueType { - match *self { - ValueRef::Null => ValueType::Null, - ValueRef::Integer(_) => ValueType::Integer, - ValueRef::Real(_) => ValueType::Real, - ValueRef::Text(_) => ValueType::Text, - ValueRef::Blob(_) => ValueType::Blob, - } - } - - /// Returns `true` if the value ref is [`Null`]. - /// - /// [`Null`]: ValueRef::Null - #[must_use] - pub fn is_null(&self) -> bool { - matches!(self, Self::Null) - } - - /// Returns `true` if the value ref is [`Integer`]. - /// - /// [`Integer`]: ValueRef::Integer - #[must_use] - pub fn is_integer(&self) -> bool { - matches!(self, Self::Integer(..)) - } - - pub fn as_integer(&self) -> Option<&i64> { - if let Self::Integer(v) = self { - Some(v) - } else { - None - } - } - - /// Returns `true` if the value ref is [`Real`]. - /// - /// [`Real`]: ValueRef::Real - #[must_use] - pub fn is_real(&self) -> bool { - matches!(self, Self::Real(..)) - } - - pub fn as_real(&self) -> Option<&f64> { - if let Self::Real(v) = self { - Some(v) - } else { - None - } - } - - /// Returns `true` if the value ref is [`Text`]. - /// - /// [`Text`]: ValueRef::Text - #[must_use] - pub fn is_text(&self) -> bool { - matches!(self, Self::Text(..)) - } - - pub fn as_text(&self) -> Option<&[u8]> { - if let Self::Text(v) = self { - Some(v) - } else { - None - } - } - - /// Returns `true` if the value ref is [`Blob`]. - /// - /// [`Blob`]: ValueRef::Blob - #[must_use] - pub fn is_blob(&self) -> bool { - matches!(self, Self::Blob(..)) - } - - pub fn as_blob(&self) -> Option<&[u8]> { - if let Self::Blob(v) = self { - Some(v) - } else { - None - } - } -} - -impl From> for Value { - fn from(vr: ValueRef<'_>) -> Value { - match vr { - ValueRef::Null => Value::Null, - ValueRef::Integer(i) => Value::Integer(i), - ValueRef::Real(r) => Value::Real(r), - ValueRef::Text(s) => Value::Text(String::from_utf8_lossy(s).to_string()), - ValueRef::Blob(b) => Value::Blob(b.to_vec()), - } - } -} - -impl<'a> From<&'a str> for ValueRef<'a> { - fn from(s: &str) -> ValueRef<'_> { - ValueRef::Text(s.as_bytes()) - } -} - -impl<'a> From<&'a [u8]> for ValueRef<'a> { - fn from(s: &[u8]) -> ValueRef<'_> { - ValueRef::Blob(s) - } -} - -impl<'a> From<&'a Value> for ValueRef<'a> { - fn from(v: &'a Value) -> ValueRef<'a> { - match *v { - Value::Null => ValueRef::Null, - Value::Integer(i) => ValueRef::Integer(i), - Value::Real(r) => ValueRef::Real(r), - Value::Text(ref s) => ValueRef::Text(s.as_bytes()), - Value::Blob(ref b) => ValueRef::Blob(b), - } - } -} - -impl<'a, T> From> for ValueRef<'a> -where - T: Into>, -{ - #[inline] - fn from(s: Option) -> ValueRef<'a> { - match s { - Some(x) => x.into(), - None => ValueRef::Null, - } - } -} - -#[cfg(feature = "core")] -impl<'a> From for ValueRef<'a> { - fn from(value: libsql_sys::Value) -> ValueRef<'a> { - match value.value_type().into() { - ValueType::Null => ValueRef::Null, - ValueType::Integer => ValueRef::Integer(value.int64()), - ValueType::Real => ValueRef::Real(value.double()), - ValueType::Text => { - let v = value.text(); - if v.is_null() { - ValueRef::Null - } else { - let v = unsafe { std::ffi::CStr::from_ptr(v as *const std::ffi::c_char) }; - ValueRef::Text(v.to_bytes()) - } - } - ValueType::Blob => { - let (len, blob) = (value.bytes(), value.blob()); - - assert!(len >= 0, "unexpected negative bytes value from sqlite3"); - - if len > 0 { - let slice: &[u8] = - unsafe { std::slice::from_raw_parts(blob as *const u8, len as usize) }; - ValueRef::Blob(slice) - } else { - ValueRef::Blob(&[]) - } - } - } - } -} - -#[cfg(feature = "core")] -impl From for ValueType { - fn from(other: libsql_sys::ValueType) -> Self { - match other { - libsql_sys::ValueType::Integer => ValueType::Integer, - libsql_sys::ValueType::Real => ValueType::Real, - libsql_sys::ValueType::Text => ValueType::Text, - libsql_sys::ValueType::Blob => ValueType::Blob, - libsql_sys::ValueType::Null => ValueType::Null, - } - } -} - -#[cfg(feature = "replication")] -impl TryFrom<&libsql_replication::rpc::proxy::Value> for Value { - type Error = Error; - - fn try_from(value: &libsql_replication::rpc::proxy::Value) -> Result { - #[derive(serde::Deserialize)] - pub enum BincodeValue { - Null, - Integer(i64), - Real(f64), - Text(String), - Blob(Vec), - } - - Ok( - match bincode::deserialize::<'_, BincodeValue>(&value.data[..]).map_err(Error::from)? { - BincodeValue::Null => Value::Null, - BincodeValue::Integer(i) => Value::Integer(i), - BincodeValue::Real(x) => Value::Real(x), - BincodeValue::Text(s) => Value::Text(s), - BincodeValue::Blob(b) => Value::Blob(b), - }, - ) - } -} - -#[cfg(feature = "serde")] -mod serde_ { - use std::marker::PhantomData; - - use serde::de::value::SeqDeserializer; - use serde::de::{self, EnumAccess, IntoDeserializer, VariantAccess, Visitor}; - use serde::Deserialize; - use serde::Deserializer; - - use super::*; - - impl<'de> Deserialize<'de> for Value { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct V; - impl<'de> Visitor<'de> for V { - type Value = Value; - - fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "an integer, a float, a string, a blob, or null") - } - - fn visit_i64(self, v: i64) -> std::result::Result - where - E: serde::de::Error, - { - Ok(Value::Integer(v)) - } - - fn visit_i32(self, v: i32) -> std::result::Result - where - E: serde::de::Error, - { - Ok(Value::Integer(v as i64)) - } - - fn visit_i16(self, v: i16) -> std::result::Result - where - E: serde::de::Error, - { - Ok(Value::Integer(v as i64)) - } - - fn visit_i8(self, v: i8) -> std::result::Result - where - E: serde::de::Error, - { - Ok(Value::Integer(v as i64)) - } - - fn visit_u64(self, v: u64) -> std::result::Result - where - E: serde::de::Error, - { - if v > i64::MAX as u64 { - Err(serde::de::Error::invalid_value( - de::Unexpected::Unsigned(v), - &"u64 is too large to fit in an i64", - )) - } else { - Ok(Value::Integer(v as i64)) - } - } - - fn visit_u32(self, v: u32) -> std::result::Result - where - E: serde::de::Error, - { - Ok(Value::Integer(v as i64)) - } - - fn visit_u16(self, v: u16) -> std::result::Result - where - E: serde::de::Error, - { - Ok(Value::Integer(v as i64)) - } - - fn visit_u8(self, v: u8) -> std::result::Result - where - E: serde::de::Error, - { - Ok(Value::Integer(v as i64)) - } - - fn visit_f64(self, v: f64) -> std::result::Result - where - E: serde::de::Error, - { - Ok(Value::Real(v)) - } - - fn visit_f32(self, v: f32) -> std::result::Result - where - E: serde::de::Error, - { - Ok(Value::Real(v as f64)) - } - - fn visit_byte_buf(self, v: Vec) -> std::result::Result - where - E: serde::de::Error, - { - Ok(Value::Blob(v)) - } - - fn visit_string(self, v: String) -> std::result::Result - where - E: serde::de::Error, - { - Ok(Value::Text(v)) - } - - fn visit_str(self, v: &str) -> std::result::Result - where - E: de::Error, - { - Ok(Value::Text(v.to_string())) - } - - fn visit_some( - self, - deserializer: D, - ) -> std::result::Result - where - D: Deserializer<'de>, - { - Deserialize::deserialize(deserializer) - } - - fn visit_none(self) -> std::result::Result - where - E: serde::de::Error, - { - Ok(Value::Null) - } - - fn visit_bool(self, v: bool) -> std::result::Result - where - E: de::Error, - { - Ok(Value::Integer(v as i64)) - } - } - - deserializer.deserialize_any(V) - } - } - - pub struct ValueDeserializer { - value: Value, - _pth: PhantomData, - } - - impl<'de, E: de::Error> Deserializer<'de> for ValueDeserializer { - type Error = E; - - serde::forward_to_deserialize_any! { - i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string - bytes byte_buf unit_struct newtype_struct seq tuple tuple_struct - map struct identifier ignored_any - } - - fn deserialize_unit(self, visitor: V) -> std::result::Result - where - V: Visitor<'de>, - { - match self.value { - Value::Null => visitor.visit_unit(), - _ => self.deserialize_any(visitor), - } - } - - fn deserialize_option(self, visitor: V) -> std::result::Result - where - V: Visitor<'de>, - { - match self.value { - Value::Null => visitor.visit_none(), - _ => visitor.visit_some(self), - } - } - - fn deserialize_bool(self, visitor: V) -> std::result::Result - where - V: Visitor<'de>, - { - match self.value { - Value::Integer(0) => visitor.visit_bool(false), - Value::Integer(1) => visitor.visit_bool(true), - _ => Err(de::Error::invalid_value( - de::Unexpected::Other(&format!("{:?}", self.value)), - &"a valid sqlite boolean representation (0 or 1)", - )), - } - } - - fn deserialize_any(self, visitor: V) -> std::result::Result - where - V: Visitor<'de>, - { - match self.value { - Value::Null => visitor.visit_none(), - Value::Integer(i) => visitor.visit_i64(i), - Value::Real(x) => visitor.visit_f64(x), - Value::Text(s) => visitor.visit_string(s), - Value::Blob(b) => visitor.visit_seq(SeqDeserializer::new(b.into_iter())), - } - } - - fn deserialize_enum( - self, - _name: &'static str, - _variants: &'static [&'static str], - visitor: V, - ) -> std::result::Result - where - V: Visitor<'de>, - { - struct ValueEnumAccess(String); - impl<'de> EnumAccess<'de> for ValueEnumAccess { - type Error = de::value::Error; - type Variant = ValueVariantAccess; - - fn variant_seed( - self, - seed: V, - ) -> std::result::Result<(V::Value, Self::Variant), Self::Error> - where - V: de::DeserializeSeed<'de>, - { - seed.deserialize(self.0.into_deserializer()) - .map(|v| (v, ValueVariantAccess)) - } - } - - struct ValueVariantAccess; - impl<'de> VariantAccess<'de> for ValueVariantAccess { - type Error = de::value::Error; - - fn unit_variant(self) -> std::result::Result<(), Self::Error> { - Ok(()) - } - - fn newtype_variant_seed( - self, - _seed: T, - ) -> std::result::Result - where - T: de::DeserializeSeed<'de>, - { - Err(de::Error::custom("newtype_variant not supported")) - } - - fn tuple_variant( - self, - _len: usize, - _visitor: V, - ) -> std::result::Result - where - V: Visitor<'de>, - { - Err(de::Error::custom("tuple_variant not supported")) - } - - fn struct_variant( - self, - _fields: &'static [&'static str], - _visitor: V, - ) -> std::result::Result - where - V: Visitor<'de>, - { - Err(de::Error::custom("struct_variant not supported")) - } - } - - match self.value { - Value::Text(s) => visitor - .visit_enum(ValueEnumAccess(s)) - .map_err(de::Error::custom), - - _ => Err(de::Error::invalid_type( - de::Unexpected::Other(&format!("{:?}", self.value)), - &"a valid sqlite enum representation", - )), - } - } - } - - impl<'de, E: de::Error> IntoDeserializer<'de, E> for Value { - type Deserializer = ValueDeserializer; - - fn into_deserializer(self) -> Self::Deserializer { - ValueDeserializer { - value: self, - _pth: PhantomData, - } - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[test] - fn test_deserialize_value() { - fn de<'de, T>(value: Value) -> std::result::Result - where - T: Deserialize<'de>, - { - T::deserialize(value.into_deserializer()) - } - - #[derive(Deserialize, Debug, PartialEq)] - enum MyEnum { - A, - B, - } - - assert_eq!(de::(Value::Text("A".to_string())), Ok(MyEnum::A)); - assert_eq!(de::<()>(Value::Null), Ok(())); - assert_eq!(de::(Value::Integer(123)), Ok(123)); - assert_eq!(de::(Value::Real(123.4)), Ok(123.4)); - assert_eq!( - de::(Value::Text("abc".to_string())), - Ok("abc".to_string()) - ); - assert_eq!( - de::>(Value::Blob(b"abc".to_vec())), - Ok(b"abc".to_vec()) - ); - - assert_eq!(de::>(Value::Null), Ok(None)); - assert_eq!(de::>>(Value::Null), Ok(None)); - assert_eq!(de::>(Value::Integer(123)), Ok(Some(123))); - assert_eq!(de::>(Value::Real(123.4)), Ok(Some(123.4))); - - assert!(de::(Value::Null).is_err()); - assert!(de::>(Value::Null).is_err()); - assert!(de::(Value::Blob(b"abc".to_vec())).is_err()); - assert!(de::(Value::Text("C".to_string())).is_err()); - - assert_eq!(de::<[u8; 2]>(Value::Blob(b"aa".to_vec())), Ok([97, 97])); - } - } -} diff --git a/vendor/libsql/src/wasm/cloudflare.rs b/vendor/libsql/src/wasm/cloudflare.rs deleted file mode 100644 index 94b62f48..00000000 --- a/vendor/libsql/src/wasm/cloudflare.rs +++ /dev/null @@ -1,96 +0,0 @@ -use crate::hrana::{HranaError, HttpBody, HttpSend, Result}; -use bytes::Bytes; -use futures::{ready, Stream}; -use std::future::Future; -use std::io::ErrorKind; -use std::pin::Pin; -use std::sync::Arc; -use std::task::{Context, Poll}; -use worker::wasm_bindgen::JsValue; - -#[derive(Debug, Copy, Clone)] -pub struct CloudflareSender(()); - -impl CloudflareSender { - pub(crate) fn new() -> Self { - CloudflareSender(()) - } - - async fn send(url: Arc, auth: Arc, body: String) -> Result> { - use worker::{ - CfProperties, Fetch, Headers, Method, Request, RequestInit, RequestRedirect, - ResponseBody, - }; - - let mut response = Fetch::Request(Request::new_with_init( - url.as_ref(), - &RequestInit { - body: Some(JsValue::from(body)), - headers: { - let mut headers = Headers::new(); - headers.append("Authorization", auth.as_ref())?; - headers - }, - cf: CfProperties::new(), - method: Method::Post, - redirect: RequestRedirect::Follow, - }, - )?) - .send() - .await?; - if response.status_code() != 200 { - let body = response.text().await?; - Err(HranaError::Api(body)) - } else { - let body: HttpBody = match response.body() { - ResponseBody::Empty => HttpBody::from(Bytes::new()), - ResponseBody::Body(body) => HttpBody::from(Bytes::from(body.clone())), - _ => HttpBody::Stream(HttpStream(response.stream()?)), - }; - Ok(body) - } - } -} - -impl HttpSend for CloudflareSender { - type Stream = HttpBody; - type Result = Pin>>>; - - fn http_send(&self, url: Arc, auth: Arc, body: String) -> Self::Result { - let fut = Self::send(url, auth, body); - Box::pin(fut) - } - - fn oneshot(self, url: Arc, auth: Arc, body: String) { - worker::wasm_bindgen_futures::spawn_local(async move { - let _ = Self::send(url, auth, body).await; - }); - } -} - -impl From for HranaError { - fn from(value: worker::Error) -> Self { - // This converts it to a string due to the error type being !Send/!Sync which will break - // a lot of stuff. - HranaError::Http(value.to_string()) - } -} - -pub struct HttpStream(worker::ByteStream); - -impl Stream for HttpStream { - type Item = std::io::Result; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let pin = Pin::new(&mut self.0); - let res = ready!(pin.poll_next(cx)); - match res { - None => Poll::Ready(None), - Some(Ok(data)) => Poll::Ready(Some(Ok(Bytes::from(data)))), - Some(Err(e)) => Poll::Ready(Some(Err(std::io::Error::new( - ErrorKind::Other, - e.to_string(), - )))), - } - } -} diff --git a/vendor/libsql/src/wasm/mod.rs b/vendor/libsql/src/wasm/mod.rs deleted file mode 100644 index 18a0a44e..00000000 --- a/vendor/libsql/src/wasm/mod.rs +++ /dev/null @@ -1,190 +0,0 @@ -//! This module contains a special [`Connection`] struct that can be used in -//! constrained wasm environments. This struct is separate from the main connection -//! struct in the root of the crate due to the nature of some wasm clients requiring -//! `!Send`/`!Sync` support. -//! -//! To use these connections in wasm, you must disable default features then enable -//! the backend below that you would like. -//! -//! Currently implemented wasm http backends are: -//! - [cloudflare workers] which can be accessed via the `cloudflare` feature flag. -//! -//! [cloudflare workers]: https://developers.cloudflare.com/workers -//! -//! # Example `Cargo.toml` -//! ```toml,ignore,no_run -//! [packages] -//! libsql = { version = "*", default-features = false, features = ["cloudflare"] } -//! ``` -//! -//! # Example Rust usage -//! -//! ```rust,no_run -//! use libsql::wasm::Connection; -//! -//! let conn = Connection::open_cloudflare_worker("libsql://my-turso-db.turso.io", "my-auth-token"); -//! -//! conn.execute("select 1", ()).await?; -//! conn.query("select 1", ()).await?; -//! ``` -mod rows; - -use crate::hrana::transaction::{HttpTransaction, TxScopeCounter}; -use crate::hrana::unwrap_err; -use crate::{ - hrana::{connection::HttpConnection, HttpSend}, - params::IntoParams, - TransactionBehavior, -}; -use libsql_hrana::proto::{Batch, Stmt}; - -pub use crate::wasm::rows::Rows; - -cfg_cloudflare! { - mod cloudflare; - pub use cloudflare::CloudflareSender; -} - -#[derive(Debug, Clone)] -pub struct Connection -where - T: HttpSend + Sync + Send + 'static, -{ - conn: HttpConnection, -} - -cfg_cloudflare! { - impl Connection { - pub fn open_cloudflare_worker(url: impl Into, auth_token: impl Into) -> Self { - Connection { - conn: HttpConnection::new(url.into(), auth_token.into(), CloudflareSender::new()), - } - } - } -} - -impl Connection -where - T: HttpSend + Sync + Send + 'static, - ::Stream: 'static, -{ - pub async fn execute(&self, sql: &str, params: impl IntoParams) -> crate::Result { - tracing::trace!("executing `{}`", sql); - let stmt = crate::hrana::Statement::new( - self.conn.current_stream().clone(), - sql.to_string(), - true, - ).await?; - let rows = stmt.execute(¶ms.into_params()?).await?; - Ok(rows as u64) - } - - pub async fn execute_batch(&self, sql: &str) -> crate::Result<()> { - let mut stmts = Vec::new(); - let parse = crate::parser::Statement::parse(sql); - let mut c = TxScopeCounter::default(); - for s in parse { - let s = s?; - c.count(s.kind); - stmts.push(Stmt::new(s.stmt, false)); - } - let stream = self.conn.current_stream(); - let in_tx_scope = !stream.is_autocommit() || c.begin_tx(); - let close = !in_tx_scope || c.end_tx(); - let res = stream - .batch_inner(Batch::from_iter(stmts), close) - .await - .map_err(|e| crate::Error::Hrana(e.into()))?; - unwrap_err(&res) - } - - pub async fn query(&self, sql: &str, params: impl IntoParams) -> crate::Result { - tracing::trace!("querying `{}`", sql); - let stmt = crate::hrana::Statement::new( - self.conn.current_stream().clone(), - sql.to_string(), - true, - ).await?; - let rows = stmt.query_raw(¶ms.into_params()?).await?; - Ok(Rows { - inner: Box::new(rows), - }) - } - - pub async fn transaction( - &self, - tx_behavior: TransactionBehavior, - ) -> crate::Result> { - let stream = self.conn.open_stream(); - let tx = HttpTransaction::open(stream, tx_behavior) - .await - .map_err(|e| crate::Error::Hrana(e.into()))?; - Ok(Transaction { inner: tx }) - } -} - -#[derive(Debug, Clone)] -pub struct Transaction -where - T: HttpSend + Sync + Send + 'static, -{ - inner: HttpTransaction, -} - -impl Transaction -where - T: HttpSend + Sync + Send + 'static, - ::Stream: 'static, -{ - pub async fn query(&self, sql: &str, params: impl IntoParams) -> crate::Result { - tracing::trace!("querying `{}`", sql); - let stream = self.inner.stream().clone(); - let stmt = crate::hrana::Statement::new(stream, sql.to_string(), true).await?; - let rows = stmt.query_raw(¶ms.into_params()?).await?; - Ok(Rows { - inner: Box::new(rows), - }) - } - - pub async fn execute(&self, sql: &str, params: impl IntoParams) -> crate::Result { - tracing::trace!("executing `{}`", sql); - let stream = self.inner.stream().clone(); - let stmt = crate::hrana::Statement::new(stream, sql.to_string(), true).await?; - let rows = stmt.execute(¶ms.into_params()?).await?; - Ok(rows as u64) - } - - pub async fn execute_batch(&self, sql: &str) -> crate::Result<()> { - let mut statements = Vec::new(); - let stmts = crate::parser::Statement::parse(sql); - let mut c = TxScopeCounter::default(); - for s in stmts { - let s = s?; - c.count(s.kind); - statements.push(crate::hrana::proto::Stmt::new(s.stmt, false)); - } - - let stream = self.inner.stream(); - let in_tx_scope = !stream.is_autocommit() || c.begin_tx(); - let close = !in_tx_scope || c.end_tx(); - stream - .batch_inner(Batch::from_iter(statements), close) - .await - .map_err(|e| crate::Error::Hrana(e.into()))?; - Ok(()) - } - - pub async fn commit(&mut self) -> crate::Result<()> { - self.inner - .commit() - .await - .map_err(|e| crate::Error::Hrana(e.into())) - } - - pub async fn rollback(&mut self) -> crate::Result<()> { - self.inner - .rollback() - .await - .map_err(|e| crate::Error::Hrana(e.into())) - } -} diff --git a/vendor/libsql/src/wasm/rows.rs b/vendor/libsql/src/wasm/rows.rs deleted file mode 100644 index 954dd02d..00000000 --- a/vendor/libsql/src/wasm/rows.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crate::hrana::HranaRows; -use crate::hrana::HttpSend; -use crate::Row; -use bytes::Bytes; -use futures::Stream; - -pub struct Rows { - pub(super) inner: Box, -} - -impl Rows { - pub async fn next(&mut self) -> crate::Result> { - self.inner.next().await - } - - pub fn column_count(&self) -> i32 { - self.inner.column_count() - } - - pub fn column_name(&self, idx: i32) -> Option<&str> { - self.inner.column_name(idx) - } - - /// Converts current [crate::Rows] into asynchronous stream, fetching rows - /// one by one. This stream can be further used with [futures::StreamExt] - /// operators. - pub fn into_stream(mut self) -> impl Stream> + Unpin { - Box::pin(async_stream::try_stream! { - if let Some(row) = self.next().await? { - yield row - } - }) - } -} - -#[async_trait::async_trait(?Send)] -pub(super) trait RowsInner { - async fn next(&mut self) -> crate::Result>; - - fn column_count(&self) -> i32; - - fn column_name(&self, idx: i32) -> Option<&str>; -} - -#[async_trait::async_trait(?Send)] -impl RowsInner for HranaRows -where - T: HttpSend + Sync + Send + 'static, - S: Stream> + Unpin, -{ - async fn next(&mut self) -> crate::Result> { - self.next().await - } - - fn column_count(&self) -> i32 { - self.column_count() - } - - fn column_name(&self, idx: i32) -> Option<&str> { - self.column_name(idx) - } -} diff --git a/vendor/libsql/tests/encryption.rs b/vendor/libsql/tests/encryption.rs deleted file mode 100644 index b6cc9267..00000000 --- a/vendor/libsql/tests/encryption.rs +++ /dev/null @@ -1,52 +0,0 @@ -use libsql::{params, Builder}; -use libsql_sys::{Cipher, EncryptionConfig}; - -#[tokio::test] -#[cfg(feature = "encryption")] -async fn test_encryption() { - let tempdir = std::env::temp_dir(); - let encrypted_path = tempdir.join("encrypted.db"); - let base_path = tempdir.join("base.db"); - - // lets create an encrypted database - { - let mut db_builder = Builder::new_local(&encrypted_path); - db_builder = db_builder.encryption_config(EncryptionConfig { - cipher: Cipher::Aes256Cbc, - encryption_key: "s3cR3t".into(), - }); - let db = db_builder.build().await.unwrap(); - - let conn = db.connect().unwrap(); - conn.execute("CREATE TABLE IF NOT EXISTS messages (text TEXT)", ()) - .await - .unwrap(); - let params = params!["the only winning move is not to play"]; - conn.execute("INSERT INTO messages (text) VALUES (?)", params) - .await - .unwrap(); - } - - // lets test encryption with ATTACH - { - let db = Builder::new_local(&base_path).build().await.unwrap(); - let conn = db.connect().unwrap(); - let attach_stmt = format!( - "ATTACH DATABASE '{}' AS encrypted KEY 's3cR3t'", - tempdir.join("encrypted.db").display() - ); - conn.execute(&attach_stmt, ()).await.unwrap(); - let mut attached_results = conn - .query("SELECT * FROM encrypted.messages", ()) - .await - .unwrap(); - let row = attached_results.next().await.unwrap().unwrap(); - let text: String = row.get(0).unwrap(); - assert_eq!(text, "the only winning move is not to play"); - } - - { - let _ = std::fs::remove_file(&encrypted_path); - let _ = std::fs::remove_file(&base_path); - } -} diff --git a/vendor/libsql/tests/integration_tests.rs b/vendor/libsql/tests/integration_tests.rs deleted file mode 100644 index 697ac220..00000000 --- a/vendor/libsql/tests/integration_tests.rs +++ /dev/null @@ -1,927 +0,0 @@ -#![allow(deprecated)] - -use futures::{StreamExt, TryStreamExt}; -use libsql::{ - named_params, params, - params::{IntoParams, IntoValue}, - AuthAction, Authorization, Connection, Database, Op, Result, Value, -}; -use rand::distributions::Uniform; -use rand::prelude::*; -use std::collections::HashSet; -use std::sync::{Arc, Mutex}; - -async fn setup() -> Connection { - let db = Database::open(":memory:").unwrap(); - let conn = db.connect().unwrap(); - let _ = conn - .execute("CREATE TABLE users (id INTEGER, name TEXT)", ()) - .await; - conn -} - -#[tokio::test] -async fn enable_disable_extension() { - let db = Database::open(":memory:").unwrap(); - let conn = db.connect().unwrap(); - conn.load_extension_enable().unwrap(); - conn.load_extension_disable().unwrap(); -} - -#[tokio::test] -async fn add_update_hook() { - let conn = setup().await; - - #[derive(PartialEq, Debug)] - struct Data { - op: Op, - db: String, - table: String, - row_id: i64, - } - - let d = Arc::new(Mutex::new(None::)); - - let d_clone = d.clone(); - conn.add_update_hook(Box::new(move |op, db, table, row_id| { - *d_clone.lock().unwrap() = Some(Data { - op, - db: db.to_string(), - table: table.to_string(), - row_id, - }); - })) - .unwrap(); - - let _ = conn - .execute("INSERT INTO users (id, name) VALUES (2, 'Alice')", ()) - .await - .unwrap(); - - assert_eq!( - *d.lock().unwrap().as_ref().unwrap(), - Data { - op: Op::Insert, - db: "main".to_string(), - table: "users".to_string(), - row_id: 1, - } - ); - - let _ = conn - .execute("UPDATE users SET name = 'Bob' WHERE id = 2", ()) - .await - .unwrap(); - - assert_eq!( - *d.lock().unwrap().as_ref().unwrap(), - Data { - op: Op::Update, - db: "main".to_string(), - table: "users".to_string(), - row_id: 1, - } - ); - - let _ = conn - .execute("DELETE FROM users WHERE id = 2", ()) - .await - .unwrap(); - - assert_eq!( - *d.lock().unwrap().as_ref().unwrap(), - Data { - op: Op::Delete, - db: "main".to_string(), - table: "users".to_string(), - row_id: 1, - } - ); -} - -#[tokio::test] -async fn connection_drops_before_statements() { - let db = Database::open(":memory:").unwrap(); - let conn = db.connect().unwrap(); - let _stmt = conn.prepare("SELECT 1").await.unwrap(); - drop(conn); -} - -#[tokio::test] -async fn file_prefix_open() { - let tempdir = std::env::temp_dir(); - - let path = tempdir.join("prefixeddata.db"); - - let path = format!("file:{}", path.display()); - - let db = Database::open(path).unwrap(); - let conn = db.connect().unwrap(); - let _stmt = conn.prepare("SELECT 1").await.unwrap(); - drop(conn); -} - -#[tokio::test] -async fn connection_query() { - let conn = setup().await; - let _ = conn - .execute("INSERT INTO users (id, name) VALUES (2, 'Alice')", ()) - .await - .unwrap(); - let mut rows = conn.query("SELECT * FROM users", ()).await.unwrap(); - let row = rows.next().await.unwrap().unwrap(); - assert_eq!(row.get::(0).unwrap(), 2); - assert_eq!(row.get::(1).unwrap(), "Alice"); -} - -#[tokio::test] -async fn connection_execute_transactional_batch_success() { - let conn = setup().await; - - conn.execute_transactional_batch( - "CREATE TABLE foo(x INTEGER); - CREATE TABLE bar(y TEXT);", - ) - .await - .unwrap(); - - let mut rows = conn - .query( - "SELECT - name - FROM - sqlite_schema - WHERE - type ='table' AND - name NOT LIKE 'sqlite_%';", - (), - ) - .await - .unwrap(); - - let row = rows.next().await.unwrap().unwrap(); - assert_eq!(row.get::(0).unwrap(), "users"); - - let row = rows.next().await.unwrap().unwrap(); - assert_eq!(row.get::(0).unwrap(), "foo"); - - let row = rows.next().await.unwrap().unwrap(); - assert_eq!(row.get::(0).unwrap(), "bar"); - - assert!(rows.next().await.unwrap().is_none()); -} - -#[tokio::test] -async fn connection_execute_transactional_batch_fail() { - let conn = setup().await; - - let res = conn - .execute_transactional_batch( - "CREATE TABLE unexpected_foo(x INTEGER); - CREATE TABLE sqlite_schema(y TEXT); - CREATE TABLE unexpected_bar(y TEXT);", - ) - .await; - assert!(res.is_err()); - - let mut rows = conn - .query( - "SELECT - name - FROM - sqlite_schema - WHERE - type ='table' AND - name NOT LIKE 'sqlite_%';", - (), - ) - .await - .unwrap(); - - let row = rows.next().await.unwrap().unwrap(); - assert_eq!(row.get::(0).unwrap(), "users"); - - assert!(rows.next().await.unwrap().is_none()); -} - -#[tokio::test] -async fn connection_execute_transactional_batch_transaction_fail() { - let conn = setup().await; - - let res = conn - .execute_transactional_batch( - "BEGIN; - CREATE TABLE unexpected_foo(x INTEGER); - COMMIT; - CREATE TABLE sqlite_schema(y TEXT); - CREATE TABLE unexpected_bar(y TEXT);", - ) - .await; - assert!(res.is_err()); - - let mut rows = conn - .query( - "SELECT - name - FROM - sqlite_schema - WHERE - type ='table' AND - name NOT LIKE 'sqlite_%';", - (), - ) - .await - .unwrap(); - - let row = rows.next().await.unwrap().unwrap(); - assert_eq!(row.get::(0).unwrap(), "users"); - - assert!(rows.next().await.unwrap().is_none()); -} - -#[tokio::test] -async fn connection_execute_transactional_batch_transaction_incorrect() { - let conn = setup().await; - - let res = conn - .execute_transactional_batch( - "COMMIT; - CREATE TABLE unexpected_foo(x INTEGER); - CREATE TABLE sqlite_schema(y TEXT); - CREATE TABLE unexpected_bar(y TEXT);", - ) - .await; - assert!(res.is_err()); - - let mut rows = conn - .query( - "SELECT - name - FROM - sqlite_schema - WHERE - type ='table' AND - name NOT LIKE 'sqlite_%';", - (), - ) - .await - .unwrap(); - - let row = rows.next().await.unwrap().unwrap(); - assert_eq!(row.get::(0).unwrap(), "users"); - - assert!(rows.next().await.unwrap().is_none()); -} - -#[tokio::test] -async fn connection_execute_batch() { - let conn = setup().await; - - conn.execute_batch( - "BEGIN; - CREATE TABLE foo(x INTEGER); - CREATE TABLE bar(y TEXT); - COMMIT;", - ) - .await - .unwrap(); - - let mut rows = conn - .query( - "SELECT - name - FROM - sqlite_schema - WHERE - type ='table' AND - name NOT LIKE 'sqlite_%';", - (), - ) - .await - .unwrap(); - - let row = rows.next().await.unwrap().unwrap(); - assert_eq!(row.get::(0).unwrap(), "users"); - - let row = rows.next().await.unwrap().unwrap(); - assert_eq!(row.get::(0).unwrap(), "foo"); - - let row = rows.next().await.unwrap().unwrap(); - assert_eq!(row.get::(0).unwrap(), "bar"); -} - -#[tokio::test] -async fn connection_execute_batch_inserts() { - let conn = setup().await; - - conn.execute("CREATE TABLE foo(x INTEGER)", ()) - .await - .unwrap(); - - conn.execute_batch( - "BEGIN; - INSERT INTO foo VALUES (1); - INSERT INTO foo VALUES (2); - INSERT INTO foo VALUES (3); - COMMIT; - ", - ) - .await - .unwrap(); - - let mut rows = conn.query("SELECT count(*) FROM foo", ()).await.unwrap(); - - let count = rows.next().await.unwrap().unwrap().get::(0).unwrap(); - assert_eq!(count, 3); -} - -#[tokio::test] -async fn connection_execute_batch_inserts_returning() { - let conn = setup().await; - - conn.execute("CREATE TABLE foo(x INTEGER)", ()) - .await - .unwrap(); - - let mut batch_rows = conn - .execute_batch( - "BEGIN; - INSERT INTO foo VALUES (1) RETURNING *; - INSERT INTO foo VALUES (2) RETURNING *; - INSERT INTO foo VALUES (3) RETURNING *; - COMMIT; - ", - ) - .await - .unwrap(); - - assert!(batch_rows.next_stmt_row().unwrap().is_none()); - - let mut rows = batch_rows.next_stmt_row().unwrap().unwrap(); - assert_eq!( - rows.next().await.unwrap().unwrap().get::(0).unwrap(), - 1 - ); - let mut rows = batch_rows.next_stmt_row().unwrap().unwrap(); - assert_eq!( - rows.next().await.unwrap().unwrap().get::(0).unwrap(), - 2 - ); - - let mut rows = batch_rows.next_stmt_row().unwrap().unwrap(); - assert_eq!( - rows.next().await.unwrap().unwrap().get::(0).unwrap(), - 3 - ); - - assert!(batch_rows.next_stmt_row().unwrap().is_none()); -} - -#[tokio::test] -async fn connection_execute_batch_newline() { - // This test checks that we handle a null raw - // stament in execute_batch. What happens when there - // are no more queries but the sql string is not empty? - // Well sqlite returns a null statment from prepare_v2 - // so this test checks that we check if the statement is - // null before we try to step it! - let conn = setup().await; - - conn.execute_batch( - " - create table foo (x INT); - ", - ) - .await - .unwrap(); -} - -#[tokio::test] -async fn prepare_invalid_sql() { - let conn = setup().await; - let result = conn.prepare("SYNTAX ERROR").await; - assert!(result.is_err()); - let actual = result.err().unwrap(); - match actual { - libsql::Error::SqliteFailure(code, msg) => { - assert_eq!(code, 1); - assert_eq!(msg, "near \"SYNTAX\": syntax error".to_string()); - } - _ => panic!("Expected SqliteFailure"), - } -} - -#[tokio::test] -async fn statement_query() { - let conn = setup().await; - let _ = conn - .execute("INSERT INTO users (id, name) VALUES (2, 'Alice')", ()) - .await - .unwrap(); - - let params = [2]; - - let mut stmt = conn - .prepare("SELECT * FROM users WHERE id = ?1") - .await - .unwrap(); - - let mut rows = stmt.query(¶ms).await.unwrap(); - let row = rows.next().await.unwrap().unwrap(); - - assert_eq!(row.get::(0).unwrap(), 2); - assert_eq!(row.get::(1).unwrap(), "Alice"); - - stmt.reset(); - - let row = stmt.query_row(¶ms).await.unwrap(); - - assert_eq!(row.get::(0).unwrap(), 2); - assert_eq!(row.get::(1).unwrap(), "Alice"); - - stmt.reset(); - - let rows = stmt.query(¶ms).await.unwrap(); - let mut names = rows - .into_stream() - .boxed() - .map_ok(|r| r.get::(1).unwrap()); - - let name = names.next().await.unwrap().unwrap(); - - assert_eq!(name, "Alice"); -} - -#[tokio::test] -async fn prepare_and_query() { - let conn = setup().await; - check_insert( - &conn, - "INSERT INTO users (id, name) VALUES (2, 'Alice')", - (), - ) - .await; - check_insert( - &conn, - "INSERT INTO users (id, name) VALUES (?1, ?2)", - params![2u64, "Alice".to_string()], - ) - .await; - check_insert( - &conn, - "INSERT INTO users (id, name) VALUES (?1, ?2)", - params![2, "Alice"], - ) - .await; -} - -#[tokio::test] -async fn prepare_and_query_named_params() { - let conn = setup().await; - - conn.query("SELECT 1", named_params![]).await.unwrap(); - conn.query("SELECT 1", params![]).await.unwrap(); - conn.query("SELECT 1", ()).await.unwrap(); - - check_insert( - &conn, - "INSERT INTO users (id, name) VALUES (:a, :b)", - ((":a", 2), (":b", "Alice")), - ) - .await; - - check_insert( - &conn, - "INSERT INTO users (id, name) VALUES (:a, :b)", - named_params! { - ":a": 2, - ":b": "Alice" - }, - ) - .await; - - check_insert( - &conn, - "INSERT INTO users (id, name) VALUES (:a, :b)", - named_params! { - ":a": 2, - ":b": "Alice", - }, - ) - .await; - - check_insert( - &conn, - "INSERT INTO users (id, name) VALUES (@a, @b)", - named_params! { - "@a": 2, - "@b": "Alice", - }, - ) - .await; - - check_insert( - &conn, - "INSERT INTO users (id, name) VALUES ($a, $b)", - named_params! { - "$a": 2, - "$b": "Alice", - }, - ) - .await; - - check_insert( - &conn, - "INSERT INTO users (id, name) VALUES ($a, $b)", - named_params! { - "$a": 2, - "$b": "Alice", - }, - ) - .await; -} - -#[tokio::test] -async fn prepare_and_dont_query() { - // TODO: how can we check that we've cleaned up the statement? - - let conn = setup().await; - - conn.prepare("INSERT INTO users (id, name) VALUES (?1, ?2)") - .await - .unwrap(); - - // Drop the connection explicitly here to show that we want to drop - // it while the above statment has not been queryd. - drop(conn); -} - -async fn check_insert(conn: &Connection, sql: &str, params: impl IntoParams) { - let _ = conn.execute(sql, params).await.unwrap(); - let mut rows = conn.query("SELECT * FROM users", ()).await.unwrap(); - let row = rows.next().await.unwrap().unwrap(); - // Use two since if you forget to insert an id it will automatically - // be set to 1 which defeats the purpose of checking it here. - assert_eq!(row.get::(0).unwrap(), 2); - assert_eq!(row.get::(1).unwrap(), "Alice"); -} - -#[tokio::test] -async fn nulls() { - let conn = setup().await; - let _ = conn - .execute("INSERT INTO users (id, name) VALUES (NULL, NULL)", ()) - .await - .unwrap(); - let mut rows = conn.query("SELECT * FROM users", ()).await.unwrap(); - let row = rows.next().await.unwrap().unwrap(); - assert!(row.get::(0).is_err()); - assert!(row.get::(1).is_err()); -} - -#[tokio::test] -async fn blob() { - let conn = setup().await; - let _ = conn - .execute("CREATE TABLE bbb (id INTEGER PRIMARY KEY, data BLOB)", ()) - .await - .unwrap(); - - let bytes = vec![2u8; 64]; - let _ = conn - .execute("INSERT INTO bbb (data) VALUES (?1)", [bytes.clone()]) - .await - .unwrap(); - - let mut rows = conn.query("SELECT * FROM bbb", ()).await.unwrap(); - let row = rows.next().await.unwrap().unwrap(); - - let out = row.get::>(1).unwrap(); - assert_eq!(&out, &bytes); - - let empty: Vec = vec![]; - let mut rows = conn - .query( - "INSERT INTO bbb (data) VALUES (?1) RETURNING *", - [Value::Blob(empty.clone())], - ) - .await - .unwrap(); - let row = rows.next().await.unwrap().unwrap(); - assert_eq!(row.get::>(1).unwrap(), empty); -} - -#[tokio::test] -async fn transaction() { - let conn = setup().await; - conn.execute("INSERT INTO users (id, name) VALUES (2, 'Alice')", ()) - .await - .unwrap(); - let tx = conn.transaction().await.unwrap(); - tx.execute("INSERT INTO users (id, name) VALUES (3, 'Bob')", ()) - .await - .unwrap(); - tx.rollback().await.unwrap(); - let mut rows = conn.query("SELECT * FROM users", ()).await.unwrap(); - let row = rows.next().await.unwrap().unwrap(); - assert_eq!(row.get::(0).unwrap(), 2); - assert_eq!(row.get::(1).unwrap(), "Alice"); - assert!(rows.next().await.unwrap().is_none()); -} - -#[tokio::test] -async fn custom_params() { - let conn = setup().await; - - enum MyValue { - Text(String), - Int(i64), - } - - impl IntoValue for MyValue { - fn into_value(self) -> libsql::Result { - match self { - MyValue::Text(s) => Ok(Value::Text(s)), - MyValue::Int(i) => Ok(Value::Integer(i)), - } - } - } - - let params = vec![MyValue::Int(2), MyValue::Text("Alice".into())]; - - conn.execute( - "INSERT INTO users (id, name) VALUES (?1, ?2)", - libsql::params_from_iter(params), - ) - .await - .unwrap(); -} - -#[tokio::test] -async fn debug_print_row() { - let db = Database::open(":memory:").unwrap(); - let conn = db.connect().unwrap(); - let _ = conn - .execute( - "CREATE TABLE users (id INTEGER, name TEXT, score REAL, data BLOB, age INTEGER)", - (), - ) - .await; - conn.execute("INSERT INTO users (id, name, score, data, age) VALUES (123, 'potato', 3.14, X'deadbeef', NULL)", ()) - .await - .unwrap(); - - let stmt = conn.prepare("SELECT * FROM users").await.unwrap(); - let mut rows = stmt.query(()).await.unwrap(); - assert_eq!( - format!("{:?}", rows.next().await.unwrap().unwrap()), - "{Some(\"id\"): (Integer, 123), Some(\"name\"): (Text, \"potato\"), Some(\"score\"): (Real, 3.14), Some(\"data\"): (Blob, 4), Some(\"age\"): (Null, ())}" - ); -} - -#[tokio::test] -async fn fts5_invalid_tokenizer() { - let db = Database::open(":memory:").unwrap(); - let conn = db.connect().unwrap(); - assert!(conn - .execute( - "CREATE VIRTUAL TABLE t USING fts5(s, tokenize='trigram case_sensitive ')", - (), - ) - .await - .is_err()); - assert!(conn - .execute( - "CREATE VIRTUAL TABLE t USING fts5(s, tokenize='trigram remove_diacritics ')", - (), - ) - .await - .is_err()); -} - -#[cfg(feature = "serde")] -#[tokio::test] -async fn deserialize_row() { - let db = Database::open(":memory:").unwrap(); - let conn = db.connect().unwrap(); - let _ = conn - .execute( - "CREATE TABLE users (id INTEGER, name TEXT, score REAL, data BLOB, age INTEGER, status TEXT, wrapper TEXT)", - (), - ) - .await; - conn.execute("INSERT INTO users (id, name, score, data, age, status, wrapper) VALUES (123, 'potato', 42.0, X'deadbeef', NULL, 'Draft', 'Published')", ()) - .await - .unwrap(); - - use serde::Deserialize; - - #[derive(Deserialize, Debug)] - struct Data { - id: i64, - name: String, - score: f64, - data: Vec, - age: Option, - none: Option<()>, - status: Status, - wrapper: Wrapper, - } - - #[derive(Deserialize, Debug, PartialEq)] - enum Status { - Draft, - Published, - } - - #[derive(Deserialize, Debug, PartialEq)] - #[serde(transparent)] - struct Wrapper(Status); - - let row = conn - .query("SELECT * FROM users", ()) - .await - .unwrap() - .next() - .await - .unwrap() - .unwrap(); - let data: Data = libsql::de::from_row(&row).unwrap(); - assert_eq!(data.id, 123); - assert_eq!(data.name, "potato".to_string()); - assert_eq!(data.score, 42.0); - assert_eq!(data.data, vec![0xde, 0xad, 0xbe, 0xef]); - assert_eq!(data.age, None); - assert_eq!(data.none, None); - assert_eq!(data.status, Status::Draft); - assert_eq!(data.wrapper, Wrapper(Status::Published)); -} - -#[tokio::test] -#[ignore] -// fuzz test can be run explicitly with following command: -// cargo test vector_fuzz_test -- --nocapture --include-ignored -async fn vector_fuzz_test() { - let mut global_rng = rand::thread_rng(); - for attempt in 0..10000 { - let seed = global_rng.next_u64(); - - let mut rng = - rand::rngs::StdRng::from_seed(unsafe { std::mem::transmute([seed, seed, seed, seed]) }); - let db = Database::open(":memory:").unwrap(); - let conn = db.connect().unwrap(); - let dim = rng.gen_range(1..=1536); - let operations = rng.gen_range(1..128); - println!( - "============== ATTEMPT {} (seed {}u64, dim {}, operations {}) ================", - attempt, seed, dim, operations - ); - - let _ = conn - .execute( - &format!( - "CREATE TABLE users (id INTEGER PRIMARY KEY, v FLOAT32({}) )", - dim - ), - (), - ) - .await; - // println!("CREATE TABLE users (id INTEGER PRIMARY KEY, v FLOAT32({}) );", dim); - let _ = conn - .execute( - "CREATE INDEX users_idx ON users ( libsql_vector_idx(v) );", - (), - ) - .await; - // println!("CREATE INDEX users_idx ON users ( libsql_vector_idx(v) );"); - - let mut next_id = 1; - let mut alive = HashSet::new(); - let uniform = Uniform::new(-1.0, 1.0); - for _ in 0..operations { - let operation = rng.gen_range(0..4); - let vector: Vec = (0..dim).map(|_| rng.sample(uniform)).collect(); - let vector_str = format!( - "[{}]", - vector - .iter() - .map(|x| format!("{}", x)) - .collect::>() - .join(",") - ); - if operation == 0 { - // println!("INSERT INTO users VALUES ({}, vector('{}') );", next_id, vector_str); - conn.execute( - "INSERT INTO users VALUES (?, vector(?) )", - libsql::params![next_id, vector_str], - ) - .await - .unwrap(); - alive.insert(next_id); - next_id += 1; - } else if operation == 1 { - let id = rng.gen_range(0..next_id); - // println!("DELETE FROM users WHERE id = {};", id); - conn.execute("DELETE FROM users WHERE id = ?", libsql::params![id]) - .await - .unwrap(); - alive.remove(&id); - } else if operation == 2 && !alive.is_empty() { - let id = alive.iter().collect::>()[rng.gen_range(0..alive.len())]; - // println!("UPDATE users SET v = vector('{}') WHERE id = {};", vector_str, id); - conn.execute( - "UPDATE users SET v = vector(?) WHERE id = ?", - libsql::params![vector_str, id], - ) - .await - .unwrap(); - } else if operation == 3 { - let k = rng.gen_range(1..200); - // println!("SELECT * FROM vector_top_k('users_idx', '{}', {});", vector_str, k); - let result = conn - .query( - "SELECT * FROM vector_top_k('users_idx', ?, ?)", - libsql::params![vector_str, k], - ) - .await - .unwrap(); - let count = result.into_stream().count().await; - assert!(count <= alive.len()); - if alive.len() > 0 { - assert!(count > 0); - } - } - } - let _ = conn.execute("REINDEX users;", ()).await.unwrap(); - } -} - -#[tokio::test] -async fn test_deny_authorizer() { - let db = Database::open(":memory:").unwrap(); - let conn = db.connect().unwrap(); - conn.execute("CREATE TABLE users (id INTEGER, name TEXT)", ()) - .await - .unwrap(); - conn.authorizer(Some(Arc::new(|ctx| { - assert_eq!( - ctx.action, - AuthAction::Insert { - table_name: "users" - } - ); - assert_eq!(ctx.database_name, Some("main")); - assert_eq!(ctx.accessor, None); - Authorization::Deny - }))) - .unwrap(); - let res = conn - .execute("INSERT INTO users (id, name) VALUES (1, 'Alice')", ()) - .await; - assert_sqlite_error(res, libsql::ffi::SQLITE_AUTH); - conn.authorizer(None).unwrap(); - conn.execute("INSERT INTO users (id, name) VALUES (1, 'Alice')", ()) - .await - .unwrap(); -} - -#[tokio::test] -async fn test_ignore_authorizer() { - let db = Database::open(":memory:").unwrap(); - let conn = db.connect().unwrap(); - conn.execute("CREATE TABLE users (id INTEGER, name TEXT)", ()) - .await - .unwrap(); - conn.authorizer(Some(Arc::new(|ctx| { - assert_eq!( - ctx.action, - AuthAction::Insert { - table_name: "users" - } - ); - assert_eq!(ctx.database_name, Some("main")); - assert_eq!(ctx.accessor, None); - Authorization::Ignore - }))) - .unwrap(); - conn.execute("INSERT INTO users (id, name) VALUES (1, 'Alice')", ()) - .await - .unwrap(); - conn.authorizer(None).unwrap(); - let rows = conn.query("SELECT * FROM users", ()).await.unwrap(); - // There should be no rows - assert_eq!(rows.into_stream().count().await, 0); -} - -fn assert_sqlite_error(res: Result, code: i32) { - match res { - Ok(_) => panic!("Expected error, got Ok"), - Err(e) => { - if let libsql::Error::SqliteFailure(c, _) = e { - assert!(c == code, "Expected error code {}, got {}", code, c); - } else { - panic!("Expected SqliteFailure, got {:?}", e); - } - } - } -} diff --git a/vendor/libsql/tests/replication.rs b/vendor/libsql/tests/replication.rs deleted file mode 100644 index dff32b0f..00000000 --- a/vendor/libsql/tests/replication.rs +++ /dev/null @@ -1,164 +0,0 @@ -#![allow(deprecated)] - -use libsql::{replication::Frames, Database}; -use libsql_replication::{ - frame::{FrameBorrowed, FrameHeader, FrameMut}, - LIBSQL_PAGE_SIZE, -}; - -const DB: &[u8] = include_bytes!("test.db"); - -#[tokio::test] -async fn inject_frames() { - let tmp = tempfile::tempdir().unwrap(); - let db = Database::open_with_local_sync(tmp.path().join("data").to_str().unwrap(), None) - .await - .unwrap(); - - let mut frames: Vec = DB - .chunks(LIBSQL_PAGE_SIZE) - .enumerate() - .map(|(i, data)| { - let header = FrameHeader { - frame_no: (i as u64).into(), - checksum: 0.into(), - page_no: (i as u32 + 1).into(), - size_after: 0.into(), - }; - FrameBorrowed::from_parts(&header, data).into() - }) - .collect(); - - frames.last_mut().unwrap().header_mut().size_after = (frames.len() as u32).into(); - - let frames = frames.into_iter().map(Into::into).collect(); - - assert_eq!( - db.sync_frames(Frames::Vec(frames)).await.unwrap().unwrap(), - 2 - ); - - let conn = db.connect().unwrap(); - let mut rows = conn.query("select count(*) from test", ()).await.unwrap(); - assert_eq!( - *rows - .next() - .await - .unwrap() - .unwrap() - .get_value(0) - .unwrap() - .as_integer() - .unwrap(), - 10 - ); - - // inject the same frames again, this should be idempotent - let mut frames: Vec = DB - .chunks(LIBSQL_PAGE_SIZE) - .enumerate() - .map(|(i, data)| { - let header = FrameHeader { - frame_no: (i as u64 + 3).into(), - checksum: 0.into(), - page_no: (i as u32 + 1).into(), - size_after: 0.into(), - }; - FrameBorrowed::from_parts(&header, data).into() - }) - .collect(); - - frames.last_mut().unwrap().header_mut().size_after = (frames.len() as u32).into(); - - let frames = frames.into_iter().map(Into::into).collect(); - - assert_eq!( - db.sync_frames(libsql::replication::Frames::Vec(frames)) - .await - .unwrap() - .unwrap(), - 5 - ); - - let conn = db.connect().unwrap(); - let mut rows = conn.query("select count(*) from test", ()).await.unwrap(); - assert_eq!( - *rows - .next() - .await - .unwrap() - .unwrap() - .get_value(0) - .unwrap() - .as_integer() - .unwrap(), - 10 - ); -} - -#[tokio::test] -async fn inject_frames_split_txn() { - let tmp = tempfile::tempdir().unwrap(); - let db = Database::open_with_local_sync(tmp.path().join("data").to_str().unwrap(), None) - .await - .unwrap(); - - let mut frames = DB.chunks(LIBSQL_PAGE_SIZE).enumerate().map(|(i, data)| { - let header = FrameHeader { - frame_no: (i as u64).into(), - checksum: 0.into(), - page_no: (i as u32 + 1).into(), - size_after: 0.into(), - }; - FrameBorrowed::from_parts(&header, data) - }); - - let conn = db.connect().unwrap(); - assert!(conn.query("select count(*) from test", ()).await.is_err()); - - assert!(db - .sync_frames(Frames::Vec(vec![frames.next().unwrap().into()])) - .await - .unwrap() - .is_none()); - assert!(db.flush_replicator().await.unwrap().is_none()); - assert!(conn.query("select count(*) from test", ()).await.is_err()); - - assert!(db - .sync_frames(Frames::Vec(vec![frames.next().unwrap().into()])) - .await - .unwrap() - .is_none()); - assert!(db.flush_replicator().await.unwrap().is_none()); - assert!(conn.query("select count(*) from test", ()).await.is_err()); - - // commit frame - assert_eq!( - db.sync_frames(Frames::Vec(vec![frames - .next() - .map(|mut f| { - f.header_mut().size_after = 3.into(); - f - }) - .unwrap() - .into()])) - .await - .unwrap() - .unwrap(), - 2 - ); - assert_eq!(db.flush_replicator().await.unwrap().unwrap(), 2); - let mut rows = conn.query("select count(*) from test", ()).await.unwrap(); - assert_eq!( - *rows - .next() - .await - .unwrap() - .unwrap() - .get_value(0) - .unwrap() - .as_integer() - .unwrap(), - 10 - ); -} diff --git a/vendor/libsql/tests/template.db b/vendor/libsql/tests/template.db deleted file mode 100644 index c88900f44b08833bdc0dc131942a4003e8820ff8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8192 zcmeI#u?oU45XSLK5I51KxZbEBLiGi#5?s2deSwM*DvF@Nw-kIN6Y1{YCjXCIE(w`_ z+wC@G6TBX;w@V##E(5Vv7OEm*+luWI6ZcMgPA%QP#me;b>cwBC|CG200tg_000Iag zfB*srAbdFLo9-~n%sSNjvk(3rQrXqk6_wMfSe8!H%cwa_^pVG!jnix%hkk+p X0tg_000IagfB*srAbUB9~ML{_@H~42*EE+6VEEbE! zViCb&u~;k?i~oZ277kX>WHG#fc{@8hu*0Xlt&Pq>mb$(E;Ha0mB~??#M61pzrA(MM z%+bJmY4j4Fy0HFjY1HEOd^LPEa-rlg1P28uKmiI+fC3bt00k&O0SZun0!4uZQ;y?U zuVlTQ!?d66r-MA~`exGZCa&9F>m)AUa?O)gOPAwDLoes2v-CK_G~do*$2E6b#h;*? zSL9yFr+mn}yveJ)$g@1jL)he?00k&O0SZun0u-PC1t>rP3Q*vm2-u3zk)1?LAjT18 z#2BK4un2?DA&M7A|F6sGkNscn Date: Wed, 9 Sep 2026 19:27:36 -0400 Subject: [PATCH 3/4] fix(store): preserve remote Turso cleanup and retries --- crates/temper-store-turso/src/driver.rs | 45 ++++++++++- crates/temper-store-turso/src/driver/tests.rs | 75 +++++++++++++++++++ crates/temper-store-turso/src/retry.rs | 7 +- docs/efforts/ARN-467/decisions.md | 26 +++++++ 4 files changed, 151 insertions(+), 2 deletions(-) diff --git a/crates/temper-store-turso/src/driver.rs b/crates/temper-store-turso/src/driver.rs index dedf9edf..5451103a 100644 --- a/crates/temper-store-turso/src/driver.rs +++ b/crates/temper-store-turso/src/driver.rs @@ -11,7 +11,28 @@ pub(crate) enum DriverError { #[error(transparent)] Local(#[from] turso::Error), #[error(transparent)] - Remote(#[from] turso_serverless::Error), + Remote(turso_serverless::Error), + #[error("remote Turso busy: {0}")] + RemoteBusy(#[source] turso_serverless::Error), + #[error("remote Turso transport failure: {0}")] + RemoteTransport(#[source] turso_serverless::Error), +} + +impl From for DriverError { + fn from(error: turso_serverless::Error) -> Self { + match error { + turso_serverless::Error::Busy(_) | turso_serverless::Error::BusySnapshot(_) => { + Self::RemoteBusy(error) + } + turso_serverless::Error::Http(ref message) + if message.starts_with("request to ") + || message.starts_with("cursor stream failed:") => + { + Self::RemoteTransport(error) + } + _ => Self::Remote(error), + } + } } #[derive(Debug)] @@ -47,6 +68,28 @@ pub(crate) enum Connection { Remote(turso_serverless::Connection), } +impl Drop for Connection { + fn drop(&mut self) { + if let Self::Remote(conn) = self { + // The SDK defers rollback to connection reuse. Store operations own + // separate connections, so close the stream when their scope ends. + let conn = conn.clone(); + match tokio::runtime::Handle::try_current() { + Ok(runtime) => { + runtime.spawn(async move { + if let Err(error) = conn.close().await { + tracing::warn!(%error, "failed to close remote Turso connection"); + } + }); + } + Err(error) => { + tracing::warn!(%error, "cannot close remote Turso connection without a runtime"); + } + } + } + } +} + impl Connection { pub(crate) async fn query( &self, diff --git a/crates/temper-store-turso/src/driver/tests.rs b/crates/temper-store-turso/src/driver/tests.rs index 41e3447a..1b23f116 100644 --- a/crates/temper-store-turso/src/driver/tests.rs +++ b/crates/temper-store-turso/src/driver/tests.rs @@ -86,3 +86,78 @@ async fn query_executes_configuration_even_when_rows_are_discarded() { 17 ); } + +#[test] +fn remote_transient_errors_remain_retryable_without_retrying_permanent_errors() { + for error in [ + turso_serverless::Error::Busy("database is locked".into()), + turso_serverless::Error::BusySnapshot("snapshot conflict".into()), + turso_serverless::Error::Http("request to https://db.invalid/v3/pipeline failed: error sending request for url (https://db.invalid/v3/pipeline)".into()), + turso_serverless::Error::Http("cursor stream failed: error decoding response body".into()), + ] { + assert!(crate::retry::is_transient_write_error( + &DriverError::from(error).to_string() + )); + } + for error in [ + turso_serverless::Error::Constraint("UNIQUE constraint failed: proof.id".into()), + turso_serverless::Error::Readonly("attempt to write a readonly database".into()), + turso_serverless::Error::Http("HTTP status 401 Unauthorized".into()), + turso_serverless::Error::Http("invalid pipeline response: missing field results".into()), + ] { + assert!(!crate::retry::is_transient_write_error( + &DriverError::from(error).to_string() + )); + } +} + +#[tokio::test] +async fn dropping_remote_connection_closes_its_transaction_stream() { + use serde_json::json; + use wiremock::matchers::{body_partial_json, method, path}; + use wiremock::{Mock, MockServer, ResponseTemplate}; + + let server = MockServer::start().await; + Mock::given(method("POST")) + .and(path("/v3/pipeline")) + .and(body_partial_json(json!({"requests": [ + {"type": "execute", "stmt": {"sql": "BEGIN IMMEDIATE"}}, + {"type": "get_autocommit"} + ]}))) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "baton": "transaction-stream", "base_url": null, + "results": [ + {"type":"ok", "response":{"type":"execute", "result":{ + "cols":[], "rows":[], "affected_row_count":0, "last_insert_rowid":null + }}}, + {"type":"ok", "response":{"type":"get_autocommit", "is_autocommit":false}} + ] + }))) + .expect(1) + .mount(&server) + .await; + let close = Mock::given(method("POST")) + .and(path("/v3/pipeline")) + .and(body_partial_json(json!({ + "baton":"transaction-stream", "requests":[{"type":"close"}] + }))) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "baton":null, "base_url":null, + "results":[{"type":"ok", "response":{"type":"close"}}] + }))) + .expect(1) + .mount_as_scoped(&server) + .await; + + let db = Database::remote(&server.uri(), "test-token").await.unwrap(); + let conn = db.connect().unwrap(); + let tx = conn.begin_immediate().await.unwrap(); + drop(tx); + drop(conn); + tokio::time::timeout( + std::time::Duration::from_secs(2), + close.wait_until_satisfied(), + ) + .await + .expect("dropping the connection must send Close, not wait for server expiry"); +} diff --git a/crates/temper-store-turso/src/retry.rs b/crates/temper-store-turso/src/retry.rs index c3449022..7a084cb3 100644 --- a/crates/temper-store-turso/src/retry.rs +++ b/crates/temper-store-turso/src/retry.rs @@ -38,6 +38,9 @@ pub(crate) fn retry_delay_ms(retry_index: usize) -> u64 { /// that retry with backoff can reasonably recover from. /// /// Matched patterns: +/// - The adapter's typed remote Busy/BusySnapshot marker. +/// - The adapter's remote request/cursor transport-failure marker. HTTP status +/// failures and malformed responses do not receive this marker. /// - Hrana `BLOCKED` code (free-tier write quota, operator quota hold, /// maintenance pause). The actual wire error shape from libsql is: /// `Hrana: stream error: Error { message: "Operation was blocked: …", @@ -60,7 +63,9 @@ pub(crate) fn is_transient_write_error(err_msg: &str) -> bool { // translates this to ConcurrencyViolation. Never retry. return false; } - lower.contains("blocked") + lower.contains("remote turso busy:") + || lower.contains("remote turso transport failure:") + || lower.contains("blocked") || lower.contains("operation was blocked") || lower.contains("stream error") || lower.contains("connection reset") diff --git a/docs/efforts/ARN-467/decisions.md b/docs/efforts/ARN-467/decisions.md index 555f2119..748bf279 100644 --- a/docs/efforts/ARN-467/decisions.md +++ b/docs/efforts/ARN-467/decisions.md @@ -592,3 +592,29 @@ D30 caller review: Generic stream uploads also need to materialize their authori **Chose the adapter over caller changes or an engine patch because:** It preserves the same contract for every store query without a fork or scattered exceptions. The adapter buffers one row and marks exhausted results so subsequent reads cannot restart a completed statement. **Where:** `crates/temper-store-turso/src/driver.rs`; `crates/temper-store-turso/src/driver/tests.rs`; PR https://github.com/nerdsane/temper/pull/457. + +## D47 — Close remote streams when their connection leaves scope + +**Decision:** The private connection adapter schedules the official serverless client's `close()` when a remote connection drops. + +**Came up because:** Grok and Fable identified that the new SDK defers transaction rollback until connection reuse or stream closure. Temper opens a connection per store operation, so returning an error can drop both the transaction and connection; without Close, its server write lock remains until stream expiry. The previous driver scheduled Close on drop. + +**Options:** Add explicit cleanup to every store return path; maintain an engine/client fork; restore connection ownership cleanup in the private adapter. + +**Chose adapter cleanup over caller-wide changes or a fork because:** It covers success, early errors and cancelled operations at the resource boundary and uses the SDK's public Close operation. Cleanup runs on the existing Tokio runtime, matching the previous driver; shutdown without a runtime is reported rather than starting a separate runtime. + +**Where:** `crates/temper-store-turso/src/driver.rs`; the protocol-level close regression in `src/driver/tests.rs`; PR https://github.com/nerdsane/temper/pull/457. + +## D48 — Preserve retry classification for remote database contention + +**Decision:** Preserve the serverless SDK's typed Busy and BusySnapshot errors as a distinct private driver error whose stable text is recognized by the existing store retry boundary. + +**Came up because:** Codex showed that transparent error formatting removed the old Hrana stream-error marker. A hosted SQLITE_BUSY response then bypassed the existing retry budget even though retrying the complete operation is valid. + +**Options:** Match generic lock-message text for every backend; change the shared kernel persistence error API; retain the remote SDK's typed classification at the adapter boundary. + +**Chose typed remote classification because:** It restores remote contention retries without changing local-error behavior or treating constraints and read-only failures as retryable. The public persistence error contract remains unchanged; its existing string boundary receives an explicit remote-busy marker. + +**Where:** `crates/temper-store-turso/src/driver.rs`; `src/retry.rs`; `src/driver/tests.rs`; PR https://github.com/nerdsane/temper/pull/457. + +**D48 follow-up:** Fable also identified loss of network-error detail. The base libSQL sender uses Hyper's Display, which includes its source; the new SDK's request and cursor-stream errors retain only flattened messages. Classify those two transport-failure forms at the private adapter too, while leaving HTTP status failures and malformed responses non-transient. The pinned SDK has no finer transport cause available. Retrying a transport failure within the existing budget preserves recovery from resets; it can also retry another connection failure whose finer cause the SDK discarded. No new retry loop, budget, HTTP client or upstream patch is added. From f7cf2ba5b3c7bc384b5d4475ce853352b11f6b5f Mon Sep 17 00:00:00 2001 From: Rita Agafonova <36133358+rita-aga@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:48:02 -0400 Subject: [PATCH 4/4] fix(store): preserve the local lock wait timeout --- crates/temper-store-turso/src/driver.rs | 6 +++- crates/temper-store-turso/src/driver/tests.rs | 36 +++++++++++++++++++ docs/efforts/ARN-467/decisions.md | 13 +++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/crates/temper-store-turso/src/driver.rs b/crates/temper-store-turso/src/driver.rs index 5451103a..7988ac37 100644 --- a/crates/temper-store-turso/src/driver.rs +++ b/crates/temper-store-turso/src/driver.rs @@ -57,7 +57,11 @@ impl Database { pub(crate) fn connect(&self) -> Result { match self { - Self::Local(db) => Ok(Connection::Local(db.connect()?)), + Self::Local(db) => { + let conn = db.connect()?; + conn.busy_timeout(std::time::Duration::from_secs(5))?; + Ok(Connection::Local(conn)) + } Self::Remote(db) => Ok(Connection::Remote(db.connect()?)), } } diff --git a/crates/temper-store-turso/src/driver/tests.rs b/crates/temper-store-turso/src/driver/tests.rs index 1b23f116..82e3c40d 100644 --- a/crates/temper-store-turso/src/driver/tests.rs +++ b/crates/temper-store-turso/src/driver/tests.rs @@ -1,5 +1,41 @@ use super::*; +#[tokio::test] +async fn local_connection_waits_for_a_contended_write_lock() { + let directory = tempfile::tempdir().unwrap(); + let path = directory.path().join("contention.db"); + let db = Database::local(path.to_str().unwrap()).await.unwrap(); + let owner = db.connect().unwrap(); + owner + .execute("CREATE TABLE committed_values(id INTEGER PRIMARY KEY)", ()) + .await + .unwrap(); + let tx = owner.begin_immediate().await.unwrap(); + tx.execute("INSERT INTO committed_values VALUES (1)", ()) + .await + .unwrap(); + + let contender = db.connect().unwrap(); + let write = contender.execute("INSERT INTO committed_values VALUES (2)", ()); + tokio::pin!(write); + assert!( + tokio::time::timeout(std::time::Duration::from_millis(25), &mut write) + .await + .is_err(), + "a contended write must wait for the owning transaction" + ); + tx.commit().await.unwrap(); + assert_eq!(write.await.unwrap(), 1); + let mut rows = contender + .query("SELECT COUNT(*) FROM committed_values", ()) + .await + .unwrap(); + assert_eq!( + rows.next().await.unwrap().unwrap().get::(0).unwrap(), + 2 + ); +} + #[tokio::test] async fn local_rows_preserve_positional_and_named_values() { let db = Database::local(":memory:").await.unwrap(); diff --git a/docs/efforts/ARN-467/decisions.md b/docs/efforts/ARN-467/decisions.md index 748bf279..16cce333 100644 --- a/docs/efforts/ARN-467/decisions.md +++ b/docs/efforts/ARN-467/decisions.md @@ -618,3 +618,16 @@ D30 caller review: Generic stream uploads also need to materialize their authori **Where:** `crates/temper-store-turso/src/driver.rs`; `src/retry.rs`; `src/driver/tests.rs`; PR https://github.com/nerdsane/temper/pull/457. **D48 follow-up:** Fable also identified loss of network-error detail. The base libSQL sender uses Hyper's Display, which includes its source; the new SDK's request and cursor-stream errors retain only flattened messages. Classify those two transport-failure forms at the private adapter too, while leaving HTTP status failures and malformed responses non-transient. The pinned SDK has no finer transport cause available. Retrying a transport failure within the existing budget preserves recovery from resets; it can also retry another connection failure whose finer cause the SDK discarded. No new retry loop, budget, HTTP client or upstream patch is added. + + +## D49 — Preserve the local connection lock-wait default + +**Decision:** Set the official Turso connection busy timeout to five seconds when each local connection opens. + +**Came up because:** Fable found that the previous driver set this timeout on every connection while the new engine defaults to immediate Busy. Plain store connections, including OTS and tenant writes, do not apply the longer configured-writer timeout. The contended-write regression failed before this correction. + +**Options:** Add retries to individual callers; change write concurrency; restore the existing connection default through the public SDK. + +**Chose the connection setting because:** It preserves all callers and the existing longer configured timeout without another retry loop or engine patch. + +**Where:** `crates/temper-store-turso/src/driver.rs`; `src/driver/tests.rs`; PR https://github.com/nerdsane/temper/pull/457.