Skip to content

feat(plugin-libsql): open local libSQL database files from the connection form#1610

Merged
datlechin merged 4 commits into
mainfrom
fix/libsql-local-file
Jun 7, 2026
Merged

feat(plugin-libsql): open local libSQL database files from the connection form#1610
datlechin merged 4 commits into
mainfrom
fix/libsql-local-file

Conversation

@datlechin
Copy link
Copy Markdown
Member

Closes #1607.

Problem

The libSQL / Turso plugin only connects to remote databases over the Hrana HTTP protocol. There is no way to open a local libSQL database file, even though unencrypted libSQL files use the standard SQLite file format.

Change

One libSQL connection type, two backends:

  • The connection form gains a Connection Mode dropdown (Remote (Turso) / Local File). Remote keeps the existing Database URL and auth token fields. Local File shows a path field with a Browse... button (NSOpenPanel sheet, same pattern as the SQLite form). Field switching uses the existing visibleWhen rules; the password row hides in local mode via the existing hidesPassword dropdown semantics.
  • The driver dispatches through a Backend enum: remote keeps HranaHttpClient, local opens the file with the system SQLite3 framework through a new SQLiteLocalBackend actor (mirrors the SQLite plugin's connection actor). Local mode adds real BEGIN/COMMIT/ROLLBACK transactions, native parameter binding, sqlite3_interrupt cancellation, and batched row streaming.
  • The app-side registry defaults mirror the new field set so the form renders correctly before the plugin is installed.

Compatibility

  • No PluginKit ABI change. No new ConnectionMode or FieldType case (both are frozen); no version bump, no bulk plugin re-release.
  • Saved remote connections have no mode key and keep working unchanged; the auth token stays in the password slot.
  • libSQL claims no file extensions, so SQLite keeps file-open routing for .db / .sqlite drops.
  • The new plugin binary uses no new PluginKit symbols, so it loads on current app versions too.

Tests

  • New LibSQLConnectionFieldsTests (8 tests, passing): field set, remote default, visibility rules per mode, password hiding, no file-extension claim.
  • Driver-level connect() tests are not reachable from TableProTests because plugin sources belong only to their bundle targets (same as every other driver plugin).
  • Manual flow: create libSQL connection, switch to Local File, browse to a SQLite file, connect, browse tables, edit data, run queries; remote mode regression against sqld.

Release note

After merge, tag plugin-libsql-v1.1.0 to publish the updated binary to the registry. Bundled plugins are unaffected.

@mintlify
Copy link
Copy Markdown

mintlify Bot commented Jun 7, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟢 Ready View Preview Jun 7, 2026, 9:14 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 36b2307df7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +87 to +93
while sqlite3_step(statement) == SQLITE_ROW {
if rows.count >= PluginRowLimits.emergencyMax {
truncated = true
break
}
rows.append(rowValues(of: statement, count: columnCount))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Report sqlite3_step failures for local statements

In local libSQL mode, DML errors are returned from sqlite3_step, not sqlite3_prepare_v2; for example a UNIQUE/CHECK/FK violation, SQLITE_BUSY, or disk I/O error makes this loop exit with a non-SQLITE_ROW result and then fall through as a successful query. That means data edits can be reported as saved even though SQLite rejected them, so please capture the final step result and throw unless it is SQLITE_DONE (apart from the intentional truncation path).

Useful? React with 👍 / 👎.

@datlechin datlechin merged commit 9ce6577 into main Jun 7, 2026
4 checks passed
@datlechin datlechin deleted the fix/libsql-local-file branch June 7, 2026 09:23
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: df52bae4c5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let localBackend = SQLiteLocalBackend()
try await localBackend.open(path: path)
let rawHandle = await localBackend.dbHandleForInterrupt
let versionResult = try await localBackend.executeQuery("SELECT sqlite_version()")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate the selected file by reading the database schema

In local mode, this connection test can accept corrupt, encrypted, or non-SQLite files because SELECT sqlite_version() only returns the SQLite library version and does not read the database file; for example, the same handle will succeed here but fail on PRAGMA schema_version/sqlite_master with "file is not a database". This means Test Connection/Save can report success for an unusable local libSQL file, and the first sidebar/schema load then fails instead.

Useful? React with 👍 / 👎.

var batch: [PluginRow] = []
batch.reserveCapacity(batchSize)

while sqlite3_step(statement) == SQLITE_ROW {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Report step failures while streaming local rows

When exporting or otherwise streaming a local libSQL query, any sqlite3_step result other than SQLITE_ROW exits this loop and finishes the stream successfully. If SQLite returns SQLITE_BUSY, SQLITE_IOERR, or SQLITE_INTERRUPT after partial rows, the export/query stream is presented as complete instead of failed, so please retain the final step result and finish with an error unless it is SQLITE_DONE.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Turso database for local files

1 participant