chore: bootstrap independent git workflow
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
38
examples/README.md
Normal file
38
examples/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# dbtool-cli-v1 Demo Assets
|
||||
|
||||
These assets give backend, frontend, and QA one small shared dataset for PostgreSQL, MySQL, and SQLite.
|
||||
|
||||
## Current Status
|
||||
|
||||
- Demo fixtures, seed scripts, and smoke inputs are staged.
|
||||
- PostgreSQL、MySQL 和 SQLite driver behavior now exists for `connect`, `inspect`, and `query`.
|
||||
- `export` execution to CSV / JSON now exists behind the shared query-result model.
|
||||
- Use these files as the shared data baseline for backend, frontend, and QA verification.
|
||||
|
||||
## Layout
|
||||
|
||||
- `examples/fixtures/` — seed SQL per database plus expected assertions
|
||||
- `examples/sql/` — happy-path and failure-path query inputs
|
||||
- `examples/scripts/` — repeatable bootstrap helpers
|
||||
|
||||
## Intended QA Coverage
|
||||
|
||||
- `connect` against PostgreSQL, MySQL, SQLite
|
||||
- `inspect` schemas / tables / columns
|
||||
- `query` from inline SQL or file input
|
||||
- `export` to CSV / JSON from a known query
|
||||
|
||||
## MySQL Bootstrap Note
|
||||
|
||||
- `examples/fixtures/mysql/init.sql` 会显式授予 `dbtool` 用户对 `qa_demo` 的权限
|
||||
- 这样可避免 demo 容器默认库名与 QA 目标库名不一致时,`dbtool` 无法访问 `qa_demo`
|
||||
|
||||
## SQLite Bootstrap Note
|
||||
|
||||
- `examples/scripts/bootstrap-sqlite.sh` 现支持 `sqlite3`、`node:sqlite` 或 `python3 sqlite3` 三种 seed 路径
|
||||
- SQLite demo inspect 语义使用 `main` 作为 schema 名称
|
||||
- SQLite demo query 目前按单条 statement 执行,多语句输入会明确失败
|
||||
|
||||
## Known Blocker
|
||||
|
||||
The seed data is ready, and PostgreSQL / MySQL / SQLite query and export flows are implemented, but these assets still cannot serve as release evidence until the full smoke path runs in a Docker-capable environment.
|
||||
28
examples/fixtures/EXPECTED_RESULTS.md
Normal file
28
examples/fixtures/EXPECTED_RESULTS.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Expected Results
|
||||
|
||||
Use these assertions when verifying `connect`, `inspect`, `query`, and `export`.
|
||||
|
||||
## PostgreSQL
|
||||
|
||||
- schema: `qa_demo`
|
||||
- tables: `accounts`, `tickets`
|
||||
|
||||
## MySQL
|
||||
|
||||
- database: `qa_demo`
|
||||
- tables: `accounts`, `tickets`
|
||||
|
||||
## SQLite
|
||||
|
||||
- file: `examples/tmp/dbtool-demo.sqlite`
|
||||
- tables: `accounts`, `tickets`
|
||||
|
||||
## Shared Data Assertions
|
||||
|
||||
- `accounts` row count: `3`
|
||||
- `tickets` row count: `4`
|
||||
- open tickets: `3`
|
||||
- closed tickets: `1`
|
||||
- at least one `NULL` value exists in `tickets.notes`
|
||||
- at least one multilingual value exists in `accounts.display_name`
|
||||
- export query returns `4` rows ordered by `ticket_id`
|
||||
35
examples/fixtures/mysql/init.sql
Normal file
35
examples/fixtures/mysql/init.sql
Normal file
@@ -0,0 +1,35 @@
|
||||
DROP DATABASE IF EXISTS qa_demo;
|
||||
CREATE DATABASE qa_demo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
GRANT ALL PRIVILEGES ON qa_demo.* TO 'dbtool'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
USE qa_demo;
|
||||
|
||||
CREATE TABLE accounts (
|
||||
id BIGINT PRIMARY KEY,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
display_name VARCHAR(255) NULL,
|
||||
is_active BOOLEAN NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE tickets (
|
||||
id BIGINT PRIMARY KEY,
|
||||
account_id BIGINT NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL,
|
||||
notes TEXT NULL,
|
||||
amount_cents INT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
CONSTRAINT fk_tickets_account FOREIGN KEY (account_id) REFERENCES accounts(id)
|
||||
);
|
||||
|
||||
INSERT INTO accounts (id, email, display_name, is_active, created_at) VALUES
|
||||
(1, 'alice@example.com', 'Alice', TRUE, '2026-03-01 08:30:00'),
|
||||
(2, 'maria@example.com', 'Máría', FALSE, '2026-03-10 09:45:00'),
|
||||
(3, 'zhang@example.com', '张敏', TRUE, '2026-03-15 12:00:00');
|
||||
|
||||
INSERT INTO tickets (id, account_id, title, status, notes, amount_cents, created_at) VALUES
|
||||
(101, 1, 'login timeout', 'open', NULL, 0, '2026-03-20 10:00:00'),
|
||||
(102, 1, 'csv export mismatch', 'closed', 'fixed after re-run', 1500, '2026-03-21 11:15:00'),
|
||||
(103, 2, '权限验证', 'open', 'needs DBA review', 300, '2026-03-22 15:30:00'),
|
||||
(104, 3, 'emoji smoke 😀', 'open', NULL, 42, '2026-03-23 07:05:00');
|
||||
32
examples/fixtures/postgres/init.sql
Normal file
32
examples/fixtures/postgres/init.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
DROP SCHEMA IF EXISTS qa_demo CASCADE;
|
||||
CREATE SCHEMA qa_demo;
|
||||
SET search_path TO qa_demo;
|
||||
|
||||
CREATE TABLE accounts (
|
||||
id BIGINT PRIMARY KEY,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
display_name TEXT,
|
||||
is_active BOOLEAN NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE tickets (
|
||||
id BIGINT PRIMARY KEY,
|
||||
account_id BIGINT NOT NULL REFERENCES accounts(id),
|
||||
title TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
notes TEXT,
|
||||
amount_cents INTEGER NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO accounts (id, email, display_name, is_active, created_at) VALUES
|
||||
(1, 'alice@example.com', 'Alice', TRUE, '2026-03-01T08:30:00Z'),
|
||||
(2, 'maria@example.com', 'Máría', FALSE, '2026-03-10T09:45:00Z'),
|
||||
(3, 'zhang@example.com', '张敏', TRUE, '2026-03-15T12:00:00Z');
|
||||
|
||||
INSERT INTO tickets (id, account_id, title, status, notes, amount_cents, created_at) VALUES
|
||||
(101, 1, 'login timeout', 'open', NULL, 0, '2026-03-20T10:00:00Z'),
|
||||
(102, 1, 'csv export mismatch', 'closed', 'fixed after re-run', 1500, '2026-03-21T11:15:00Z'),
|
||||
(103, 2, '权限验证', 'open', 'needs DBA review', 300, '2026-03-22T15:30:00Z'),
|
||||
(104, 3, 'emoji smoke 😀', 'open', NULL, 42, '2026-03-23T07:05:00Z');
|
||||
34
examples/fixtures/sqlite/init.sql
Normal file
34
examples/fixtures/sqlite/init.sql
Normal file
@@ -0,0 +1,34 @@
|
||||
PRAGMA foreign_keys = OFF;
|
||||
DROP TABLE IF EXISTS tickets;
|
||||
DROP TABLE IF EXISTS accounts;
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
CREATE TABLE accounts (
|
||||
id INTEGER PRIMARY KEY,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
display_name TEXT,
|
||||
is_active INTEGER NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE tickets (
|
||||
id INTEGER PRIMARY KEY,
|
||||
account_id INTEGER NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
notes TEXT,
|
||||
amount_cents INTEGER NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
FOREIGN KEY (account_id) REFERENCES accounts(id)
|
||||
);
|
||||
|
||||
INSERT INTO accounts (id, email, display_name, is_active, created_at) VALUES
|
||||
(1, 'alice@example.com', 'Alice', 1, '2026-03-01T08:30:00Z'),
|
||||
(2, 'maria@example.com', 'Máría', 0, '2026-03-10T09:45:00Z'),
|
||||
(3, 'zhang@example.com', '张敏', 1, '2026-03-15T12:00:00Z');
|
||||
|
||||
INSERT INTO tickets (id, account_id, title, status, notes, amount_cents, created_at) VALUES
|
||||
(101, 1, 'login timeout', 'open', NULL, 0, '2026-03-20T10:00:00Z'),
|
||||
(102, 1, 'csv export mismatch', 'closed', 'fixed after re-run', 1500, '2026-03-21T11:15:00Z'),
|
||||
(103, 2, '权限验证', 'open', 'needs DBA review', 300, '2026-03-22T15:30:00Z'),
|
||||
(104, 3, 'emoji smoke 😀', 'open', NULL, 42, '2026-03-23T07:05:00Z');
|
||||
13
examples/scripts/bootstrap-mysql.sh
Executable file
13
examples/scripts/bootstrap-mysql.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
COMPOSE_FILE="${COMPOSE_FILE:-$ROOT_DIR/docker-compose.demo.yml}"
|
||||
SERVICE_NAME="${MYSQL_SERVICE_NAME:-mysql}"
|
||||
MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD:-dbtoolroot}"
|
||||
|
||||
docker compose -f "$COMPOSE_FILE" exec -T "$SERVICE_NAME" \
|
||||
mysql -uroot "-p$MYSQL_ROOT_PASSWORD" \
|
||||
< "$ROOT_DIR/examples/fixtures/mysql/init.sql"
|
||||
|
||||
echo "MySQL demo data seeded into qa_demo."
|
||||
14
examples/scripts/bootstrap-postgres.sh
Executable file
14
examples/scripts/bootstrap-postgres.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
COMPOSE_FILE="${COMPOSE_FILE:-$ROOT_DIR/docker-compose.demo.yml}"
|
||||
SERVICE_NAME="${POSTGRES_SERVICE_NAME:-postgres}"
|
||||
POSTGRES_USER="${POSTGRES_USER:-dbtool}"
|
||||
POSTGRES_DB="${POSTGRES_DB:-dbtool_demo}"
|
||||
|
||||
docker compose -f "$COMPOSE_FILE" exec -T "$SERVICE_NAME" \
|
||||
psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" \
|
||||
< "$ROOT_DIR/examples/fixtures/postgres/init.sql"
|
||||
|
||||
echo "PostgreSQL demo data seeded into $POSTGRES_DB."
|
||||
45
examples/scripts/bootstrap-sqlite.sh
Executable file
45
examples/scripts/bootstrap-sqlite.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
SQLITE_DB_PATH="${SQLITE_DB_PATH:-$ROOT_DIR/examples/tmp/dbtool-demo.sqlite}"
|
||||
SQL_FILE="$ROOT_DIR/examples/fixtures/sqlite/init.sql"
|
||||
|
||||
mkdir -p "$(dirname "$SQLITE_DB_PATH")"
|
||||
|
||||
if command -v sqlite3 >/dev/null 2>&1; then
|
||||
sqlite3 "$SQLITE_DB_PATH" < "$SQL_FILE"
|
||||
elif command -v node >/dev/null 2>&1; then
|
||||
node --no-warnings - "$SQLITE_DB_PATH" "$SQL_FILE" <<'JS'
|
||||
const { DatabaseSync } = require('node:sqlite');
|
||||
const fs = require('fs');
|
||||
|
||||
const dbPath = process.argv[2];
|
||||
const sqlPath = process.argv[3];
|
||||
|
||||
const db = new DatabaseSync(dbPath);
|
||||
try {
|
||||
db.exec(fs.readFileSync(sqlPath, 'utf8'));
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
JS
|
||||
else
|
||||
python3 - "$SQLITE_DB_PATH" "$SQL_FILE" <<'PY'
|
||||
import pathlib
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
db_path = pathlib.Path(sys.argv[1])
|
||||
sql_path = pathlib.Path(sys.argv[2])
|
||||
|
||||
conn = sqlite3.connect(db_path)
|
||||
try:
|
||||
conn.executescript(sql_path.read_text(encoding="utf-8"))
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
PY
|
||||
fi
|
||||
|
||||
echo "SQLite demo data seeded into $SQLITE_DB_PATH."
|
||||
12
examples/sql/mysql/export_query.sql
Normal file
12
examples/sql/mysql/export_query.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
USE qa_demo;
|
||||
|
||||
SELECT
|
||||
t.id AS ticket_id,
|
||||
a.email,
|
||||
t.title,
|
||||
t.status,
|
||||
t.amount_cents,
|
||||
t.created_at
|
||||
FROM tickets t
|
||||
JOIN accounts a ON a.id = t.account_id
|
||||
ORDER BY t.id;
|
||||
3
examples/sql/mysql/failing_query.sql
Normal file
3
examples/sql/mysql/failing_query.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
USE qa_demo;
|
||||
|
||||
SELECT * FROM missing_table;
|
||||
11
examples/sql/mysql/happy_path_query.sql
Normal file
11
examples/sql/mysql/happy_path_query.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
USE qa_demo;
|
||||
|
||||
SELECT
|
||||
a.id AS account_id,
|
||||
a.display_name,
|
||||
COUNT(t.id) AS ticket_count,
|
||||
SUM(CASE WHEN t.status = 'open' THEN 1 ELSE 0 END) AS open_ticket_count
|
||||
FROM accounts a
|
||||
LEFT JOIN tickets t ON t.account_id = a.id
|
||||
GROUP BY a.id, a.display_name
|
||||
ORDER BY a.id;
|
||||
12
examples/sql/postgres/export_query.sql
Normal file
12
examples/sql/postgres/export_query.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
SET search_path TO qa_demo;
|
||||
|
||||
SELECT
|
||||
t.id AS ticket_id,
|
||||
a.email,
|
||||
t.title,
|
||||
t.status,
|
||||
t.amount_cents,
|
||||
t.created_at
|
||||
FROM tickets t
|
||||
JOIN accounts a ON a.id = t.account_id
|
||||
ORDER BY t.id;
|
||||
3
examples/sql/postgres/failing_query.sql
Normal file
3
examples/sql/postgres/failing_query.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
SET search_path TO qa_demo;
|
||||
|
||||
SELECT * FROM missing_table;
|
||||
11
examples/sql/postgres/happy_path_query.sql
Normal file
11
examples/sql/postgres/happy_path_query.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
SET search_path TO qa_demo;
|
||||
|
||||
SELECT
|
||||
a.id AS account_id,
|
||||
a.display_name,
|
||||
COUNT(t.id) AS ticket_count,
|
||||
SUM(CASE WHEN t.status = 'open' THEN 1 ELSE 0 END) AS open_ticket_count
|
||||
FROM accounts a
|
||||
LEFT JOIN tickets t ON t.account_id = a.id
|
||||
GROUP BY a.id, a.display_name
|
||||
ORDER BY a.id;
|
||||
10
examples/sql/sqlite/export_query.sql
Normal file
10
examples/sql/sqlite/export_query.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
SELECT
|
||||
t.id AS ticket_id,
|
||||
a.email,
|
||||
t.title,
|
||||
t.status,
|
||||
t.amount_cents,
|
||||
t.created_at
|
||||
FROM tickets t
|
||||
JOIN accounts a ON a.id = t.account_id
|
||||
ORDER BY t.id;
|
||||
1
examples/sql/sqlite/failing_query.sql
Normal file
1
examples/sql/sqlite/failing_query.sql
Normal file
@@ -0,0 +1 @@
|
||||
SELECT * FROM missing_table;
|
||||
9
examples/sql/sqlite/happy_path_query.sql
Normal file
9
examples/sql/sqlite/happy_path_query.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
SELECT
|
||||
a.id AS account_id,
|
||||
a.display_name,
|
||||
COUNT(t.id) AS ticket_count,
|
||||
SUM(CASE WHEN t.status = 'open' THEN 1 ELSE 0 END) AS open_ticket_count
|
||||
FROM accounts a
|
||||
LEFT JOIN tickets t ON t.account_id = a.id
|
||||
GROUP BY a.id, a.display_name
|
||||
ORDER BY a.id;
|
||||
Reference in New Issue
Block a user