feat: SQL Workbench qbit 1.0 backend #1

Merged
KofTwentyTwo merged 10 commits from feature/1-sql-workbench-1.0 into develop 2026-10-11 18:52:05 +00:00
10 Commits
Author SHA1 Message Date
james.maesandClaude Opus 5.5 ec607016bd build: build against the released QQQ 4.1.0-RC.2
Re-pin the default-active qqq-snapshot profile from qqq-bom-pom
4.1.0-RC.1 to 4.1.0-RC.2 (now on Maven Central) and update the docs.
No SNAPSHOT dependencies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:49:10 -05:00
james.maesandClaude Opus 5.5 5342c04a2a docs: warn that production backends need readOnlyCredentials
Without a least-privilege read-only role, read mode runs as the app
role and relies on the classifier (H2 / generic) or a READ ONLY
transaction that still allows side-effecting functions (Postgres).
Also records the security fixes in the CHANGELOG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:40:16 -05:00
james.maesandClaude Opus 5.5 c43c135ca4 fix: keep credential jdbcUrl out of JSON; correct CHANGELOG coordinates
- SqlWorkbenchCredentials.getJdbcUrl is @JsonIgnore (a URL can carry a
  password); it is only read in code when deriving the connection.
- CHANGELOG names the artifact com.kingsrook.qbits:qbit-sql-workbench.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:39:55 -05:00
james.maesandClaude Opus 5.5 f67853d0e4 fix(export): neutralize CSV formula injection
Text cells starting with =, +, -, @, tab or CR are prefixed with a
quote so spreadsheets open them as text. Numbers are left alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:39:54 -05:00
james.maesandClaude Opus 5.5 1ac98b25cb fix(engine): generic connection errors; never leave an audit row RUNNING
- A failure to open the target connection (driver messages can expose
  hosts, users, URLs) now answers "Could not connect to backend X; see
  the server log." with no sqlState, and logs the cause server-side.
  Schema introspection does the same.
- An unexpected exception inside a started run finishes its audit row
  as FAILED (generic message, detail logged) before it propagates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:39:54 -05:00
james.maesandClaude Opus 5.5 24ce85f187 fix(sql): fail closed on MySQL executable comments and optimizer hints
MySQL runs the contents of /*! ... */ (and MariaDB /*M! ... */) and reads
/*+ ... */ hints, but the scanner skipped them as comments, so
"SELECT * FROM t /*! INTO OUTFILE '/tmp/x' */" classified as READ.

On the MySQL dialect the scanner now emits EXECUTABLE_COMMENT tokens for
these, and the classifier marks any statement containing one FORBIDDEN.
MySQL "--" also only starts a comment when whitespace follows ("1--1"
is arithmetic), so text after it is no longer hidden from the classifier.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:37:50 -05:00
james.maesandClaude Opus 5.5 52d2d6bc87 fix(engine): make client-chosen executionUuids single-use
Re-sending an in-flight executionUuid overwrote its registration, failed
the audit insert (UNIQUE), and the finally-unregister then removed the
original - escaping maxConcurrentExecutionsPerUser and cancellation.

- RunningStatements.register refuses an id already registered
  (putIfAbsent); unregister(uuid, registration) removes only its own.
- The engine refuses a client id that is running or already in the
  audit, before registering or recording anything
  (DuplicateExecutionException; the route provider answers 409).
- ExecutionAuditor.record() checks the insert result and fails loudly.
- Execution ids are lower-cased at the API edge (execute, rows, export,
  cancel, snapshots) and in engine.cancel.
- Tests for each, including 409 over HTTP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:36:09 -05:00
james.maesandClaude Opus 5.5 ebef56f34b fix(security): guard the qbit's tables with a separate admin permission
The five tables used the same HAS_ACCESS_PERMISSION base (sqlWorkbench)
that every workbench user holds, so any user could read and write other
users' personal notebooks, cells, versions, pinned result snapshots and
the full execution audit through the generic /qqq/v1/table API.

- Tables now use adminPermissionBaseName (default sqlWorkbenchAdmin,
  validated to differ from the access base name).
- Cell, version and snapshot tables drop insert/update/delete
  capabilities, like the audit table.
- NotebookService and the history / snapshot audit lookups check the
  caller in code, then read and write as the system user (SystemUser,
  shared with ExecutionAuditor).
- Tests: a second ordinary user (and the owner) get permission denied
  from the table query executor on all five tables; the workbench API
  keeps working for ordinary users; admins can read everything.
- README / DESIGN document the admin permission.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:33:23 -05:00
james.maesandClaude Opus 5.5 c497e7ece8 feat: accept a client-chosen executionUuid
POST /execute takes an optional executionUuid (validated as a UUID) so
the UI can cancel a running statement before the response arrives.
Generated server-side when absent; the audit's unique key still
guarantees no reuse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 10:14:01 -05:00
james.maesandClaude Opus 5.5 b8d33460bf feat: SQL Workbench qbit 1.0 backend
QQQ 4.1 QBit (com.kingsrook.qbits:sql-workbench) for a super-user SQL
notebook over any RDBMS backend of the instance:

- Dialect-aware scanner, statement splitter/classifier (READ,
  EXPLAIN_ANALYZE, WRITE, DDL, FORBIDDEN) and :named parameter binding.
- Execution engine: read-only sessions in a database READ ONLY
  transaction (Postgres, MySQL, SQLite query_only; classifier on H2),
  gated write mode (rolled-back dry run, single-use confirm token,
  row guard, hard cap, drift check), statement timeouts, row/byte caps,
  cancellation, per-user concurrency, clean connection return, and a
  fail-closed execution audit (audit updates deferred until the
  session connection is released).
- Dedicated read-only/write credentials per backend; backend read-only
  replica support.
- EXPLAIN / EXPLAIN ANALYZE (always rolled back) normalized to a plan
  tree; schema introspection with QQQ table/field -> SQL name mapping.
- Notebooks (personal/shared), cells, versions with retention, result
  snapshots.
- REST routes under /qqq/v1/sql-workbench (Javalin 7, JSON-only
  bodies), CSV/XLSX/JSON export.
- Tables, notebook PVS, SQL_WORKBENCH widget and sqlWorkbench app; no
  processes. PostgreSQL DDL in docs/ddl/postgres.sql.
- Tests: 83 unit (H2, SQLite, HTTP) + 6 Testcontainers Postgres
  integration tests; JaCoCo 94% instructions.
- CircleCI qqq-orb 0.6.8 config, Maven wrapper, CLAUDE.md/AGENTS.md,
  CHANGELOG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:47:40 -05:00