Skip to content

feat(plugins): send connection context to DDL builders and add drop_table_sql - #1199

Merged
debba merged 12 commits into
mainfrom
feat/plugin-ddl-context
Oct 18, 2026
Merged

debba merged 12 commits into
mainfrom
feat/plugin-ddl-context

Conversation

@debba

@debba debba commented Oct 18, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #629. Review only the commits after 02d65fbe; the base switches to main once #629 is merged.

Summary

Plugin drivers that model more than one database (for example projects holding datasets) generated DDL against the wrong database, because the DDL builder RPCs received params only with the connection_metadata opt-in. This PR gives plugin DDL builders the context they need and lets plugins provide the statements the host cannot build generically.

Changes

  • Connection context for DDL builders: get_create_table_sql, get_add_column_sql, get_alter_column_sql, get_create_index_sql, get_create_foreign_key_sql and routine_create_template always receive params with the browsed database applied. The commands take an optional database, passed by the Create Table, Modify Column, Create Index and Create FK modals.
    • The builder params are built without resolving SSH, SSM or Kubernetes tunnels (host and port stay as saved), so previews never open a tunnel and keep working offline. Keychain values and plugin-owned secrets are added when they can be read; a failed read still builds the preview.
    • Built-in drivers return early and behave exactly as before.
  • Column descriptions: optional ColumnDefinition.comment and a new optional capability column_comments. The Modify Column modal shows a Description input only when the driver declares it. i18n keys added to all locales. Built-in drivers are unchanged.
  • Plugin drop statement: new optional RPC get_drop_table_sql ({ params, table, schema, database } → string or null), gated by a new optional capability drop_table_sql like table_query_templates. Without the capability the explorer runs the generic DROP TABLE with no RPC and no credential read. null, a blank string, -32601 or any non-string result falls back to the generic statement.
  • Keyless row identity: float32 / float64 / binary_float / binary_double count as approximate numerics, and nested types (STRUCT, RECORD, REPEATED, MAP, TUPLE, NESTED, ARRAY<...>, RANGE<...>, ROW(...), VARIANT, OBJECT, SUPER) are excluded. Bare ARRAY, type[] and PostgreSQL ranges stay comparable.
  • PLUGIN_GUIDE.md: the DDL section is rewritten (the documented request shapes were wrong) and documents the new RPC and capabilities.

Compatibility

  • All new parameters, fields and capabilities are optional. Existing plugins never receive get_drop_table_sql, and the extra params on DDL requests is additive; every local plugin (duckdb, sqlserver, oracle, mongodb, libsql, elasticsearch, wordpress) was exercised over stdio with the new request shape.
  • Behaviour change: plugins that declare connection_metadata now receive the saved host and port in DDL builder params instead of tunnel-resolved ones. Builders only generate SQL, so this only changes what they see.

Tests

  • plugins/ddl_context_tests.rs, commands.rs builder tests (no tunnel resolution, secrets bound, keychain failures tolerated), dropTable.test.ts, rowIdentity.test.ts, driverCapabilities.test.ts.
  • cargo test --all-targets, pnpm build, vitest, pnpm lint pass. Clippy reports too_many_arguments on get_create_index_sql (8/7); get_create_foreign_key_sql was already flagged on main.

debba added 10 commits October 18, 2026 10:39
Driver plugins can now adapt the host connection inputs and keep their
own credentials in the OS keychain.

- Manifest `connection_fields`: optional hidden/label/placeholder
  overrides for host, port, username, password and database. A hidden
  database input is no longer required, and hiding host also hides the
  Load databases action. Declared in manifest.schema.json and in the
  Tabularium extension schema.
- `connection-modal.extra_fields` slot: optional `secretFields` (metadata
  only: value typed in this session, hasStoredValue, dirty) and
  `setSecretField(key, value)`; an empty value clears the secret.
- Secrets are stored under `<id>:plugin:<driver>:<key>` and only their
  names are persisted (`plugin_secret_keys`). They are resolved into
  `params.extra[key]` in memory for every driver call, Test Connection,
  Load Databases and the MCP server, and never written to
  connections.json or returned to the frontend.
- Lifecycle: save, update (driver switch drops the old secrets), duplicate,
  delete, group delete, export with credentials and backups, import (a
  metadata-only re-import keeps the stored secrets).
- Disconnect and export no longer fail when a stored plugin secret
  cannot be read.
- PLUGIN_GUIDE documents both contracts; @tabularis/plugin-api exposes
  the typed slot context and `PluginSecretFieldState`.

Existing manifests, saved connections and UI extensions keep their
behaviour: every new field is optional.
A plugin secret listed in `plugin_secret_keys` but missing from the OS
keychain (synced connections file, keychain reset, downgrade) made every
driver call of the connection fail, along with DDL, dump, MCP and
duplicate.

- Hydration is lenient everywhere: an invalid, missing or unreadable key
  (including a locked keychain or denied access) is logged by name, never
  by value, and left unset so the plugin reports the missing credential.
  Driver calls, Test Connection, Load Databases, export, backups and
  duplicate share one read path.
- Duplicate copies only the values that can be read and lists only those
  keys on the copy.
- Import skips invalid secret keys and oversized values with a warning
  instead of rejecting the payload, and writes plugin secrets to the
  keychain only after connections.json has been saved, so a failed save
  leaves no orphaned entries.
- PLUGIN_GUIDE documents the unset-secret behaviour and what happens
  after a downgrade drops `plugin_secret_keys`.
Plugin SQL builders (create table, add/alter column, create index,
foreign key, routine template) only received ConnectionParams when the
plugin opted into connection metadata, and the commands ignored the
database the user was browsing. Plugins with databases-as-projects could
not qualify names correctly.

Add DatabaseDriver::bind_connection_params so plugin drivers always get a
snapshot with the resolved params (browsed database applied, multi-db
selection coerced). The DDL commands accept an optional database
override and the modals pass it. Built-in drivers keep the registered
instance and saved params. The extra params field is additive.
Add an optional comment to ColumnDefinition (omitted from payloads when
unset) and an opt-in column_comments capability. When a driver declares
it, the add/modify column modal shows a description field and the DDL
builder RPCs receive the comment; old_column carries the current value
so plugins can detect changes.

Built-in drivers keep column_comments false and their DDL unchanged.
Keyless row identity matched every comparable column with =. FLOAT64,
FLOAT32 and BINARY_FLOAT/BINARY_DOUBLE were not recognised as approximate
numerics, and STRUCT/ARRAY/MAP/RECORD/ROW/RANGE, array suffixes and
document types (VARIANT, OBJECT) were compared through their serialized
grid value, which either never matches or is rejected by the engine.
The check is based on type names only, so it applies to every dialect.
The explorer always ran a generic DROP TABLE, which some engines reject
for snapshot or external tables. Add an optional get_drop_table_sql RPC
(params, table, schema, database) and a matching command. The explorer
uses the plugin statement when one is returned and falls back to the
generic, dialect-quoted DROP TABLE on -32601, null or for built-in
drivers.
Plugins that answer unknown methods with a generic error or an empty
array could no longer drop tables, because the explorer always asked
get_drop_table_sql. The RPC is now opt-in through the manifest
capability drop_table_sql (default false): without it the explorer runs
the generic DROP TABLE without any RPC or credential resolution. A
non-string result from an opted-in plugin is ignored with a warning.
PostgreSQL reports int[]/text[] columns as ARRAY (or type[]) and
compares them with =, so excluding them weakened the identity of
keyless tables and let UPDATE/DELETE match several rows. Only the
parameterized and nested forms (ARRAY<...>, RANGE<...>, ROW(...),
STRUCT, RECORD, REPEATED, MAP, TUPLE, NESTED) and document types stay
excluded; PostgreSQL ranges such as int4range remain comparable.
Plugin DDL previews, routine templates and drop statements resolved
SSH/SSM/K8s tunnels and keychain credentials on every call, so opening
Create Table on a closed SSH connection could build a tunnel and
previews failed offline. The SQL-builder commands now bind the saved
params (connection id and browsed database applied, host/port untouched)
without touching any tunnel or metadata discovery, and read keychain
secrets leniently: a keychain failure binds the params without the
secret.
The SQL-builder lookup bound the keychain password and connection URI
but not the plugin-owned secrets, so builders that call the plugin's
backend (for example get_drop_table_sql looking up the table kind) got
no credential. Resolve them into params.extra with the tolerant
plugin_secrets hydration: a missing or unreadable secret is logged and
left unset, and tunnels are still never resolved.
>
{t("modifyColumn.comment")}
</label>
<input autoCorrect="off" autoComplete="off"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

SUGGESTION: Description input is missing the text-input attributes its sibling inputs set

Every other free-text <input> in this modal (name, length, default) sets autoCapitalize="off" and spellCheck={false} alongside autoCorrect/autoComplete. This new one omits them, so the WebView may capitalise or spell-check the description. A column description is prose, so spell-check may be intentional; if so, ignore this. Otherwise align it with the siblings.

Suggested change
<input autoCorrect="off" autoComplete="off"
<input autoCorrect="off" autoCapitalize="off" autoComplete="off" spellCheck={false}

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

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.

Fixed in 96b9475: the description input now sets autoCapitalize="off" and spellCheck={false} like the other inputs in the modal.

@kilo-code-bot

kilo-code-bot Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Notes

  • Incremental review from 86ef73d to 96b9475: the only PR-owned change is ModifyColumnModal.tsx, where the Description <input> now sets autoCapitalize="off" and spellCheck={false}. That resolves the earlier suggestion.
  • The rest of the incremental diff (AI activity lexer, dump utils, notebook variables, SQL splitter, etc.) is not part of this PR's file list and appears to come from merging main, so it was not reviewed here.
  • Not run (read-only mode): cargo test, vitest, pnpm lint.
Files Reviewed (41 files)
  • plugins/PLUGIN_GUIDE.md
  • plugins/manifest.schema.json
  • plugins/tabularium-extensions.schema.json
  • src-tauri/src/clipboard_import_tests.rs
  • src-tauri/src/commands.rs
  • src-tauri/src/drivers/driver_trait.rs
  • src-tauri/src/drivers/mysql/mod.rs
  • src-tauri/src/drivers/postgres/mod.rs
  • src-tauri/src/drivers/sqlite/mod.rs
  • src-tauri/src/lib.rs
  • src-tauri/src/models.rs
  • src-tauri/src/plugins/ddl_context_tests.rs
  • src-tauri/src/plugins/driver.rs
  • src-tauri/tests/postgres_integration/ddl_generation.rs
  • src-tauri/tests/postgres_integration/parity.rs
  • src-tauri/tests/postgres_integration/parity_ddl.rs
  • src/components/layout/ExplorerSidebar.tsx
  • src/components/modals/CreateForeignKeyModal.tsx
  • src/components/modals/CreateIndexModal.tsx
  • src/components/modals/CreateTableModal.tsx
  • src/components/modals/ModifyColumnModal.tsx
  • src/i18n/locales/*.json (11 locales)
  • src/types/plugins.ts
  • src/utils/columnTypes.ts
  • src/utils/driverCapabilities.ts
  • src/utils/dropTable.ts
  • src/utils/rowIdentity.ts
  • tests/utils/columnTypes.test.ts
  • tests/utils/driverCapabilities.test.ts
  • tests/utils/dropTable.test.ts
  • tests/utils/rowIdentity.test.ts
Previous Review Summary (commit 86ef73d)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 86ef73d)

Status: 1 Issue Found | Recommendation: Merge (optional nit)

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
src/components/modals/ModifyColumnModal.tsx 337 New Description <input> omits autoCapitalize="off" and spellCheck={false}, which every sibling input in the modal sets. May be intentional for prose.

Notes

  • Verified against current HEAD (86ef73d): DDL builder params carry the browsed database, never resolve tunnels, and tolerate keychain/plugin-secret read failures; built-in drivers return early with unchanged behaviour.
  • drop_table_sql and column_comments are not in ConnectionCapabilityOverrides, so gating on the static manifest (host) and on activeCapabilities (frontend) stays consistent for discovery-enabled plugins.
  • get_drop_table_sql fallbacks (null, blank, non-string, -32601) are covered by Rust and vitest tests; other plugin errors abort the drop before anything runs.
  • handleNewRoutine does not pass database to get_routine_create_template (unchanged code). The command accepts it, but the sidebar caller has no database in scope, so there is no regression.
  • Not run (read-only mode): cargo test, vitest, pnpm lint. Assessment is from reading the code and tests.

Fix these issues in Kilo Cloud

Files Reviewed (41 files)
  • plugins/PLUGIN_GUIDE.md
  • plugins/manifest.schema.json
  • plugins/tabularium-extensions.schema.json
  • src-tauri/src/clipboard_import_tests.rs
  • src-tauri/src/commands.rs
  • src-tauri/src/drivers/driver_trait.rs
  • src-tauri/src/drivers/mysql/mod.rs
  • src-tauri/src/drivers/postgres/mod.rs
  • src-tauri/src/drivers/sqlite/mod.rs
  • src-tauri/src/lib.rs
  • src-tauri/src/models.rs
  • src-tauri/src/plugins/ddl_context_tests.rs
  • src-tauri/src/plugins/driver.rs
  • src-tauri/tests/postgres_integration/ddl_generation.rs
  • src-tauri/tests/postgres_integration/parity.rs
  • src-tauri/tests/postgres_integration/parity_ddl.rs
  • src/components/layout/ExplorerSidebar.tsx
  • src/components/modals/CreateForeignKeyModal.tsx
  • src/components/modals/CreateIndexModal.tsx
  • src/components/modals/CreateTableModal.tsx
  • src/components/modals/ModifyColumnModal.tsx - 1 issue
  • src/i18n/locales/*.json (11 locales)
  • src/types/plugins.ts
  • src/utils/columnTypes.ts
  • src/utils/driverCapabilities.ts
  • src/utils/dropTable.ts
  • src/utils/rowIdentity.ts
  • tests/utils/columnTypes.test.ts
  • tests/utils/driverCapabilities.test.ts
  • tests/utils/dropTable.test.ts
  • tests/utils/rowIdentity.test.ts

Reviewed by claude-sonnet-5.5 · Input: 246 · Output: 2.3K · Cached: 193.9K

Review guidance: REVIEW.md from base branch main

@debba
debba changed the base branch from feat/plugin-connection-fields to main October 18, 2026 13:42
@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Preview build

The preview build of commit 96b9475 failed (skipped). See the workflow run for logs.

@debba
debba merged commit 312cef8 into main Oct 18, 2026
10 checks passed
@debba
debba deleted the feat/plugin-ddl-context branch October 18, 2026 15:09
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.

1 participant