Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .openspec/specs/006-btree/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions src/btree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
Loading