You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit b7a6838
Browse filesBrowse the repository at this point in the historyBrowse files
feat(editor): the web editor's run decision as one shared function (#2204, part 1) (#2240)
* refactor(sql): ask one unreadable-run predicate in the gate and the safety dialog
The gate asked whether a run never closes or a line comment holds a lone carriage return before it read a keyword, and the safety dialog asked the same two questions again to say why it asks. hasUnreadableRun in src/lib/sql/dangerous-query.ts is that predicate, and both now call it, so they cannot drift apart. No behaviour changes; tests/unit/sql/dangerous-query.test.ts is the gate module's own unit file.
Part of #2204.
* refactor(sql): move the VS Code child's nothing-to-run rule into runnableTarget
query/target answered an empty statement when the run target held no code under the sql editor language, with a countCodeStatements check written in the child. runnableTarget in src/lib/sql/run-target.ts is runTarget with that rule, so the statement analysis can share it, and the child calls it. Every query/target answer stays as it was: the extension's query/target unit tests pass unchanged.
Part of #2204.
* refactor(editor): move the VS Code child's editorLanguageOf into tab-language
The child read the editor language of a provider's text with a local editorLanguageOf over resolveTabType and editorLanguageForTabType. It now lives beside them in src/lib/editor/tab-language.ts, so the statement analysis reads the same answer, and connection/open and query/target call it there.
Part of #2204.
* refactor(editor): share the web editor's routing predicate and typed confirmation value
Whether a run takes /api/db/multi-query was written inline in use-query-execution.ts, and the text a typed confirmation is compared with was a private function of QuerySafetyDialog. Both move to the new src/lib/editor/statement-analysis.ts as runsAsScript and typedConfirmationExpected, which hold no component, and the hook and the dialog call them there. The hook keeps every other routing condition and its "sql" default while the provider's metadata loads, so the route of every run is unchanged.
Part of #2204.
* feat(editor): analyzeStatement, the web editor's run decision in one function
analyzeStatement in src/lib/editor/statement-analysis.ts takes the editor's text, selection and caret, the connection's type, capabilities and name, and answers nothing, refuse, run or confirm, with the exact statement to send, its range, its route, the unreadable notice and the typed value. It composes runnableTarget, statementRefusal, runsAsScript, isDangerousQuery, vocabularyTypedConfirmation, typedConfirmationExpected and hasUnreadableRun in the web editor's order, opens no connection, and is the decision the statement analysis route of #2204 will answer.
Its test drives the 62 non-HTTP acceptance cases of #2204 on each engine's real capabilities, built without connecting, and a parity test holds each one to the web editor's own composition of the same parts. The one divergence, a target that holds no code, which the web sends and the analysis answers as nothing to run, is recorded as docs/BACKLOG.md U200.
Part of #2204.
* docs: name the shared run decision, and record the extension's refusal gap
docs/VSCODE_EXTENSION.md names runnableTarget and editorLanguageOf as what the child calls, and section 8 says that Run in the extension does not apply the connection type's refusal, so a statement the web editor refuses before sending reaches query/run without a question and only the provider's guard stops it, recorded as docs/BACKLOG.md U201. docs/ARCHITECTURE.md says which functions analyzeStatement composes and which of them each caller uses.
Part of #2204.
* docs: name runsAsScript where comments named the hook's removed dialectIsSql
The routing condition use-query-execution.ts kept in a local dialectIsSql is now runsAsScript, so the comments in the hook and Redis provider tests and the paragraph in docs/providers/redis.md that named the local now name the function. No behaviour changes.
Part of #2204.
* test(editor): hold the statement analysis parity test to what the web editor does
The embedded store reads its text as SQL, so an unclosed literal there is unreadable; a new row pins that answer.
A caret target that is an unclosed block comment holds no code, and the web asks about it before sending it, so the recorded divergence now admits that confirm, with two rows and U200 saying so.
At a name-typed confirmation on a connection with no name, the web's dialog keeps Proceed disabled rather than refusing, and the test now composes that state instead of copying the refusal.
Copy file name to clipboardExpand all lines: docs/ARCHITECTURE.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -291,6 +291,8 @@ See [LAUNCH.md](LAUNCH.md).
291
291
292
292
Multi-statement queries execute sequentially via `POST /api/db/multi-query`, one request per execution unit (`splitExecutionUnits`): a statement, or on SQL Server the whole batch between `GO` lines.
293
293
294
+
The run target, the connection type's refusal, the confirmation and the route of a run are decided by the functions `analyzeStatement` in `src/lib/editor/statement-analysis.ts` composes in the web editor's order: the web editor calls them one by one, and the VS Code extension's child calls the run target, the editor language and the gate.
@@ -6943,6 +6943,27 @@ Found 2026-11-04 while moving the cross-platform jobs' one-time test setup out o
6943
6943
6944
6944
**Done when:** the test refuses a cyclic plan well inside the default timeout on a busy runner, by a Bun release that detects the cycle on the indented path as fast as on the plain one, or by a fence that refuses a cyclic plan before it serialises it, with the test kept.
6945
6945
6946
+
### U200. The web editor sends a run target that holds no code, which the extension and the statement analysis answer as nothing to run
6947
+
6948
+
With the caret on a statement of comments only (`-- note` after `SELECT 1;`, or `SELECT 1; -- note` with the caret at the line's end), or with only a comment selected, `getEffectiveQuery` in `src/components/QueryEditor.tsx` answers the comment and `executeQuery` in `src/hooks/use-query-execution.ts` sends it to the engine; a text of blanks only is sent the same way.
6949
+
A target that is an unclosed block comment (`/* open` after `SELECT 1;`, with the caret at the end) is asked about first, with the notice that part of the statement could not be read, and then sent.
6950
+
The VS Code extension's `query/target` and `analyzeStatement` in `src/lib/editor/statement-analysis.ts` answer each of these with nothing to run, through `runnableTarget` in `src/lib/sql/run-target.ts`.
6951
+
`tests/unit/sql/run-target.test.ts` pins `runTarget`'s own answer for such text, and the parity test in `tests/unit/editor/statement-analysis.test.ts` holds the divergence case by case.
6952
+
The web cannot adopt `runnableTarget` alone: when the effective query is empty, `executeQuery` falls back to the tab's whole text.
6953
+
Found 2026-11-03 while designing the statement analysis for #2204.
6954
+
6955
+
**Done when:** the web answers a target that holds no code with Nothing to run, through `runnableTarget`, and no empty effective query falls back to the tab text.
6956
+
6957
+
### U201. The VS Code extension sends a statement the web editor refuses to `query/run` without a question
6958
+
6959
+
The web editor asks `statementRefusal` (`src/lib/db/destructive-commands.ts`) before the confirmation gate, and on the nine connection types whose vocabulary row declares a refusal or a console text bound a refused statement is never sent or written to history (`src/hooks/use-query-execution.ts`).
6960
+
The extension's `query/target` in `vscode-extension/src/backend/handlers.ts` answers only the gate's verdict, and the host in `vscode-extension/src/host/editor/run-query.ts` sends a statement the gate does not flag to `query/run` without asking, where `checkEditorQuery` applies the console text bound but not the refusal, so only the provider's own guard stops it.
6961
+
Checked on 2026-11-04 with the shared functions: `isDangerousQuery` answers false and `statementRefusal` refuses both a Redis `GET a` with `DEL b` on the line after it and `kubectl delete pod web-1`; the Redis provider then refuses the first with the same sentence in `executeRedisCommand`, and the Kubernetes console refuses the second in `readKubectlRun` before any request.
6962
+
`analyzeStatement` in `src/lib/editor/statement-analysis.ts` answers both with `refuse`, but `query/target` has no field for a refusal and the host no way to show one.
6963
+
Found 2026-11-03 while designing the statement analysis for #2204.
6964
+
6965
+
**Done when:** `query/target` answers the connection type's refusal, the host shows it and sends nothing, and a test drives Redis `GET a\nDEL b` and `kubectl delete pod web-1` through it.
6966
+
6946
6967
## Dependencies
6947
6968
6948
6969
### P1. The desktop shell's `glib` advisory has no reachable fix while Tauri v2 targets GTK 3
Copy file name to clipboardExpand all lines: docs/VSCODE_EXTENSION.md
+3-2Lines changed: 3 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -88,7 +88,7 @@ The webview build swaps three imports for shims through `shimPlugin` in `scripts
88
88
| Connection profiles and secrets |`src/host/connections/profile.ts` (`readProfiles`, `toEntry`, `toWireConnection`), `connection-store.ts` (`ConnectionStore`), `secret-keys.ts` (`secretKey`, `SECRET_PATHS`) |`src/lib/storage/secret-fields.ts` (`secretFieldValues`, `withoutSecretFields`, `withSecretFieldValues` and its field classification maps), `EXTERNAL_DATABASE_TYPES` in `src/lib/db/compatibility.ts`| The profile sits in the `libredb.connections` setting; every secret sits in SecretStorage |
89
89
| Add Connection |`src/host/connections/add-connection-flow.ts` (`AddConnectionFlow`, a pure state machine), `add-connection-ui.ts` (`addConnection`), `field-prompts.ts` (`FIELD_PROMPTS`), `src/backend/engine-catalog.ts` (`engineCatalog`) |`CONNECTION_FORM`, `CONNECTION_PICKER_ORDER` and the `connectionField*` helpers in `src/lib/db/connection-form.ts`, `CONNECTION_FIELDS` and `PLACEABLE_FIELDS` in `src/lib/db/connection-fields.ts`, `CONNECTION_STRING` in `src/lib/db/connection-string`, `parseConnectionString` in `src/lib/connection-string-parser.ts`, `PASTE_FORMATS_TEXT` in `src/lib/connection-paste-formats.ts` (the catalogue's `pasteFormats`, so the host bundles no provider), `testConnection` in `src/lib/api/test-connection.ts`, `newLocalId` in `src/lib/ids.ts`| The flow names no engine: every per-engine fact comes from the `engines/list` catalogue |
90
90
| Connections tree |`src/host/tree/connections-tree.ts` (`ConnectionsTree`), `nodes.ts`, `tree-items.ts`, `icons.ts`| The kind helpers in `src/lib/db/object-kinds.ts` (`containerDepth`, `enumerableKinds`, `findKind`, `kindGroup`, ...), and in the child the provider methods `listContainers`, `countObjects`, `listObjects`, `describeObject`| Levels, folders and labels come from the provider's capabilities, recorded in `OpenConnections`|
91
-
| Editor binding, Run, Cancel, status bar |`src/host/editor/` (`BindingStore`, `RunRegistry`, `RunService`, `runQuery`, `newQuery`, `selectRows`, `cancelQuery`, `changeConnection`, `ConnectionStatusBar`, `ContextKeys`, `DocumentLifecycle`) | In the child: `runTarget` in `src/lib/sql/run-target.ts`, `isDangerousQuery` in `src/lib/sql/dangerous-query.ts`, `resolveSqlGrammar`, `countCodeStatements`, `generateTableQuery` in `src/lib/query-generators.ts`, `editorLanguageForTabType` in `src/lib/editor/tab-language.ts`| Bindings live in memory only, so a window reload forgets them |
91
+
| Editor binding, Run, Cancel, status bar |`src/host/editor/` (`BindingStore`, `RunRegistry`, `RunService`, `runQuery`, `newQuery`, `selectRows`, `cancelQuery`, `changeConnection`, `ConnectionStatusBar`, `ContextKeys`, `DocumentLifecycle`) | In the child: `runnableTarget` in `src/lib/sql/run-target.ts`, `isDangerousQuery` in `src/lib/sql/dangerous-query.ts`, `resolveSqlGrammar`, `generateTableQuery` in `src/lib/query-generators.ts`, `editorLanguageOf` in `src/lib/editor/tab-language.ts`| Bindings live in memory only, so a window reload forgets them |
92
92
| Backend handlers and registry |`src/backend/handlers.ts` (`createHandlers`, `registerHandlers`, `toResponseError`), `registry.ts` (`ConnectionRegistry`), `start.ts` (`startBackend`), `main.ts`|`getOrCreateProvider`, `removeProvider`, `clearProviderCache`, `enabledProviderTypes` in `src/lib/db/factory.ts`; `checkEditorQuery` and `runEditorQuery` in `src/lib/api/editor-query.ts`; `databaseErrorBody` and `internalErrorBody` in `src/lib/api/error-body.ts`; `supportsQueryCancel`, `scopeToCatalog`, `executionUiFor`|`query/run` re-does the steps of `POST /api/db/query` for one statement through the same functions |
| JSON-RPC protocol |`src/protocol.ts` (`BackendApi`, `METHODS`, `PROTOCOL_VERSION`), `src/host/backend-client.ts` (`BackendClient`, `BackendError`) | Types only (`DatabaseConnection`, `ProviderCapabilities`, `EditorQueryBody`, `ApiErrorCode`, ...) | Every Studio import in `protocol.ts` is type-only, so it adds no provider code to the host |
@@ -195,7 +195,7 @@ Step by step, with the code that does it:
195
195
1.`runQuery` in `src/host/editor/run-query.ts` finds the editor, reads its binding from `BindingStore` (or binds it through `pickConnection`), refuses a second run in the same document with `ALREADY_RUNNING`, and reads `libredb.query.rowLimit` (1 to 10000, default 500) with `readRowLimit`.
196
196
2.`RunRegistry.prepare` registers a preparing run with its own cancellation token, so Cancel works even while the connection opens.
197
197
3.`BackendClient.ensureOpen` returns the connection's `ProviderDescription`, opening it first when needed; where the capabilities declare catalog sessions, the person picks a catalog once per binding.
198
-
4.`query/target` sends the whole text, the selection and the cursor; the child applies the web editor's run target rule (`runTarget`) with the engine's grammar, and an empty target becomes "Nothing to run.".
198
+
4.`query/target` sends the whole text, the selection and the cursor; the child applies the web editor's run target rule through `runnableTarget`, with the engine's grammar and the editor language `editorLanguageOf` reads from its capabilities, and a target that holds no code becomes "Nothing to run.".
199
199
The answer also says whether the web editor would ask before running that statement: the child reads it with `isDangerousQuery` under the connection's type, the gate behind the web editor's Query Safety dialog.
200
200
The range that will run is highlighted for one second, and a statement the gate flags then asks in a modal `showWarningMessage` that shows it and offers Run Anyway; Cancel or Escape runs nothing.
201
201
5.`RunService.execute` mints a fresh `queryId`, turns the preparing run into a running `RunEntry`, tells the presenter, and sends `query/run` inside a progress shown on the Results view.
@@ -370,6 +370,7 @@ Select Rows asks the child for `objects/selectStatement`, which writes the state
370
370
-**No login or roles.** The extension serves one person's VS Code: no host or backend code signs anyone in or checks a role, and the host bundle check refuses `src/lib/config/auth-env.ts`.
371
371
-**No storage layer or sync.** Connections are the setting plus SecretStorage, not `src/lib/storage`; the webview's `@/lib/storage` shim throws on any use; editor bindings are in memory only.
372
372
-**A plainer dangerous-query confirmation.** The statement is read by the web editor's own `isDangerousQuery`, but the question is one modal button: no AI risk analysis, and no typed confirmation where the web editor asks for one (an etcd range delete or lease revoke).
373
+
Nor does Run apply the connection type's refusal: a statement the web editor refuses before sending it (a Redis `GET a` with `DEL b` on the line after it, or `kubectl delete pod web-1`) reaches `query/run` without a question, and only the provider's own guard stops it (`docs/BACKLOG.md` U201).
373
374
Select Rows and Load More ask nothing: the first runs the read the engine's generator built for the object (a SELECT, a find, a `get`, an `aws s3 ls`), and the second reads more pages of a statement already confirmed.
374
375
That differs from the web editor on purpose, whose table click goes through the gate, so a generated read on an object whose name the gate cannot read safely (a ClickHouse table named `a$$b`) asks there and not here.
375
376
-**No query events or governance.** The child bundle refuses `src/lib/governance/start.ts` and `src/lib/query-events/sink.ts`, and `query/run` runs no admission gate.
0 commit comments