diff --git a/.gitignore b/.gitignore index a6ef1f0..e656359 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ /target/ +/out/ .env /data/ *.dump .DS_Store +.idea/ # Local pmat / pv tooling state /.pmat/ diff --git a/Cargo.lock b/Cargo.lock index 66a614f..e8d2853 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -150,9 +150,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.61" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", "shlex", diff --git a/Makefile b/Makefile index fe5004f..41820c5 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ -.PHONY: help up down pagila psql demo capstone verify test coverage fmt lint clean nuke +.PHONY: help up down pagila psql demo capstone verify test coverage fmt lint clean nuke \ + demo-1-fundamentals demo-2-joins demo-3-pagila demo-4-rust demo-all DC := docker compose PSQL := $(DC) exec -T postgres psql -U postgres -d pagila @@ -13,6 +14,14 @@ help: @echo " make demo — run the SQL example walkthrough" @echo " make capstone — build and run the Rust postgres-reports binary" @echo " make verify — assert all headline counts (CI smoke test)" + @echo "" + @echo " Standalone demos (one script per concept):" + @echo " make demo-1-fundamentals — sql/01-fundamentals/*.sql" + @echo " make demo-2-joins — sql/02-joins/*.sql" + @echo " make demo-3-pagila — sql/pagila-analytics/*.sql" + @echo " make demo-4-rust — Rust capstone binary, 3 contract-enforced reports" + @echo " make demo-all — run every demo in sequence" + @echo "" @echo " make test — cargo test for the Rust crate" @echo " make coverage — cargo llvm-cov (100% line gate)" @echo " make fmt lint — cargo fmt && cargo clippy" @@ -48,6 +57,21 @@ capstone: pagila verify: pagila @bash scripts/verify.sh +demo-1-fundamentals: pagila + @bash scripts/demo-1-fundamentals.sh + +demo-2-joins: pagila + @bash scripts/demo-2-joins.sh + +demo-3-pagila: pagila + @bash scripts/demo-3-pagila.sh + +demo-4-rust: pagila + @bash scripts/demo-4-rust.sh + +demo-all: pagila + @bash scripts/demo-all.sh + test: pagila @DATABASE_URL=postgres://postgres:postgres@localhost:5432/pagila cargo test --release diff --git a/scripts/demo-1-fundamentals.sh b/scripts/demo-1-fundamentals.sh new file mode 100755 index 0000000..d95c19b --- /dev/null +++ b/scripts/demo-1-fundamentals.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# demo-1-fundamentals.sh — first commands you run after `psql -d pagila`. +# +# Walks through the five SQL files in sql/01-fundamentals/: +# 01-connect.sql psql conventions, schema introspection +# 02-show-tables.sql listing tables, describing a table +# 03-select-limit.sql safe exploratory SELECT against a 16k-row table +# 04-modify.sql INSERT / UPDATE / DELETE inside a transaction +# 05-export.sql \copy a query result to a CSV file +# +# Usage: +# scripts/demo-1-fundamentals.sh +# +set -euo pipefail +cd "$(dirname "$0")/.." + +bar() { printf '\n\033[1;36m=== %s ===\033[0m\n\n' "$*"; } + +# bring up postgres + load Pagila if not already done +if ! docker compose ps postgres 2>/dev/null | grep -q "Up"; then + bar "starting postgres + loading Pagila (one-time)" + make pagila >/dev/null +fi + +PSQL=(docker compose exec -T postgres psql -U postgres -d pagila) + +for f in sql/01-fundamentals/*.sql; do + bar "$(basename "$f")" + "${PSQL[@]}" -f - < "$f" +done + +bar "fundamentals demo complete" diff --git a/scripts/demo-2-joins.sh b/scripts/demo-2-joins.sh new file mode 100755 index 0000000..a0f1154 --- /dev/null +++ b/scripts/demo-2-joins.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# demo-2-joins.sh — the three join patterns every Pagila query uses. +# +# 01-traversal.sql customer → rental → inventory → film (the Sakila spine) +# 02-inner-vs-left.sql what changes when you flip INNER ↔ LEFT +# 03-explain-analyze.sql reading a query plan against real row counts +# +# Usage: +# scripts/demo-2-joins.sh +# +set -euo pipefail +cd "$(dirname "$0")/.." + +bar() { printf '\n\033[1;36m=== %s ===\033[0m\n\n' "$*"; } + +if ! docker compose ps postgres 2>/dev/null | grep -q "Up"; then + bar "starting postgres + loading Pagila (one-time)" + make pagila >/dev/null +fi + +PSQL=(docker compose exec -T postgres psql -U postgres -d pagila) + +for f in sql/02-joins/*.sql; do + bar "$(basename "$f")" + "${PSQL[@]}" -f - < "$f" +done + +bar "joins demo complete" diff --git a/scripts/demo-3-pagila.sh b/scripts/demo-3-pagila.sh new file mode 100755 index 0000000..f0a73bf --- /dev/null +++ b/scripts/demo-3-pagila.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# demo-3-pagila.sh — three Pagila analytics queries as raw SQL. +# +# Each query is the same one the Rust capstone runs (see scripts/demo-4-rust.sh). +# This demo shows them in pure SQL so you can see exactly what the binary +# wraps with runtime contracts. +# +# top-customers.sql customer × rental → 10 by rental count +# top-films.sql film × inventory × rental → 10 by rental count +# top-actors.sql actor × film_actor → 10 by distinct films +# +# Usage: +# scripts/demo-3-pagila.sh +# +set -euo pipefail +cd "$(dirname "$0")/.." + +bar() { printf '\n\033[1;36m=== %s ===\033[0m\n\n' "$*"; } + +if ! docker compose ps postgres 2>/dev/null | grep -q "Up"; then + bar "starting postgres + loading Pagila (one-time)" + make pagila >/dev/null +fi + +PSQL=(docker compose exec -T postgres psql -U postgres -d pagila) + +for q in top-customers top-films top-actors; do + bar "pagila-analytics/${q}.sql" + "${PSQL[@]}" -f - < "sql/pagila-analytics/${q}.sql" +done + +bar "pagila analytics complete (run scripts/demo-4-rust.sh for the contract-enforced version)" diff --git a/scripts/demo-4-rust.sh b/scripts/demo-4-rust.sh new file mode 100755 index 0000000..df66e94 --- /dev/null +++ b/scripts/demo-4-rust.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# demo-4-rust.sh — Rust capstone: postgres-reports binary, three reports. +# +# Same SQL as scripts/demo-3-pagila.sh, but wrapped by a Rust binary that +# uses an async sqlx::PgPool and enforces named runtime contracts on every +# result before printing JSON. If the database disagrees with the schema's +# invariants, the process aborts loudly instead of shipping corrupt JSON. +# +# Contracts proved per call: +# row_count == limit +# top row count >= 1 +# ORDER BY DESC monotonic +# per-report id > 0, text field non-empty +# +# Output: pretty-printed JSON to stdout AND to out/ as files. +# +# Usage: +# scripts/demo-4-rust.sh # default --limit 10 +# scripts/demo-4-rust.sh 5 # --limit 5 +# +set -euo pipefail +cd "$(dirname "$0")/.." + +LIMIT="${1:-10}" +OUT_DIR="out" + +bar() { printf '\n\033[1;36m=== %s ===\033[0m\n\n' "$*"; } + +if ! docker compose ps postgres 2>/dev/null | grep -q "Up"; then + bar "starting postgres + loading Pagila (one-time)" + make pagila >/dev/null +fi + +bar "building postgres-reports (release)" +cargo build --release --bin postgres-reports + +mkdir -p "$OUT_DIR" + +export DATABASE_URL="${DATABASE_URL:-postgres://postgres:postgres@localhost:5432/pagila}" + +for r in customers films actors; do + bar "postgres-reports --report ${r} --limit ${LIMIT}" + cargo run --release --quiet --bin postgres-reports -- \ + --report "$r" --limit "$LIMIT" --out "${OUT_DIR}/${r}.json" + echo + echo "wrote ${OUT_DIR}/${r}.json" +done + +bar "capstone complete — JSON written to ${OUT_DIR}/" +ls -lh "$OUT_DIR"/*.json diff --git a/scripts/demo-all.sh b/scripts/demo-all.sh new file mode 100755 index 0000000..70080e7 --- /dev/null +++ b/scripts/demo-all.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# demo-all.sh — run every demo in sequence. +# +# 1. SQL fundamentals (scripts/demo-1-fundamentals.sh) +# 2. Joins (scripts/demo-2-joins.sh) +# 3. Pagila analytics SQL (scripts/demo-3-pagila.sh) +# 4. Rust capstone (scripts/demo-4-rust.sh) +# +# Usage: +# scripts/demo-all.sh +# +set -euo pipefail +cd "$(dirname "$0")/.." + +bar() { printf '\n\033[1;33m################ %s ################\033[0m\n' "$*"; } + +bar "DEMO 1/4 — SQL fundamentals" +scripts/demo-1-fundamentals.sh + +bar "DEMO 2/4 — Joins" +scripts/demo-2-joins.sh + +bar "DEMO 3/4 — Pagila analytics" +scripts/demo-3-pagila.sh + +bar "DEMO 4/4 — Rust capstone" +scripts/demo-4-rust.sh + +bar "ALL DEMOS COMPLETE" diff --git a/sql/pagila-analytics/top-actors.sql b/sql/pagila-analytics/top-actors.sql index a8aa13b..646d897 100644 --- a/sql/pagila-analytics/top-actors.sql +++ b/sql/pagila-analytics/top-actors.sql @@ -1,3 +1,7 @@ +-- Pagila top-N actors by distinct film count. +-- Joins actor to the film_actor bridge table to count how many distinct +-- films each actor appeared in. The Rust binary `postgres-reports +-- --report actors` runs the same query and enforces runtime contracts. SELECT a.actor_id, a.first_name, a.last_name, COUNT(DISTINCT fa.film_id) AS film_count FROM actor a diff --git a/sql/pagila-analytics/top-customers.sql b/sql/pagila-analytics/top-customers.sql index b87d32d..4ff7264 100644 --- a/sql/pagila-analytics/top-customers.sql +++ b/sql/pagila-analytics/top-customers.sql @@ -1,3 +1,8 @@ +-- Pagila top-N customers by rental count. +-- Joins customer to rental and ranks customers by the number of rentals +-- they appear in. The Rust binary `postgres-reports --report customers` +-- executes the same query and asserts runtime contracts on the result +-- (see crates/postgres-reports/src/lib.rs). SELECT c.customer_id, c.first_name || ' ' || c.last_name AS name, COUNT(r.rental_id) AS rental_count, diff --git a/sql/pagila-analytics/top-films.sql b/sql/pagila-analytics/top-films.sql index dab31c1..e214ab7 100644 --- a/sql/pagila-analytics/top-films.sql +++ b/sql/pagila-analytics/top-films.sql @@ -1,3 +1,7 @@ +-- Pagila top-N films by rental count. +-- Walks film -> inventory -> rental to count how many times each film +-- was rented. The Rust binary `postgres-reports --report films` runs +-- the same query and enforces runtime contracts on the result. SELECT f.film_id, f.title, COUNT(r.rental_id) AS rental_count FROM film f LEFT JOIN inventory i ON i.film_id = f.film_id