SQLite

View Ticket
Login
2013-03-27
21:11 Fixed ticket [6bfb98df]: False report of database corruption on an INSERT plus 5 other changes (artifact: f41e9efa user: drh)
03:15
Candidate fix for ticket [6bfb98dfc0c]: Make sure invalid cursors drop all references to database pages prior to doing any insert or update. (check-in: 322a5f08 user: drh tags: trunk)
02:54 New ticket [6bfb98df] False report of database corruption on an INSERT. (artifact: a6e34319 user: drh)

Ticket Hash: 6bfb98dfc0c2fee464aa23707ba50be946b49f1c
Title: False report of database corruption on an INSERT
Status: Fixed Type: Code_Defect
Severity: Severe Priority: Immediate
Subsystem: Unknown Resolution: Rejected
Last Modified: 2013-03-27 21:11:25
Version Found In: 3.7.16
User Comments:
drh added on 2013-03-27 02:54:09:

The final INSERT in the script below reports that the database is corrupt (SQLITE_CORRUPT) and aborts. Subsequent analysis shows no database corruption.

PRAGMA page_size=512;
CREATE TABLE t1(x INTEGER PRIMARY KEY, y);
INSERT INTO t1 VALUES(1,randomblob(400));
INSERT INTO t1 VALUES(2,randomblob(400));
INSERT INTO t1 SELECT x+2, randomblob(400) FROM t1;
INSERT INTO t1 SELECT x+4, randomblob(400) FROM t1;
INSERT INTO t1 SELECT x+8, randomblob(400) FROM t1;
INSERT INTO t1 SELECT x+16, randomblob(400) FROM t1;
INSERT INTO t1 SELECT x+32, randomblob(400) FROM t1;
INSERT INTO t1 SELECT x+64, randomblob(400) FROM t1 WHERE x<10;
CREATE TRIGGER r1 AFTER INSERT ON t1 WHEN new.x=74 BEGIN
  DELETE FROM t1;
  INSERT INTO t1 VALUES(75, randomblob(400));
  INSERT INTO t1 VALUES(76, randomblob(400));
END;
INSERT INTO t1 VALUES(74, randomblob(400));

The problem occurs because the INSERT operation leaves its cursor open, though marked CURSOR_INVALID, while holding a reference to a second-level page of the btree. When the trigger fires it deletes content out from under this cursor, which is normally OK since the cursor is invalid. However, during the second INSERT inside the trigger, when a page is removed from the freelist to hold the new content, that page is checked to make sure it is unreferenced. This check fails since the invalid INSERT cursor is still holding a reference to the page. That reference makes it appear as if the page was on the freelist and in a btree at the same time and so an SQLITE_CORRUPT error is thrown. However, the page is not really on the btree since the cursor that is referencing it is marked CURSOR_INVALID.

This problem has existing in the code since late 2009. It is very difficult to hit, however (which helps to explain why it has gone undetected until now). The problem is very sensitive to the specific constants used in the script shown above.