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>
109 lines
4.3 KiB
SQL
109 lines
4.3 KiB
SQL
-- SQL Workbench QBit (com.kingsrook.qbits:qbit-sql-workbench) - PostgreSQL DDL
|
|
--
|
|
-- Table and column names are the snake_case forms of the QQQ names, which is
|
|
-- what a host gets when its tableMetaDataCustomizer applies
|
|
-- QInstanceEnricher.inferBackendName to the table name and
|
|
-- QInstanceEnricher.setInferredFieldBackendNames to the fields (see README,
|
|
-- "Storage"). With a tableNamePrefix, prefix the table names the same way.
|
|
--
|
|
-- Liquibase: paste into a formatted-SQL changeset, e.g.
|
|
-- -- liquibase formatted sql
|
|
-- -- changeset you:NNNN-1
|
|
-- Idempotent (IF NOT EXISTS), so it is safe on re-run.
|
|
|
|
CREATE TABLE IF NOT EXISTS sql_workbench_notebook (
|
|
id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
|
title VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
owner_user_id VARCHAR(255) NOT NULL,
|
|
owner_name VARCHAR(255),
|
|
visibility VARCHAR(16) NOT NULL DEFAULT 'PERSONAL',
|
|
default_backend_name VARCHAR(100),
|
|
parameters_json TEXT,
|
|
current_version INTEGER,
|
|
create_date TIMESTAMP WITHOUT TIME ZONE,
|
|
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
|
CONSTRAINT pk_sql_workbench_notebook PRIMARY KEY (id)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS ix_sql_workbench_notebook_owner ON sql_workbench_notebook (owner_user_id);
|
|
CREATE INDEX IF NOT EXISTS ix_sql_workbench_notebook_visibility ON sql_workbench_notebook (visibility);
|
|
|
|
CREATE TABLE IF NOT EXISTS sql_workbench_cell (
|
|
id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
|
notebook_id INTEGER NOT NULL,
|
|
cell_key VARCHAR(64) NOT NULL,
|
|
sort_order INTEGER,
|
|
cell_type VARCHAR(16) NOT NULL,
|
|
title VARCHAR(255),
|
|
content TEXT,
|
|
backend_name VARCHAR(100),
|
|
create_date TIMESTAMP WITHOUT TIME ZONE,
|
|
modify_date TIMESTAMP WITHOUT TIME ZONE,
|
|
CONSTRAINT pk_sql_workbench_cell PRIMARY KEY (id),
|
|
CONSTRAINT uq_sql_workbench_cell_key UNIQUE (notebook_id, cell_key),
|
|
CONSTRAINT fk_sql_workbench_cell_notebook FOREIGN KEY (notebook_id) REFERENCES sql_workbench_notebook (id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS sql_workbench_notebook_version (
|
|
id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
|
notebook_id INTEGER NOT NULL,
|
|
version_number INTEGER NOT NULL,
|
|
content_json TEXT,
|
|
saved_by_user_id VARCHAR(255),
|
|
saved_by_name VARCHAR(255),
|
|
comment VARCHAR(255),
|
|
create_date TIMESTAMP WITHOUT TIME ZONE,
|
|
CONSTRAINT pk_sql_workbench_notebook_version PRIMARY KEY (id),
|
|
CONSTRAINT uq_sql_workbench_notebook_version UNIQUE (notebook_id, version_number),
|
|
CONSTRAINT fk_sql_workbench_notebook_version_notebook FOREIGN KEY (notebook_id) REFERENCES sql_workbench_notebook (id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS sql_workbench_result_snapshot (
|
|
id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
|
notebook_id INTEGER NOT NULL,
|
|
cell_key VARCHAR(64),
|
|
execution_uuid VARCHAR(36),
|
|
label VARCHAR(255),
|
|
backend_name VARCHAR(100),
|
|
sql_text TEXT,
|
|
result_json TEXT,
|
|
row_count INTEGER,
|
|
truncated BOOLEAN,
|
|
created_by_user_id VARCHAR(255),
|
|
create_date TIMESTAMP WITHOUT TIME ZONE,
|
|
CONSTRAINT pk_sql_workbench_result_snapshot PRIMARY KEY (id),
|
|
CONSTRAINT fk_sql_workbench_result_snapshot_notebook FOREIGN KEY (notebook_id) REFERENCES sql_workbench_notebook (id)
|
|
);
|
|
|
|
-- Audit: append-only. Not foreign-keyed to notebooks, so deleting a
|
|
-- notebook never deletes its audit trail.
|
|
CREATE TABLE IF NOT EXISTS sql_workbench_execution (
|
|
id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
|
execution_uuid VARCHAR(36) NOT NULL,
|
|
user_id VARCHAR(255),
|
|
user_name VARCHAR(255),
|
|
backend_name VARCHAR(100),
|
|
mode VARCHAR(24),
|
|
status VARCHAR(16),
|
|
sql_text TEXT,
|
|
sql_hash VARCHAR(64),
|
|
parameters_json TEXT,
|
|
statement_count INTEGER,
|
|
statement_kinds VARCHAR(255),
|
|
notebook_id INTEGER,
|
|
cell_key VARCHAR(64),
|
|
start_time TIMESTAMP WITHOUT TIME ZONE,
|
|
end_time TIMESTAMP WITHOUT TIME ZONE,
|
|
duration_ms INTEGER,
|
|
rows_returned INTEGER,
|
|
rows_affected INTEGER,
|
|
truncated BOOLEAN,
|
|
error_message TEXT,
|
|
client_ip VARCHAR(64),
|
|
create_date TIMESTAMP WITHOUT TIME ZONE,
|
|
CONSTRAINT pk_sql_workbench_execution PRIMARY KEY (id),
|
|
CONSTRAINT uq_sql_workbench_execution_uuid UNIQUE (execution_uuid)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS ix_sql_workbench_execution_user ON sql_workbench_execution (user_id, id);
|
|
CREATE INDEX IF NOT EXISTS ix_sql_workbench_execution_start ON sql_workbench_execution (start_time);
|