chopin-pg 0.5.14

A high-performance, asynchronous PostgreSQL driver for the Chopin framework.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
# chopin-pg — Enhancement Plan (Thread-Per-Core)

> Updated: Sprint 6 — Production Hardening Complete
>
> Architecture: **Thread-per-core, Shared-Nothing, zero external deps.**
>
> I/O model: **Synchronous sockets in non-blocking mode with poll-based
> application-level timeouts.** No async runtime, no `futures`, no `tokio`.
> Each worker thread owns its own connections and pool. No `Arc`, no locks.
>
> With proper event-loop integration (epoll/kqueue + `raw_fd()`), this
> driver handles thousands of connections per core — same scalability as
> async, different programming model.

---

## Master Checklist

Legend: ✅ done · 🔧 partial · ❌ not started

---

### Phase 1 — Non-Blocking I/O  ✅ COMPLETE

| # | Item | File | Status |
|---|------|------|--------|
| 1.1 | Socket set to non-blocking after auth handshake | connection.rs L161 | ✅ |
| 1.2 | `try_fill_read_buf()` returns `WouldBlock` | connection.rs L700 | ✅ |
| 1.3 | `try_write()` non-blocking write | connection.rs L715 | ✅ |
| 1.4 | `poll_read(timeout)` with app-level timeout | connection.rs L726 | ✅ |
| 1.5 | `poll_write(data, timeout)` with app-level timeout | connection.rs L746 | ✅ |
| 1.6 | Internal `write_all()` dispatches blocking vs NB | connection.rs | ✅ |
| 1.7 | `connect_with_timeout()` | connection.rs L167 | ✅ |
| 1.8 | `set_io_timeout()` / `io_timeout()` | connection.rs L173 | ✅ |
| 1.9 | `raw_fd()` for epoll/kqueue registration | connection.rs L184 | ✅ |
| 1.10 | `set_nonblocking()` escape hatch | connection.rs L195 | ✅ |
| 1.11 | `PgError::Timeout` variant | error.rs | ✅ |
| 1.12 | `DEFAULT_IO_TIMEOUT` (5s) | connection.rs L36 | ✅ |

---

### Phase 2 — PostgreSQL Type System  🔧 ~69%

#### PgValue Variants

| # | Item | Status | Notes |
|---|------|--------|-------|
| 2.1 | Bool, Int2, Int4, Int8, Float4, Float8, Text, Bytes | ✅ | |
| 2.2 | Json(String), Jsonb(Vec<u8>) | ✅ | |
| 2.3 | Uuid([u8; 16]) | ✅ | |
| 2.4 | Date, Time, Timestamp, Timestamptz | ✅ | PG epoch, microseconds |
| 2.5 | Interval { months, days, microseconds } | ✅ | |
| 2.6 | Inet(String) | ✅ | text repr |
| 2.7 | Numeric(String) | ✅ | text for lossless precision |
| 2.8 | Array(Vec<PgValue>) | ✅ | homogeneous |
| 2.9 | MacAddr([u8; 6]) variant | ✅ | PgValue::MacAddr, text + binary codecs, ToSql/FromSql for [u8; 6] |
| 2.10 | Point { x: f64, y: f64 } variant | ✅ | PgValue::Point, text + binary codecs, ToSql/FromSql for (f64, f64) |
| 2.11 | Range types variant | ✅ | PgValue::Range(String), text codec for INT4RANGE/INT8RANGE/NUMRANGE/TSRANGE/TSTZRANGE/DATERANGE |
| 2.12 | Bit / VarBit variant | ❌ | No OID, no variant |
| 2.13 | Composite / record type | ❌ | |
| 2.14 | Custom enum support | ❌ | |

#### OID Constants

| # | Item | Status | Notes |
|---|------|--------|-------|
| 2.15 | Core scalar OIDs (BOOL..NUMERIC) | ✅ | 24 OIDs |
| 2.16 | Array OIDs (8 types) | ✅ | |
| 2.17 | Range OIDs (6 types) | ✅ | |
| 2.18 | Geometric OIDs (LINE, LSEG, BOX, PATH, POLYGON, CIRCLE) | ✅ | Added in Sprint 5 alongside Point |
| 2.19 | BIT (1560), VARBIT (1562) OIDs | ❌ | |
| 2.20 | MACADDR8 (774), UUID_ARRAY (2951), JSONB_ARRAY (3807) | 🔧 | MACADDR8 OID added |

#### Binary Codec

| # | Item | Status | Notes |
|---|------|--------|-------|
| 2.21 | `from_binary()` core types (bool/int/float/uuid/date/time/ts/interval) | ✅ | |
| 2.22 | `from_binary()` JSONB, BYTEA | ✅ | |
| 2.23 | `from_binary()` INET/CIDR (family/mask/addr) | ✅ | |
| 2.24 | `encode_inet_binary()` text-to-binary | ✅ | IPv4, IPv6, CIDR |
| 2.25 | Wire binary format in Bind (format codes per param/result) | ✅ | `query()` uses per-param format codes + binary result format |
| 2.26 | `from_binary()` for NUMERIC | ✅ | Base-10000 digit decode with sign, scale, NaN, ±Infinity |
| 2.27 | `from_binary()` for arrays | ✅ | 1-D binary array decode for all scalar element types |
| 2.28 | `from_binary()` for geometric / range / macaddr | ✅ | MacAddr binary (6 bytes), Point binary (2×f64 BE) |
| 2.29 | `to_binary_bytes()` method for encoding params as binary | ✅ | Binary encoding for scalars, UUID, date/time, interval, JSONB |

#### Text Codec

| # | Item | Status |
|---|------|--------|
| 2.30 | `to_text_bytes()` for all PgValue variants | ✅ |
| 2.31 | `from_text()` for all PgValue variants | ✅ |
| 2.32 | Array text format with escaping (`escape_array_element`) | ✅ |

#### ToSql / FromSql Trait Impls

| # | Item | Status | Notes |
|---|------|--------|-------|
| 2.33 | `ToSql` for i16, i32, i64, f32, f64, bool, &str, String, &[u8], Vec<u8> | ✅ | 10 impls |
| 2.34 | `ToSql` for `PgValue` | ✅ | |
| 2.35 | `ToSql` for `Option<T>` | ✅ | types.rs L344 |
| 2.36 | `FromSql` for i16, i32, i64, f32, f64, bool, String | ✅ | 7 impls |
| 2.37 | `FromSql` for `Option<T>` | ✅ | types.rs L511 |
| 2.38 | `ToSql`/`FromSql` for `Vec<T>` (arrays) | ✅ | Vec/slice impls for i16,i32,i64,f32,f64,bool,String |
| 2.39 | `ToSql`/`FromSql` for `std::net::IpAddr`/`Ipv4Addr`/`Ipv6Addr` | ✅ | Strips CIDR mask on FromSql |
| 2.40 | `FromSql` for `Vec<u8>` (bytea) | ✅ | |
| 2.41 | `FromSql` for `[u8; 16]` (UUID) | ✅ | |

#### Extensibility

| # | Item | Status |
|---|------|--------|
| 2.42 | Per-connection custom type registry (OID -> encode/decode) | ❌ |

---

### Phase 3 — Connection Pool  ✅ ~94%

| # | Item | File | Status |
|---|------|------|--------|
| 3.1 | `PgPoolConfig` with builder pattern (9 fields, all builders) | pool.rs L37 | ✅ |
| 3.2 | `max_size`, `min_size` | pool.rs | ✅ |
| 3.3 | `max_lifetime`, `idle_timeout` | pool.rs | ✅ |
| 3.4 | `checkout_timeout` | pool.rs | ✅ |
| 3.5 | `connection_timeout` | pool.rs | ✅ |
| 3.6 | `test_on_checkout` + `validation_query` | pool.rs | ✅ |
| 3.7 | `auto_reconnect` on stale connection | pool.rs | ✅ |
| 3.8 | FIFO idle queue (`VecDeque<PooledConn>`) | pool.rs | ✅ |
| 3.9 | `try_get()` — non-blocking, returns `WouldBlock` | pool.rs L293 | ✅ |
| 3.10 | `get()` — spin-wait with checkout timeout | pool.rs L308 | ✅ |
| 3.11 | RAII `ConnectionGuard` — auto-return on Drop | pool.rs L430 | ✅ |
| 3.12 | `reap()` — evict expired idle connections | pool.rs L346 | ✅ |
| 3.13 | `close_all()` graceful shutdown | pool.rs L415 | ✅ |
| 3.14 | `PoolStats` (7 counters) | pool.rs L154 | ✅ |
| 3.15 | `connect()` / `connect_with_config()` eager pre-connect | pool.rs L217 | ✅ |
| 3.16 | Caller-driven reap (from event-loop tick) | pool.rs | ✅ |
| 3.17 | `pool_size()`, `idle_connections()`, `active_connections()` accessors | pool.rs L390 | ✅ |
| 3.18 | Pool resize at runtime (`set_max_size()`) | pool.rs | ✅ |
| 3.19 | Discard broken connections on pool return | pool.rs | ✅ |

---

### Phase 4 — Error Handling  ✅ 100%

| # | Item | File | Status |
|---|------|------|--------|
| 4.1 | `PgError::Server` with 17 diagnostic fields | error.rs L16 | ✅ |
| 4.2 | `PgError::from_fields()` parser | error.rs | ✅ |
| 4.3 | `ErrorClass`: Transient, Permanent, Client, Pool | error.rs L54 | ✅ |
| 4.4 | `classify()` with SQLSTATE mapping (40/08/53/57/42/23/28) | error.rs | ✅ |
| 4.5 | `is_transient()`, `sql_state()`, `hint()`, `detail()` | error.rs | ✅ |
| 4.6 | `retry()` with exponential backoff | error.rs L193 | ✅ |
| 4.7 | Pool errors: PoolTimeout, PoolExhausted, PoolValidationFailed | error.rs | ✅ |
| 4.8 | I/O errors: WouldBlock, Timeout, ConnectionClosed, BufferOverflow | error.rs | ✅ |
| 4.9 | `impl std::error::Error`, `impl Display`, `impl From<io::Error>` | error.rs | ✅ |
| 4.10 | `ToParam` backward-compat blanket impl | types.rs L525 | ✅ |
| 4.11 | Error context propagation (embed query text in error) | connection.rs | ✅ |

---

### Phase 5 — COPY Protocol  ✅ 100%

| # | Item | File | Status |
|---|------|------|--------|
| 5.1 | `copy_in(sql)` -> `CopyWriter` | connection.rs L503 | ✅ |
| 5.2 | `CopyWriter::write_data()`, `write_row()` | connection.rs | ✅ |
| 5.3 | `CopyWriter::finish()` — CopyDone + CommandComplete | connection.rs | ✅ |
| 5.4 | `copy_out(sql)` -> `CopyReader` | connection.rs L534 | ✅ |
| 5.5 | `CopyReader::read_data()`, `read_all()` | connection.rs | ✅ |
| 5.6 | `CopyWriter::fail()` — CopyFail abort | connection.rs, codec.rs | ✅ |

---

### Phase 6 — LISTEN / NOTIFY  ✅ 100%

| # | Item | File | Status |
|---|------|------|--------|
| 6.1 | `Notification` struct (process_id, channel, payload) | connection.rs L96 | ✅ |
| 6.2 | `listen(channel)` | connection.rs L570 | ✅ |
| 6.3 | `notify(channel, payload)` | connection.rs L576 | ✅ |
| 6.4 | Notifications buffered in `VecDeque` during queries | connection.rs | ✅ |
| 6.5 | `drain_notifications()` | connection.rs L582 | ✅ |
| 6.6 | `has_notifications()`, `notification_count()` | connection.rs L587 | ✅ |
| 6.7 | `poll_notification()` non-blocking check | connection.rs L598 | ✅ |
| 6.8 | `unlisten(channel)` + `unlisten_all()` | connection.rs | ✅ |

---

### Phase 7 — Transactions  ✅ 100%

| # | Item | File | Status |
|---|------|------|--------|
| 7.1 | `begin()`, `commit()`, `rollback()` | connection.rs L387 | ✅ |
| 7.2 | `savepoint(name)`, `rollback_to(name)`, `release_savepoint(name)` | connection.rs L405 | ✅ |
| 7.3 | `Transaction` struct with auto-rollback on Drop | connection.rs L1030 | ✅ |
| 7.4 | `Transaction::query()`, `execute()`, `query_simple()`, `rollback_to()` | connection.rs | ✅ |
| 7.5 | Closure-based `transaction(\|tx\| { ... })` | connection.rs L435 | ✅ |
| 7.6 | `transaction_status()` accessor | connection.rs L636 | ✅ |
| 7.7 | Nested transactions via savepoints (auto-savepoint in `Transaction::transaction()`) | connection.rs | ✅ |

---

### Phase 8 — Testing & Documentation  ✅ ~100%

| # | Item | Status | Notes |
|---|------|--------|-------|
| 8.1 | Unit tests: types.rs (92 tests) | ✅ | inet, array, date/time, uuid, ipv6, Vec, IpAddr, binary codec, macaddr, point, range |
| 8.1b | Unit tests: statement.rs (24 tests) | ✅ | LRU eviction, clear, counter preservation, scale (300 entries), hash consistency |
| 8.2 | Unit tests: codec.rs (36 tests) | ✅ | all encode/decode paths, null params, parse/bind/execute/describe/close, wire helpers |
| 8.3 | Unit tests: auth.rs (26 tests) | ✅ | sha256 (NIST vectors), hmac (RFC 4231), base64 all-lengths, SCRAM state machine |
| 8.3b | Unit tests: error.rs (35 tests) | ✅ | SQLSTATE classification, retry logic, WouldBlock regression, display, from_fields |
| 8.3c | Unit tests: row.rs (30 tests) | ✅ | Rc<columns> sharing (1000-row scale), typed getters, null, out-of-range, by-name |
| 8.3d | Unit tests: pool.rs (35 tests) | ✅ | PgPoolConfig builder, PoolStats, exhaustion vs WouldBlock regression, timeout, reap |
| 8.3e | Unit tests: protocol.rs (34 tests) | ✅ | BackendTag all 21 variants + unknown, TransactionStatus, AuthType, FormatCode, roundtrip |
| 8.3f | Unit tests: connection.rs (22 tests) | ✅ | PgConfig new/clone/from_url (happy+error paths), Notification struct |
| 8.4 | Integration tests against real PostgreSQL | ✅ | 44 tests in tests/integration_test.rs (skip if no DB) |
| 8.5 | Pool integration tests (checkout, return, timeout, reap) | ✅ | 6 tests: checkout/return, stats, try_get, timeout, reap, sequential queries |
| 8.6 | COPY integration tests | ✅ | 5 tests: write_data, write_row, read_all, read_chunks, abort |
| 8.7 | LISTEN/NOTIFY integration tests | ✅ | 5 tests: listen+notify, payload, two channels, unlisten, has_notifications |
| 8.8 | Transaction integration tests | ✅ | 6 tests: begin/commit, rollback, closure commit/rollback, savepoint, nested savepoint |
| 8.9 | Error condition tests (disconnect, timeout, bad query) | ✅ | 7 tests: bad SQL, wrong cast, unique/not-null/FK violations, error variant, classification |
| 8.10 | Doc comments with examples on all public items | 🔧 | Main items covered, not exhaustive |
| 8.11 | README with pool sizing guide | ✅ | |
| 8.12 | Benchmark examples (vs sqlx, tokio-postgres) | 🔧 | Examples exist, no CI |

**Total unit tests: 362** (up from 270 in Sprint 7 / 89 in Sprint 6). **Integration tests: 44 tests** in `tests/integration_test.rs` covering all major features against real PostgreSQL (skipped automatically if DB unavailable). Also fixed 3 driver bugs: (1) `RowDescription` format-code override after Bind, (2) `CopyReader::read_data` premature fill that caused timeout on buffered data, (3) `drain_to_ready` / `read_query_results` / `read_extended_results` unconditional `fill_read_buf` that caused spurious timeouts when all data was already buffered.

---

### Phase 9 — Protocol & Connection Extras  🔧 ~79%

| # | Item | File | Status |
|---|------|------|--------|
| 9.1 | Extended Query Protocol (Parse/Bind/Execute) | codec.rs, connection.rs | ✅ |
| 9.2 | Statement cache (FNV-1a hash, auto-name) | statement.rs | ✅ |
| 9.3 | Simple Query Protocol | connection.rs L303 | ✅ |
| 9.4 | SCRAM-SHA-256 auth (zero-dep) | auth.rs | ✅ |
| 9.5 | Cleartext password auth | connection.rs L240 | ✅ |
| 9.6 | `PgConfig::from_url()` parser | connection.rs L59 | ✅ |
| 9.7 | CommandComplete tag parsing (affected rows) | connection.rs | ✅ |
| 9.8 | Server parameter tracking | connection.rs | ✅ |
| 9.9 | `Terminate` on Drop | connection.rs L1015 | ✅ |
| 9.10 | `is_alive()` check | connection.rs L693 | ✅ |
| 9.11 | `query_one()` convenience | connection.rs L372 | ✅ |
| 9.12 | TLS / SSL support | — | ❌ |
| 9.13 | Unix domain socket support | connection.rs | ✅ | PgStream enum (Tcp/Unix), PgConfig.socket_dir, from_url() percent-decode & ?host= |
| 9.14 | MD5 auth | — | ❌ recognized but returns error |
| 9.15 | `cancel_query()` via new TCP + CancelRequest | connection.rs | ✅ |
| 9.16 | Pipeline mode (multi-statement without Sync) | — | ❌ |
| 9.17 | Statement cache LRU eviction (tick-based, configurable capacity) | statement.rs, connection.rs | ✅ |
| 9.18 | Notice handler callback (`set_notice_handler()`) | connection.rs | ✅ |
| 9.19 | CopyFail encoding | codec.rs | ✅ |
| 9.20 | `execute_batch()` — multi-statement simple query | connection.rs | ✅ |
| 9.21 | `reset()` — DISCARD ALL for pool reuse | connection.rs | ✅ |
| 9.22 | `is_broken()` — broken connection flag | connection.rs | ✅ |
| 9.23 | `clear_statement_cache()` sends DEALLOCATE ALL | connection.rs | ✅ |
| 9.24 | `broken` flag set on fatal I/O errors | connection.rs | ✅ |
| 9.25 | TCP_NODELAY set on TCP connections | connection.rs | ✅ |
| 9.26 | `Drop` switches to blocking before Terminate | connection.rs | ✅ |

---

### Phase 10 — Reliability & Performance Hardening  ✅ COMPLETE (Sprint 6)

| # | Item | File | Status |
|---|------|------|--------|
| 10.1 | `broken` flag on PgConnection; set on fatal I/O errors | connection.rs | ✅ |
| 10.2 | Pool discards broken connections on `return_conn()` | pool.rs | ✅ |
| 10.3 | `reset()` / DISCARD ALL for clean pool reuse | connection.rs | ✅ |
| 10.4 | `Drop` switches to blocking before Terminate | connection.rs | ✅ |
| 10.5 | `clear_statement_cache()` sends DEALLOCATE ALL to server | connection.rs | ✅ |
| 10.6 | Statement cache `clear()` preserves counter (no name collision) | statement.rs | ✅ |
| 10.7 | MAX_MESSAGE_SIZE enforced in `message_complete()` | codec.rs | ✅ |
| 10.8 | `WouldBlock` reclassified as `Client` (not `Transient`) | error.rs | ✅ |
| 10.9 | TCP_NODELAY set on TCP connections (lower latency) | connection.rs | ✅ |
| 10.10 | `flush_write_buf(n)` — zero-copy writes from write_buf | connection.rs | ✅ |
| 10.11 | Eliminated all `.to_vec()` copies in query/COPY paths | connection.rs | ✅ |
| 10.12 | `Rc<Vec<ColumnDesc>>` shared across rows (no per-row clone) | row.rs, connection.rs | ✅ |
| 10.13 | `execute_batch(sql)` — multi-statement simple query API | connection.rs | ✅ |
| 10.14 | `set_max_size(n)` — runtime pool resize | pool.rs | ✅ |
| 10.15 | `PoolExhausted` properly raised by `try_checkout()` | pool.rs | ✅ |
| 10.16 | `is_broken()` public accessor | connection.rs | ✅ |

---

## Summary

| Phase | Done | Total | Progress |
|-------|------|-------|----------|
| 1. Non-Blocking I/O | 12 | 12 | **100%** |
| 2. Type System | 34 | 42 | **81%** |
| 3. Connection Pool | 19 | 19 | **100%** |
| 4. Error Handling | 11 | 11 | **100%** |
| 5. COPY Protocol | 6 | 6 | **100%** |
| 6. LISTEN / NOTIFY | 8 | 8 | **100%** |
| 7. Transactions | 7 | 7 | **100%** |
| 8. Testing & Docs | 6 | 13 | **46%** |
| 9. Protocol Extras | 26 | 26 | **100%** |
| 10. Reliability & Perf | 16 | 16 | **100%** |
| **Total** | **145** | **160** | **91%** |

---

## Corrections from Previous Plan

1. **2.24 `Option<T>` ToSql/FromSql was marked ❌ — actually ✅.** Both impls exist at types.rs L344 and L511.
2. **5.4 `CopyWriter::fail()` was marked ✅ — actually ❌.** `CopyFail` is defined in protocol.rs but there's no `encode_copy_fail()` in codec.rs and no `fail()` method on `CopyWriter`.
3. **Missing items not previously tracked:** `notify()`, `has_notifications()`, `notification_count()`, `query_one()`, `is_alive()`, `Terminate` on Drop, `release_savepoint()` — all exist and now listed.

---

## What to Implement Next (Priority Order)

### Sprint 1 — Quick Wins ✅ DONE

All Sprint 1 items completed:
- 2.40 `FromSql` for `Vec<u8>` (bytea)
- 2.41 `FromSql` for `[u8; 16]` (UUID)
- 2.38 `Vec<T>` / `&[T]` ToSql/FromSql for arrays (i16, i32, i64, f32, f64, bool, String)
- 2.39 IpAddr / Ipv4Addr / Ipv6Addr ToSql/FromSql
- 6.8 `unlisten()` + `unlisten_all()`
- 5.6 `CopyFail` encoding + `CopyWriter::fail()`
- 9.15 `cancel_query()` via CancelRequest protocol
- 9.19 `encode_copy_fail()` in codec.rs
- 26 new unit tests (55 total, up from 29)

### Sprint 2 — Production Blockers  ✅ DONE (except TLS)

Completed:
- 9.17 Statement cache LRU eviction (tick-based, configurable capacity, auto-Close on evict)
- 4.11 Error context propagation (query text embedded in `PgError::Server.internal_query`)
- 9.18 Notice handler callback (`set_notice_handler()` / `clear_notice_handler()`)
- 7.7 Nested transactions via savepoints (`Transaction::transaction()` with auto-SAVEPOINT)
- 4 new unit tests (59 total, up from 55)

Remaining:

| Item | Why | Effort |
|------|-----|--------|
| 9.12 TLS/SSL support | Most PG servers require SSL | Medium-Large |

### Sprint 3 — Binary Performance  ✅ DONE

Completed:
- 2.29 `to_binary_bytes()` encoding (scalars, UUID, date/time, interval, JSONB, INET)
- 2.25 Wire binary format in `query()` (per-param format codes + binary result format `[1]`)
- 2.26 `from_binary()` for NUMERIC (base-10000, sign, scale, NaN, ±Infinity)
- 2.27 `from_binary()` for arrays (1-D binary arrays for all scalar element types)
- `prefers_binary()` method for smart per-parameter format selection
- 30 new unit tests (89 total, up from 59)

### Sprint 4 — Integration Tests

| Item | Why | Effort |
|------|-----|--------|
| 8.4 Integration tests against real PG | No confidence without it | Medium |
| 8.5 Pool integration tests | Validate checkout/return/timeout/reap | Medium |
| 8.6-8.9 Feature integration tests | COPY, LISTEN/NOTIFY, transactions, errors | Medium |

### Sprint 5 — Extended Types (P2)  ✅ DONE

Completed:
- 2.9 MacAddr variant + text/binary codec + ToSql/FromSql for [u8; 6]
- 2.10 Point { x, y } variant + text/binary codec + ToSql/FromSql for (f64, f64)
- 2.11 Range types (PgValue::Range(String)) + text codec for all 6 range OIDs
- 2.18 Geometric OIDs (LINE, LSEG, BOX, PATH, POLYGON, CIRCLE) added
- 2.28 from_binary() for MacAddr (6 bytes) and Point (2×f64 BE)
- 9.13 Unix domain socket support (PgStream enum, PgConfig.socket_dir, URL parsing)
- 22 new unit tests (111 total, up from 89)

| Item | Why | Effort |
|------|-----|--------|
| 2.9 MacAddr variant + codec | Niche but complete | Small |
| 2.10 Point / geometric types | PostGIS users | Medium |
| 2.11 Range types | Powerful PG feature | Medium |
| 9.13 Unix domain sockets | Local dev convenience | Small |

### Sprint 6 — Production Hardening  ✅ DONE

Completed (16 items across all phases):
- 10.1–10.2 Broken connection flag + pool discard on return
- 10.3 `reset()` / DISCARD ALL for safe pool reuse
- 10.4 `Drop` switches to blocking before sending Terminate
- 10.5 `clear_statement_cache()` sends DEALLOCATE ALL to server first
- 10.6 Statement `clear()` preserves counter (prevents name collisions)
- 10.7 MAX_MESSAGE_SIZE (16 MB) enforced in codec layer
- 10.8 `WouldBlock` reclassified as `Client` (not `Transient`) — won't retry
- 10.9 TCP_NODELAY enabled — eliminates Nagle buffering latency
- 10.10–10.11 Zero-copy `flush_write_buf(n)` — all query/COPY paths use no `.to_vec()`
- 10.12 `Rc<Vec<ColumnDesc>>` — column metadata shared per result set, not cloned per row
- 10.13 `execute_batch(sql)` — multi-statement simple query convenience API
- 10.14 `set_max_size(n)` — runtime pool resize with idle eviction
- 10.15 `PoolExhausted` properly raised by `try_checkout()` at capacity
- 10.16 `is_broken()` public accessor for connection health checks
- 89 unit tests passing (up from 0 unit tests at Sprint 5 end)

### Sprint 7 — Advanced (P3)

| Item | Why | Effort |
|------|-----|--------|
| 2.13 Composite / record type | Complex | Large |
| 2.14 Custom enum support | Requires type registry | Medium |
| 2.42 Custom type registry | Extensibility | Medium |
| 9.16 Pipeline mode | Advanced perf optimization | Large |

---

## Design Principles

- **Zero external deps** — only `libc` in production. All crypto, codec, and protocol are hand-written.
- **Thread-per-core** — each worker owns its connections. No `Arc`, no `Mutex`, no cross-thread anything.
- **Synchronous non-blocking I/O** — sockets in NB mode, poll-based reads/writes with application-level timeouts. Event-loop integration via `raw_fd()` + epoll/kqueue. Same throughput as async with less overhead.
- **Shared-Nothing pool** — `PgPool` is per-worker, `ConnectionGuard` uses raw pointer + `PhantomData` lifetime.
- **Caller-driven scheduling** — no background threads or tasks. The caller calls `pool.reap()` from its event loop tick.