Skip to content

Commit 9a821f5

Browse files
noahgiftclaude
andcommitted
fix: cargo fmt + harden SQL examples
- cargo fmt --all cleanup across lib.rs / main.rs / bin_smoke.rs / benches/contract_assertions.rs (max-line-width drift; CI gate caught it). Tests + clippy + 100% coverage all still hold. - sql/01-fundamentals/01-connect.sql + 02-show-tables.sql: psql backslash meta-commands consume the rest of the line, so trailing `-- comment` text was leaking into args (`\l: extra argument "list" ignored`, `\c: invalid integer value "to" for connection option "port"`). Moved every comment onto its own line above the meta-command. - sql/02-joins/03-explain-analyze.sql: original predicate matched ~99% of rows, so the planner stayed on Seq Scan even after the index was created — the example claimed a plan flip that didn't happen. Strengthened to show wide-vs-narrow predicate side by side: same wide query before/after the index (still Seq Scan, still ~2.4 ms) plus a narrow-day query that flips to Index Scan in 0.019 ms. Now actually teaches the lesson. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 505cc5e commit 9a821f5

7 files changed

Lines changed: 151 additions & 37 deletions

File tree

crates/postgres-reports/benches/contract_assertions.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion};
1111
// the asserts look at: a sorted, fixed-shape Vec with a numeric metric.
1212

1313
fn synthetic_rows(n: usize) -> Vec<(i32, String, i64)> {
14-
(0..n).map(|i| (i as i32 + 1, format!("NAME {i}"), (n - i) as i64)).collect()
14+
(0..n)
15+
.map(|i| (i as i32 + 1, format!("NAME {i}"), (n - i) as i64))
16+
.collect()
1517
}
1618

1719
fn check_invariants(rows: &[(i32, String, i64)]) {
@@ -31,9 +33,15 @@ fn bench_invariants(c: &mut Criterion) {
3133
let rows_100 = synthetic_rows(100);
3234
let rows_1000 = synthetic_rows(1000);
3335

34-
c.bench_function("invariants_10", |b| b.iter(|| check_invariants(black_box(&rows_10))));
35-
c.bench_function("invariants_100", |b| b.iter(|| check_invariants(black_box(&rows_100))));
36-
c.bench_function("invariants_1000", |b| b.iter(|| check_invariants(black_box(&rows_1000))));
36+
c.bench_function("invariants_10", |b| {
37+
b.iter(|| check_invariants(black_box(&rows_10)))
38+
});
39+
c.bench_function("invariants_100", |b| {
40+
b.iter(|| check_invariants(black_box(&rows_100)))
41+
});
42+
c.bench_function("invariants_1000", |b| {
43+
b.iter(|| check_invariants(black_box(&rows_1000)))
44+
});
3745
}
3846

3947
criterion_group!(benches, bench_invariants);

crates/postgres-reports/src/lib.rs

Lines changed: 87 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,10 @@ fn write_to_path(path: &Path, body: &str) -> Result<()> {
9494
fn assert_well_formed_json(body: &str) {
9595
let parsed: serde_json::Value =
9696
serde_json::from_str(body).expect("run-output: rendered JSON must parse");
97-
assert!(parsed.is_array(), "run-output: rendered JSON must be an array");
97+
assert!(
98+
parsed.is_array(),
99+
"run-output: rendered JSON must be an array"
100+
);
98101
eprintln!("contract: run-output OK");
99102
}
100103

@@ -247,42 +250,74 @@ pub async fn top_actors(pool: &PgPool, limit: i64) -> Result<Vec<TopActor>> {
247250
/// the row count matches the requested limit, the leading row's metric is
248251
/// non-trivial, and the metric is monotonically non-increasing.
249252
fn assert_topn_invariants<T>(rows: &[T], limit: usize, label: &str, metric: impl Fn(&T) -> i64) {
250-
assert_eq!(rows.len(), limit, "{label}-row-count: expected {limit}, got {}", rows.len());
253+
assert_eq!(
254+
rows.len(),
255+
limit,
256+
"{label}-row-count: expected {limit}, got {}",
257+
rows.len()
258+
);
251259
eprintln!("contract: {label}-row-count OK");
252260

253-
assert!(metric(&rows[0]) >= 1, "{label}-top-nonzero: leading row metric must be >= 1");
261+
assert!(
262+
metric(&rows[0]) >= 1,
263+
"{label}-top-nonzero: leading row metric must be >= 1"
264+
);
254265
eprintln!("contract: {label}-top-nonzero OK");
255266

256267
for w in rows.windows(2) {
257-
assert!(metric(&w[0]) >= metric(&w[1]), "{label}-monotonic: ORDER BY DESC violated");
268+
assert!(
269+
metric(&w[0]) >= metric(&w[1]),
270+
"{label}-monotonic: ORDER BY DESC violated"
271+
);
258272
}
259273
eprintln!("contract: {label}-monotonic OK");
260274
}
261275

262276
fn assert_contracts_customers(rows: &[TopCustomer], limit: usize) {
263277
assert_topn_invariants(rows, limit, "customers", |r| r.rental_count);
264278
for r in rows {
265-
assert!(r.customer_id > 0, "customers-row-shape: customer_id must be positive (got {})", r.customer_id);
266-
assert!(r.name.contains(' '), "customers-row-shape: name must be 'First Last' (got {:?})", r.name);
279+
assert!(
280+
r.customer_id > 0,
281+
"customers-row-shape: customer_id must be positive (got {})",
282+
r.customer_id
283+
);
284+
assert!(
285+
r.name.contains(' '),
286+
"customers-row-shape: name must be 'First Last' (got {:?})",
287+
r.name
288+
);
267289
}
268290
eprintln!("contract: customers-row-shape OK");
269291
}
270292

271293
fn assert_contracts_films(rows: &[TopFilm], limit: usize) {
272294
assert_topn_invariants(rows, limit, "films", |r| r.rental_count);
273295
for r in rows {
274-
assert!(r.film_id > 0, "films-row-shape: film_id must be positive (got {})", r.film_id);
275-
assert!(!r.title.is_empty(), "films-row-shape: title must be non-empty");
296+
assert!(
297+
r.film_id > 0,
298+
"films-row-shape: film_id must be positive (got {})",
299+
r.film_id
300+
);
301+
assert!(
302+
!r.title.is_empty(),
303+
"films-row-shape: title must be non-empty"
304+
);
276305
}
277306
eprintln!("contract: films-row-shape OK");
278307
}
279308

280309
fn assert_contracts_actors(rows: &[TopActor], limit: usize) {
281310
assert_topn_invariants(rows, limit, "actors", |r| r.film_count);
282311
for r in rows {
283-
assert!(r.actor_id > 0, "actors-row-shape: actor_id must be positive (got {})", r.actor_id);
284-
assert!(!r.first_name.is_empty() && !r.last_name.is_empty(),
285-
"actors-row-shape: first/last name must be non-empty");
312+
assert!(
313+
r.actor_id > 0,
314+
"actors-row-shape: actor_id must be positive (got {})",
315+
r.actor_id
316+
);
317+
assert!(
318+
!r.first_name.is_empty() && !r.last_name.is_empty(),
319+
"actors-row-shape: first/last name must be non-empty"
320+
);
286321
}
287322
eprintln!("contract: actors-row-shape OK");
288323
}
@@ -292,13 +327,27 @@ mod tests {
292327
use super::*;
293328

294329
fn customer(id: i32, name: &str, count: i64) -> TopCustomer {
295-
TopCustomer { customer_id: id, name: name.into(), rental_count: count, email: None }
330+
TopCustomer {
331+
customer_id: id,
332+
name: name.into(),
333+
rental_count: count,
334+
email: None,
335+
}
296336
}
297337
fn film(id: i32, title: &str, count: i64) -> TopFilm {
298-
TopFilm { film_id: id, title: title.into(), rental_count: count }
338+
TopFilm {
339+
film_id: id,
340+
title: title.into(),
341+
rental_count: count,
342+
}
299343
}
300344
fn actor(id: i32, first: &str, last: &str, count: i64) -> TopActor {
301-
TopActor { actor_id: id, first_name: first.into(), last_name: last.into(), film_count: count }
345+
TopActor {
346+
actor_id: id,
347+
first_name: first.into(),
348+
last_name: last.into(),
349+
film_count: count,
350+
}
302351
}
303352

304353
#[test]
@@ -340,7 +389,10 @@ mod tests {
340389

341390
#[test]
342391
fn films_contracts_pass_on_well_formed_rows() {
343-
let rows = vec![film(1, "BUCKET BROTHERHOOD", 34), film(2, "ROCKETEER MOTHER", 33)];
392+
let rows = vec![
393+
film(1, "BUCKET BROTHERHOOD", 34),
394+
film(2, "ROCKETEER MOTHER", 33),
395+
];
344396
assert_contracts_films(&rows, 2);
345397
}
346398

@@ -376,7 +428,10 @@ mod tests {
376428

377429
#[test]
378430
fn actors_contracts_pass_on_well_formed_rows() {
379-
let rows = vec![actor(1, "GINA", "DEGENERES", 42), actor(2, "WALTER", "TORN", 41)];
431+
let rows = vec![
432+
actor(1, "GINA", "DEGENERES", 42),
433+
actor(2, "WALTER", "TORN", 41),
434+
];
380435
assert_contracts_actors(&rows, 2);
381436
}
382437

@@ -419,7 +474,10 @@ mod tests {
419474
#[tokio::test]
420475
async fn pool_returns_error_for_unreachable_url() {
421476
let result = pool("postgres://nobody@127.0.0.1:1/none").await;
422-
assert!(result.is_err(), "unreachable URL must return Err, not panic");
477+
assert!(
478+
result.is_err(),
479+
"unreachable URL must return Err, not panic"
480+
);
423481
}
424482

425483
#[test]
@@ -458,9 +516,18 @@ mod tests {
458516
std::fs::write(&blocker, "i am a file, not a directory").expect("seed blocker");
459517
let target = blocker.join("inner.json");
460518
let result = write_to_path(&target, "[]");
461-
assert!(result.is_err(), "create_dir_all must fail under a regular file");
462-
let msg = format!("{:?}", result.expect_err("create_dir_all should have failed"));
463-
assert!(msg.contains("failed to create directory"), "unexpected error: {msg}");
519+
assert!(
520+
result.is_err(),
521+
"create_dir_all must fail under a regular file"
522+
);
523+
let msg = format!(
524+
"{:?}",
525+
result.expect_err("create_dir_all should have failed")
526+
);
527+
assert!(
528+
msg.contains("failed to create directory"),
529+
"unexpected error: {msg}"
530+
);
464531
std::fs::remove_file(&blocker).ok();
465532
}
466533

crates/postgres-reports/src/main.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ use clap::{Parser, ValueEnum};
99
use postgres_reports::{pool, run, Report, RunOptions};
1010

1111
#[derive(Parser, Debug)]
12-
#[command(name = "postgres-reports", version, about = "Sakila reports via sqlx + Postgres")]
12+
#[command(
13+
name = "postgres-reports",
14+
version,
15+
about = "Sakila reports via sqlx + Postgres"
16+
)]
1317
struct Cli {
1418
#[arg(long, value_enum, default_value_t = ReportArg::Customers)]
1519
report: ReportArg,
@@ -20,8 +24,11 @@ struct Cli {
2024
#[arg(long)]
2125
out: Option<std::path::PathBuf>,
2226

23-
#[arg(long, env = "DATABASE_URL",
24-
default_value = "postgres://postgres:postgres@localhost:5432/pagila")]
27+
#[arg(
28+
long,
29+
env = "DATABASE_URL",
30+
default_value = "postgres://postgres:postgres@localhost:5432/pagila"
31+
)]
2532
database_url: String,
2633
}
2734

crates/postgres-reports/tests/bin_smoke.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ fn binary_errors_on_unreachable_database() {
7676
.env("DATABASE_URL", "postgres://nobody@127.0.0.1:1/none")
7777
.output()
7878
.expect("spawn binary");
79-
assert!(!output.status.success(), "binary should fail with unreachable DB");
79+
assert!(
80+
!output.status.success(),
81+
"binary should fail with unreachable DB"
82+
);
8083
}
8184

8285
#[test]

sql/01-fundamentals/01-connect.sql

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
-- The first commands you run after `psql -d pagila`. Module 1.1.
2-
\l -- list databases
3-
\c pagila -- connect to pagila
4-
\dt -- list tables in current database
5-
\d film -- describe the film table
2+
-- Note: psql backslash commands consume the rest of the line, so put
3+
-- comments on their own line above each command — never trailing.
4+
5+
-- list databases
6+
\l
7+
8+
-- connect to pagila
9+
\c pagila
10+
11+
-- list tables in current database
12+
\dt
13+
14+
-- describe the film table
15+
\d film
616

717
SELECT version();

sql/01-fundamentals/02-show-tables.sql

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
-- The Postgres equivalents of MySQL's SHOW commands. Module 1.2.
2-
\dt -- list tables (\dt+ adds size, owner)
3-
\dn -- list schemas
4-
\du -- list roles (users + groups)
5-
\df -- list functions
2+
3+
-- list tables (\dt+ adds size, owner)
4+
\dt
5+
6+
-- list schemas
7+
\dn
8+
9+
-- list roles (users + groups)
10+
\du
11+
12+
-- list functions
13+
\df
614

715
-- Information_schema is the SQL-standard alternative.
816
SELECT table_name
Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
11
-- EXPLAIN (ANALYZE, BUFFERS) reveals what the planner actually did.
22
-- Look for "Seq Scan" (full table) vs "Index Scan" / "Bitmap Heap Scan".
33

4+
-- Wide predicate: 16k rows, ~99% match. Even after we add the index,
5+
-- the planner stays on Seq Scan because reading the whole table
6+
-- sequentially is faster than chasing index pointers for that many rows.
47
EXPLAIN (ANALYZE, BUFFERS)
58
SELECT customer_id, COUNT(*) FROM rental
69
WHERE rental_date >= '2022-04-01'
710
GROUP BY customer_id;
811

9-
-- Build an index, re-run, watch the plan flip.
1012
CREATE INDEX IF NOT EXISTS idx_rental_date ON rental (rental_date);
1113

14+
-- Same query, post-index: still Seq Scan. The index didn't help here.
15+
-- Lesson: indexes earn their cost when the predicate is selective.
1216
EXPLAIN (ANALYZE, BUFFERS)
1317
SELECT customer_id, COUNT(*) FROM rental
1418
WHERE rental_date >= '2022-04-01'
1519
GROUP BY customer_id;
20+
21+
-- Narrow predicate: a single day's worth of rentals (a few rows out of
22+
-- 16k). Now the plan flips to Index Scan / Bitmap Heap Scan and the
23+
-- index pays for itself.
24+
EXPLAIN (ANALYZE, BUFFERS)
25+
SELECT * FROM rental
26+
WHERE rental_date BETWEEN '2022-05-25' AND '2022-05-26';

0 commit comments

Comments
 (0)