diff --git a/.openspec/specs/006-btree/spec.md b/.openspec/specs/006-btree/spec.md index 1fc454c..dbb8298 100644 --- a/.openspec/specs/006-btree/spec.md +++ b/.openspec/specs/006-btree/spec.md @@ -121,9 +121,17 @@ A column declared exactly `INTEGER PRIMARY KEY` is not stored in the record (SQL - WHEN the cursor decodes a row's payload via `record::decode_record` - THEN the alias column's decoded value MUST be `Value::Null` (faithful to the stored bytes), and callers needing the real value MUST substitute the row's own `rowid` -**Tests:** `src/schema/ddl_reader.rs` (planned) +**Tests:** `src/btree.rs::rowid_alias_column_decodes_as_null_not_substituted` -Covered functionally once #34 (DDL reader) lands and can identify the alias column; flagging here rather than leaving this scenario unlinked. +#### Scenario: A higher layer substitutes the alias column with the row's own rowid + +- GIVEN a table with an `INTEGER PRIMARY KEY` column, queried via `SELECT` +- WHEN the alias column is projected (directly or via `SELECT *`), including through a covering-index scan where the value comes from an index leaf's own rowid rather than a table lookup +- THEN the returned value MUST be the row's actual rowid, byte-identical to the pinned oracle's value, never the `Value::Null` this layer returns + +**Tests:** `src/dump.rs::tests::rowid_alias_detects_plain_integer_primary_key`, `tests/corpus/no_stats_optimizations_test.rs::covering_index_select_star_with_rowid_alias_matches_oracle`, `tests/corpus/no_stats_optimizations_test.rs::covering_index_select_star_with_rowid_alias_non_unique_duplicates_matches_oracle` + +Substitution landed via #34 (DDL reader)'s `rowid_alias_from_sql`/`TableSchema::rowid_alias`, now wired through `src/codegen/select/projection.rs`, `src/codegen/stmt/insert.rs`, and `src/codegen/stmt/update.rs` — this requirement's original "(planned)" note was stale; the two scenarios above are both discharged. ### Requirement 5: Index B-Tree Page Format [MUST] diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d6b2d7..b78d98f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,14 @@ All notable changes to sqlite-rs. Format follows [Keep a Changelog](https://keep ### Docs +- 006-btree Req 4 (rowid-alias columns) read as unimplemented ("covered + functionally once #34 lands"), but #34's DDL reader + (`rowid_alias_from_sql`/`TableSchema::rowid_alias`) already landed and + substitution is already wired through `codegen/select/projection.rs`, + `codegen/stmt/insert.rs`, and `codegen/stmt/update.rs` — the note was + stale. Added the one missing piece, a btree-layer unit test proving + this module itself decodes the alias column as `Value::Null`, and + split the requirement into its two scenarios, both now test-linked. - Audited all 11 specs for drift between prose and implementation and fixed every confirmed instance; specs 004, 007 and 008 were already clean. No requirement semantics changed — citations, type definitions and diff --git a/src/btree.rs b/src/btree.rs index 8ba2aad..9353d10 100644 --- a/src/btree.rs +++ b/src/btree.rs @@ -1797,6 +1797,24 @@ mod tests { assert!(cursor.next_row().unwrap().is_none()); } + /// 006-btree Requirement 4: a plain `INTEGER PRIMARY KEY` column isn't + /// stored in the record at all — SQLite encodes it as `NULL` and + /// expects a higher layer to substitute the cell's own rowid. This + /// layer must decode it faithfully as `Value::Null`, never attempt + /// schema-aware substitution itself (it has no schema information). + #[test] + fn rowid_alias_column_decodes_as_null_not_substituted() { + let mut cursor = open_cursor("select_parity.db"); + let row = cursor.first_row().unwrap().unwrap(); + assert_eq!(row.rowid, 1); + let values = decode_record(&row.payload, TextEncoding::Utf8).unwrap(); + assert_eq!( + values[0], + Value::Null, + "the rowid-alias column must decode as NULL at this layer, not the rowid" + ); + } + #[test] fn table_multipage_full_scan_matches_oracle() { let mut cursor = open_cursor("table_multipage.db");