Skip to content

Commit 09b5eb2

Browse files
committed
Use AUTOINCREMENT so generated ids are never reused (#6)
Plain INTEGER PRIMARY KEY reuses the rowid of a deleted highest row on the next insert. Declaring the id column as INTEGER PRIMARY KEY AUTOINCREMENT makes SQLite track the highest id ever assigned in sqlite_sequence and never hand it out again, which is the expected behavior for stable record identifiers. - CreateTableQuery emits PRIMARY KEY AUTOINCREMENT for the id column - Added a test asserting an id is not reused after the highest row is deleted - README documents the no-reuse guarantee
1 parent 383f75d commit 09b5eb2

3 files changed

Lines changed: 24 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ people.push_back({L"Jane", L"Doe", 41, true, 7});
182182
db.Save(people);
183183
```
184184
185-
Use `SaveAutoIncrement` when you want the library to assign the next available `id` for the saved row. The generated `id` is written back into the passed-in object, so it must be a mutable (non-`const`) lvalue.
185+
Use `SaveAutoIncrement` when you want the library to assign the next available `id` for the saved row. The database generates the `id` (via an `AUTOINCREMENT` column, so values are never reused even after rows are deleted) and it is written back into the passed-in object, which must therefore be a mutable (non-`const`) lvalue.
186186
187187
```c++
188188
// Omit the id and let sqlite-reflection assign the next available value.

src/queries.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,9 @@ std::string CreateTableQuery::CustomizedColumnName(size_t index) const {
194194
const auto is_id = name.compare(std::string("id")) == 0;
195195
name += " " + record_.member_metadata[index].sqlite_column_name;
196196

197-
return is_id ? name + " PRIMARY KEY" : name;
197+
// AUTOINCREMENT guarantees that ids are never reused, even after the row with the
198+
// highest id is deleted
199+
return is_id ? name + " PRIMARY KEY AUTOINCREMENT" : name;
198200
}
199201

200202
DeleteQuery::DeleteQuery(sqlite3* db, const Reflection& record, const QueryPredicateBase* predicate)

tests/database_test.cc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,26 @@ TEST_F(DatabaseTest, AutoIdIncrementContinuesFromExistingMaxId) {
120120
EXPECT_EQ(11, p.id);
121121
}
122122

123+
TEST_F(DatabaseTest, AutoIdIsNotReusedAfterDeletingHighestRow) {
124+
const auto& db = Database::Instance();
125+
126+
Person first{L"ada", L"lovelace", 36};
127+
db.SaveAutoIncrement(first);
128+
EXPECT_EQ(1, first.id);
129+
130+
Person second{L"grace", L"hopper", 85};
131+
db.SaveAutoIncrement(second);
132+
EXPECT_EQ(2, second.id);
133+
134+
// Remove the row with the highest id
135+
db.Delete(second);
136+
137+
// A subsequent auto-increment save must not reuse the freed id
138+
Person third{L"john", L"doe", 28};
139+
db.SaveAutoIncrement(third);
140+
EXPECT_EQ(3, third.id);
141+
}
142+
123143
TEST_F(DatabaseTest, MultipleInsertions) {
124144
const auto& db = Database::Instance();
125145

0 commit comments

Comments
 (0)