Skip to content

Add portable ClientSQL runtime and debugger - #181

Open
bjdodson-openai wants to merge 1 commit into
Snapchat:mainfrom
bjdodson-openai:bjd/debugger-clientsql
Open

Add portable ClientSQL runtime and debugger#181
bjdodson-openai wants to merge 1 commit into
Snapchat:mainfrom
bjdodson-openai:bjd/debugger-clientsql

Conversation

@bjdodson-openai

@bjdodson-openai bjdodson-openai commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Description

Adds a dependency-complete portable ClientSQL implementation on top of the debugger capabilities landed from #180.

The single commit includes the hermetic ClientSQL generator, pinned SQLite toolchain, native and web runtimes, integration coverage, debugger provider, and a cross-platform Ledger SQL demo. The demo exercises reactive account and ledger queries, atomic transfers, reset behavior, and deterministic stress batches on macOS, iOS, and Android.

This revision also incorporates the review hardening requested on this PR: debug-database registration is inert unless runtime debugging is enabled; debugger query results are bounded; native transactions have timeout and exception-safe rollback paths; database teardown is safe from the coordinator queue; and the SQL generator now covers parameterized types, joins, nested constraints, WITHOUT ROWID, reserved identifiers, quoted identifiers and aliases, and scalar subqueries.

The SQLite source-archive provenance and global build-rule changes require the existing sensitive-file/import review.

This PR supersedes #176 and #177; those PRs retain the incremental review history.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • Documentation improvement
  • Performance optimization
  • Test improvement
  • Other (feature: portable ClientSQL runtime, debugger provider, and Ledger SQL demo)

Testing

  • Tests pass locally (bazel test //...)
  • Added/updated tests for changes (if applicable)
  • Tested on multiple platforms (iOS/Android/Web/macOS as applicable)
  • Manual testing performed (describe below)

Testing Details

  • bazel test //compiler/clientsql:test_clientsql — 24 tests passed
  • bazel test //src/valdi_modules/src/valdi/client_sql:client_sql_native_tests //valdi:test_client_sql_runtime_integration — passed
  • bazel build //src/valdi_modules/src/valdi/client_sql:client_sql_web //apps/ledger_sql_demo:ledger_sql_demo_ios — passed
  • bazel build //apps/ledger_sql_demo:ledger_sql_demo_android --snap_flavor=platform_development --copt=-DANDROID_WITH_JNI --repo_env=VALDI_PLATFORM_DEPENDENCIES=android --define=client_repo_arm64=true --android_platforms=@snap_platforms//os:android_arm64 — passed
  • Ledger SQL demo macOS build and launch — passed; hot reload connected to the running app and delivered changes
  • Repository-wide bazel query //... — passed
  • git diff --check — clean

The full repository-wide bazel test //... matrix was not run locally; CI remains the final platform matrix.

Checklist

  • Code follows project style guidelines
  • Documentation updated (if needed)
  • No breaking changes (or documented in description)
  • Commit messages follow conventional format
  • No secrets, API keys, or internal URLs included

Related Issues

Builds on the capabilities landing from #180.

Supersedes #176 and #177 while preserving their review history.

Additional Context

The single head commit is directly parented on the imported #180 capabilities landing at b83dc83ee3d3587c3a94a3634fed560df3b0ccdd. Review the ClientSQL-only range b83dc83ee3d3587c3a94a3634fed560df3b0ccdd..f3ce8a09d972dc54aeca945cb35d325d0727dc36.

@github-actions github-actions Bot added area/runtime Valdi runtime (C++/native) area/compiler Valdi compiler area/build-system Bazel build rules and config area/docs Documentation platform/ios iOS-specific platform/android Android-specific labels Aug 28, 2026
@github-actions

Copy link
Copy Markdown

Sensitive Files Detected

📦 Dependency change — Modifies the Bazel module graph — needs runtime team review after import.

📎 Prebuilt binary — Changes a prebuilt binary — requires verification of provenance.

🔧 Build rules — Affects build rules for all Valdi consumers.

This is an automated notice. A maintainer will review after import.

@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown

⚠️ Bazel & CI Test Results

Test Suite Result
API Surface Check ✅ success
Test Coverage Delta ✅ success
Linux: Registry Validation ✅ success
valdi_web Integration Test ✅ success
macOS: C++ & Platform Tests ❌ failure
Snapshot Tests ✅ success
Valdi Smoke Tests ❌ failure
Linux: Build Compiler ✅ success
Linux: Build & Export ✅ success
Linux: Hotreload Smoke ✅ success
Linux: C++ Tests ❌ failure
Linux: Module Tests ✅ success

Some tests failed. Please check the workflow logs for details.

🚀 Bazel remote cache is now enabled - future builds will be faster!

Workflow: Valdi CI

@bjdodson-openai
bjdodson-openai force-pushed the bjd/debugger-clientsql branch 2 times, most recently from f8f1449 to 406e3c4 Compare September 1, 2026 05:23
@github-actions github-actions Bot removed area/runtime Valdi runtime (C++/native) area/docs Documentation platform/ios iOS-specific platform/android Android-specific labels Sep 1, 2026
@bjdodson-openai
bjdodson-openai marked this pull request as ready for review September 1, 2026 05:48
@bjdodson-openai
bjdodson-openai marked this pull request as draft September 1, 2026 06:11
@bjdodson-openai
bjdodson-openai marked this pull request as ready for review September 1, 2026 16:42

@clholgat clholgat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice work — this is careful and thorough. Generated code is injection-safe throughout (values bound via ?, identifiers escaped, literals json.dumps'd), RAII is correct in the native layer, the SQLite provenance is documented and hash-pinned, and the tests genuinely compile + execute generated output rather than snapshotting strings. Agreed it should stay in draft until the @Version(__PLACEHOLDER__) annotations are finalized.

I've left inline comments. Grouping and priority:

Blocking / please confirm

  • Debugger auto-registration exposes read access to open DBs — is it gated to debug builds / authenticated? (comment on ClientSQLDebug.ts)
  • Native: unbounded result set (High), and two transaction-liveness gaps (Med) that can wedge the writer or std::terminate.

Generator — silently-wrong generated types, not crashes (worth fixing since they hit common SQL): parameterized column types, SELECT * across a JOIN, WITHOUT ROWID tables, and the await reserved word.

Two items I couldn't anchor cleanly inline:

  1. Teardown/GC path (Med). On the drop-without-explicit-close path, the last queued task dropping the final coordinator reference destroys the dispatch queue (and runs sqlite3_close) from within a task running on that same queue — a potential self-join deadlock/crash. The explicit-close path looks fine; this is the GC path, and it depends on the dispatch-queue destructor semantics, so worth a look.
  2. Non-transactional query() during an open write transaction reads the pre-transaction WAL snapshot (dispatched to the reader pool, not deferred like execute()). Defensible reader-pool semantics, but an easy caller foot-gun — worth documenting.

Suggested tests: three fixtures — a parameterized column type, SELECT * across a JOIN, and a WITHOUT ROWID table — would catch the top three generator findings directly.

Nothing here is a redesign; the structure is solid. Happy to dig deeper on any of these.

Comment thread src/valdi_modules/src/valdi/client_sql/src/ClientSQLDebug.ts
Comment thread compiler/clientsql/src/clientsql/sql.py
Comment thread compiler/clientsql/src/clientsql/sql.py
Comment thread compiler/clientsql/src/clientsql/sql.py
Comment thread compiler/clientsql/src/clientsql/sql.py Outdated
Comment thread MODULE.bazel Outdated
)

http_archive(
name = "sqlite",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this http_archive block is duplicated in bzl/dependencies.bzl for the WORKSPACE path. The consistency test helps catch drift, but a single shared definition would remove the hazard entirely.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at consolidating this. MODULE.bazel and the legacy WORKSPACE entry point cannot directly reuse the same repository-setup macro; moving the Bzlmod side behind a module extension would broaden this change considerably. I kept the declarations in their required entry points, added comments documenting why, and retained test_native_sqlite_dependency_shape_is_explicit_for_apple_and_default to pin both URLs and hashes and catch drift. Happy to pursue a module-extension cleanup separately if preferred.

@bjdodson-openai

Copy link
Copy Markdown
Collaborator Author

Addressed both unanchored findings as well:

  1. On the drop-without-close path, ClientSQLConnection now abandons its handle explicitly. When the final coordinator reference is released from one of its owned queues, the coordinator transfers its queue-owning writer/reader references to the fallback release queue before the current task unwinds, avoiding self-destruction/self-join. releasesFinalDroppedHandleWithoutClose verifies that the coordinator returns to the baseline live count.
  2. The reader-pool behavior is now documented under “Query snapshot semantics.” A non-transactional query() may observe the committed WAL snapshot available when its reader starts; it does not provide read-your-writes or writer callback ordering. The README directs callers to transaction.query() for transaction-local reads and queryOnWriter() for serialization behind writer work. allowsSeparateHandleSnapshotReadersDuringActiveNativeTransaction verifies the before/after-commit behavior.

I also added the suggested parameterized-type, joined-SELECT *, and WITHOUT ROWID generator fixtures.

@clholgat clholgat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fast turnaround — this addresses the review well. Confirmed on 8e1b957: result-set caps (row + byte, with a clear "add a LIMIT" error), the transaction watchdog plus broadened Exception& / std::exception& / ... catches, the isDebugEnabled trust-boundary comment, and on the generator the parameterized column types, WITHOUT ROWID, JOIN handling, and reserved-word set all look fixed. 👍

Left inline comments for the two small remaining items (teardown/GC path, NUL nit) and a couple of portability/gating requests (overridable SQLite labels, folding the new tests into //valdi:test).

One design question that informs how this slots into existing setups: is the emitted TypeScript intended to be output-compatible with SQLDelight v1, or is it a fresh contract? i.e. would an existing SQLDelight-v1 .sq consumer regenerate to equivalent bindings, or should they expect behavioral differences to revalidate? Not a blocker — just want to understand the migration story.

Nice work overall.

] + select({
"//bzl/conditions:ios": [],
"//bzl/conditions:macos": [],
"//conditions:default": ["@sqlite//:sqlite"],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Portability request: consider exposing the SQLite dependency as an overridable label (e.g. a label_flag defaulting to @sqlite//:sqlite) rather than hardcoding it here. That lets an embedder that already vendors SQLite point this at their copy and avoid linking a second amalgamation with duplicate sqlite3_* symbols — without patching this BUILD file. Same idea would help for the validator's @sqlite_316.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, and the internal ClientSQL context makes this broader than SQLite alone. I will add overridable runtime and validation SQLite labels, preserve the existing internal compiler seam, and make the whole native implementation selectable so an embedder links exactly one factory/runtime. I am leaving this open until the merged internal graph proves that the OSS SQLite and factory are absent when internal providers are selected.

Comment thread src/valdi_modules/src/valdi/client_sql/BUILD.bazel
Comment thread valdi/BUILD.bazel
@bjdodson-openai

bjdodson-openai commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Answering the compatibility question: this generator is a fresh Valdi-specific SQLDelight-style contract. It is not currently demonstrated to be output-compatible with SQLDelight v1 or Snap’s internal ClientSQL, so existing consumers should not regenerate against it without revalidation.

The current f3ce8a09 revision makes the public implementation coexistence-safe at the build boundary:

  1. It preserves the established @valdi_toolchain//:sqldelight_compiler seam and the exact legacy -p / -c / -m invocation contract.
  2. The portable generator is the public default behind that seam, while an internal embedder can retain its existing compiler target.
  3. Runtime SQLite, validation SQLite, the whole native implementation, and implementation-specific aggregate native tests are independently overrideable.
  4. The public SQLite repositories use collision-resistant valdi_clientsql_sqlite names.

Those changes prevent the open-source import from implicitly replacing Snap’s compiler or linking two native factories. They do not by themselves prove generated-binding, native-name, debugger-ID, database-path, or migration compatibility.

The remaining integration gate is:

  1. Compare the internal and portable generated TypeScript/native metadata and behavior using the same sanitized SQL fixture.
  2. Validate the merged internal graph: query, aggregate test link, Android/iOS exports, exactly one factory registration, and legacy database migration behavior.
  3. Choose either atomic convergence on one canonical implementation while preserving internal contracts and storage behavior, or a fully namespaced client_sql_v2 coexistence lane.

Generated artifacts are sufficient; internal source is not needed. The useful inputs are the current ClientSQL .d.ts or compilation metadata, compiler CLI plus one generated fixture, native registration and platform names, database path/migration semantics, debugger IDs, and SQLite/build labels. The remaining blocker is this integration choice, not a native API allocation step.

Consolidate the hermetic ClientSQL generator, pinned SQLite toolchain, native/web runtime, integration coverage, debugger provider, and smoke fixtures into one dependency-complete change.\n\nCarries six native API version annotations plus one matching generator expectation; the commit must remain draft until Snap allocates one concrete version and completes the sensitive-file/import review.
Comment on lines +249 to +257
[
str(CLIENTSQL_TOOLCHAIN_EXECUTABLE),
"-s", str(sql_dir),
"-p", "SharedDb",
"-c", "SharedDb",
"-m", "SharedDb",
"-o", str(output),
"-l", "typescript",
],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semgrep identified an issue in your code:
Detected subprocess function 'run' with user controlled data. A malicious actor could leverage this to perform command injection. You may consider using 'shlex.quote()'.

Dataflow graph
flowchart LR
    classDef invis fill:white, stroke: none
    classDef default fill:#e7f5ff, color:#1c7fd6, stroke: none

    subgraph File0["<b>compiler/clientsql/test_clientsql.py</b>"]
        direction LR
        %% Source

        subgraph Source
            direction LR

            v0["<a href=https://github.com/Snapchat/Valdi/blob/f3ce8a09d972dc54aeca945cb35d325d0727dc36/compiler/clientsql/test_clientsql.py#L25 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 25] os.environ</a>"]
        end
        %% Intermediate

        subgraph Traces0[Traces]
            direction TB

            v2["<a href=https://github.com/Snapchat/Valdi/blob/f3ce8a09d972dc54aeca945cb35d325d0727dc36/compiler/clientsql/test_clientsql.py#L28 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 28] path</a>"]

            v3["<a href=https://github.com/Snapchat/Valdi/blob/f3ce8a09d972dc54aeca945cb35d325d0727dc36/compiler/clientsql/test_clientsql.py#L25 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 25] value</a>"]

            v4["<a href=https://github.com/Snapchat/Valdi/blob/f3ce8a09d972dc54aeca945cb35d325d0727dc36/compiler/clientsql/test_clientsql.py#L34 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 34] environment_tool_path</a>"]

            v5["<a href=https://github.com/Snapchat/Valdi/blob/f3ce8a09d972dc54aeca945cb35d325d0727dc36/compiler/clientsql/test_clientsql.py#L34 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 34] CLIENTSQL_TOOLCHAIN_EXECUTABLE</a>"]

            v6["<a href=https://github.com/Snapchat/Valdi/blob/f3ce8a09d972dc54aeca945cb35d325d0727dc36/compiler/clientsql/test_clientsql.py#L34 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 34] CLIENTSQL_TOOLCHAIN_EXECUTABLE</a>"]
        end
            v2 --> v3
            v3 --> v4
            v4 --> v5
            v5 --> v6
        %% Sink

        subgraph Sink
            direction LR

            v1["<a href=https://github.com/Snapchat/Valdi/blob/f3ce8a09d972dc54aeca945cb35d325d0727dc36/compiler/clientsql/test_clientsql.py#L249 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 249] [<br>                    str(CLIENTSQL_TOOLCHAIN_EXECUTABLE),<br>                    &quot;-s&quot;, str(sql_dir),<br>                    &quot;-p&quot;, &quot;SharedDb&quot;,<br>                    &quot;-c&quot;, &quot;SharedDb&quot;,<br>                    &quot;-m&quot;, &quot;SharedDb&quot;,<br>                    &quot;-o&quot;, str(output),<br>                    &quot;-l&quot;, &quot;typescript&quot;,<br>                ]</a>"]
        end
    end
    %% Class Assignment
    Source:::invis
    Sink:::invis

    Traces0:::invis
    File0:::invis

    %% Connections

    Source --> Traces0
    Traces0 --> Sink


Loading

To resolve this comment:

🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.

💬 Ignore this finding

Reply with Semgrep commands to ignore this finding.

  • /fp <comment> for false positive
  • /ar <comment> for acceptable risk
  • /other <comment> for all other reasons

Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by dangerous-subprocess-use-tainted-env-args.

You can view more details about this finding in the Semgrep AppSec Platform.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/fp False positive: CLIENTSQL_TEST_TOOLCHAIN_EXECUTABLE is supplied by the Bazel py_test via $(rootpath :clientsql_toolchain), and subprocess.run receives a structured argv list with shell=False (the default). No value is interpreted by a shell.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/build-system Bazel build rules and config area/compiler Valdi compiler

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants