1-- Version 6: forgetting reaches the conversation it came from; the log has an index by time and can be cleared by 2-- age; what waits to be thought about survives a restart. 3-- 4-- ## Forgetting empties the turns a memory was told in 5-- 6-- A memory is told in a turn of the conversation, and the turn has an identity (`Entry.turn`, on every line of it, and 7-- on the memory's mentions). Forgetting a memory writes those identities as `tombstone_turn` rows (never words), and 8-- the schema empties the turn's text wherever the identity is written, and in any line of the turn that arrives later. 9-- A memory told before turns had identities has none, and nothing of its conversation can be found: that is a 10-- fact about the data, not a rule here. 11 12-- A mention names the turn she said the memory in. It is part of the key: the same moment on the same device in two 13-- turns is two mentions. The table is rebuilt because a key cannot be altered. 14CREATE TABLE fact_mention_with_turn ( 15 gid TEXT NOT NULL REFERENCES fact (gid) ON DELETE CASCADE, 16 at_ms INTEGER NOT NULL, 17 device TEXT NOT NULL, 18 turn TEXT NOT NULL DEFAULT '', 19 PRIMARY KEY (gid, at_ms, device, turn) 20) STRICT, WITHOUT ROWID; 21INSERT INTO fact_mention_with_turn (gid, at_ms, device) SELECT gid, at_ms, device FROM fact_mention; 22DROP TABLE fact_mention; 23ALTER TABLE fact_mention_with_turn RENAME TO fact_mention; 24 25-- ## What waits to be thought about 26-- 27-- After a turn the memory work reads the exchange and decides what to remember; it can fail or be cut short, so the 28-- exchange is kept until it has been dealt with and survives a restart. Forgetting drops these (see above): they hold her 29-- words. 30CREATE TABLE pending_reflection ( 31 id INTEGER PRIMARY KEY AUTOINCREMENT, 32 turn TEXT NOT NULL, 33 heard TEXT NOT NULL, 34 said TEXT NOT NULL, 35 description TEXT, 36 -- The names of the pictures she showed, as a JSON array. 37 pictures TEXT NOT NULL DEFAULT '[]', 38 queued_at_ms INTEGER NOT NULL 39) STRICT; 40 41-- ## The chat has been emptied for these forgettings 42-- 43-- The identities of the forgotten memories this copy of the session has been emptied for (a JSON array). See 44-- `ChatState::merge`: a copy that was not emptied for a forgetting another was emptied for cannot win. 45ALTER TABLE chat_session ADD COLUMN scrubbed TEXT NOT NULL DEFAULT '[]'; 46 47-- ## The log by time 48-- 49-- When a line was written, read from the line itself, so there is no second copy of it to disagree, and indexed: 50-- the parents' view reads a day at a time, and clearing by age finds the oldest lines without reading the rest. 51ALTER TABLE journal_line ADD COLUMN at_ms INTEGER GENERATED ALWAYS AS (json_extract(entry, '$.at_ms')) VIRTUAL; 52CREATE INDEX journal_line_by_time ON journal_line (at_ms); 53-- Whether a line still has its content (retention empties old ones into `Expired`), with an index of just those by time, so 54-- that clearing the log by age looks at lines that have something to clear and not at every line already cleared. 55ALTER TABLE journal_line ADD COLUMN live INTEGER GENERATED ALWAYS AS (json_extract(entry, '$.event') IS NOT 'Expired') VIRTUAL; 56CREATE INDEX journal_line_live_by_time ON journal_line (at_ms) WHERE live; 57-- The turn a line belongs to, likewise: forgetting a memory finds its turns' lines by it. 58ALTER TABLE journal_line ADD COLUMN turn TEXT GENERATED ALWAYS AS (json_extract(entry, '$.turn')) VIRTUAL; 59CREATE INDEX journal_line_by_turn ON journal_line (turn) WHERE turn IS NOT NULL; 60 61-- The turns of what the parents forgot, by identity: what a tombstone carries to every device. No content. 62CREATE TABLE tombstone_turn ( 63 gid TEXT NOT NULL, 64 turn TEXT NOT NULL CHECK (turn <> ''), 65 PRIMARY KEY (gid, turn) 66) STRICT, WITHOUT ROWID; 67 68-- What was said in a turn of a forgotten memory is emptied: her words, what the model wrote and what was said back, 69-- what a look at her pictures found, and a memory proposed from it; the pictures she showed are let go (a picture 70-- nothing else uses is deleted with the last line that showed it, `journal_picture_unused`). The lines stay, with 71-- their time, their writer and how the turn ended, so the log still shows that the turn happened. 72CREATE TRIGGER tombstone_turn_empties_the_turn AFTER INSERT ON tombstone_turn 73BEGIN 74 UPDATE journal_line SET entry = json_set(entry, '$.event.Heard.text', '') 75 WHERE turn = NEW.turn AND json_type(entry, '$.event.Heard.text') = 'text' AND json_extract(entry, '$.event.Heard.text') <> ''; 76 UPDATE journal_line SET entry = json_set(entry, '$.event.Heard.pictures', json('[]')) 77 WHERE turn = NEW.turn AND json_array_length(entry, '$.event.Heard.pictures') > 0; 78 UPDATE journal_line SET entry = json_set(entry, '$.event.ModelWrote.text', '') 79 WHERE turn = NEW.turn AND json_type(entry, '$.event.ModelWrote.text') = 'text' AND json_extract(entry, '$.event.ModelWrote.text') <> ''; 80 UPDATE journal_line SET entry = json_set(entry, '$.event.Said.text', '') 81 WHERE turn = NEW.turn AND json_type(entry, '$.event.Said.text') = 'text' AND json_extract(entry, '$.event.Said.text') <> ''; 82 UPDATE journal_line SET entry = json_set(entry, '$.event.PictureSeen.description', '') 83 WHERE turn = NEW.turn AND json_type(entry, '$.event.PictureSeen.description') = 'text' AND json_extract(entry, '$.event.PictureSeen.description') <> ''; 84 UPDATE journal_line SET entry = json_set(entry, '$.event.NotRemembered.fact', '') 85 WHERE turn = NEW.turn AND json_type(entry, '$.event.NotRemembered.fact') = 'text' AND json_extract(entry, '$.event.NotRemembered.fact') <> ''; 86 DELETE FROM journal_picture WHERE (device, seq) IN (SELECT device, seq FROM journal_line WHERE turn = NEW.turn); 87 DELETE FROM pending_reflection WHERE turn = NEW.turn; 88END; 89 90-- A picture goes with the last line that showed it, as it goes with the last memory that has it. 91CREATE TRIGGER journal_picture_unused AFTER DELETE ON journal_picture 92BEGIN 93 DELETE FROM picture 94 WHERE id = OLD.picture_id 95 AND NOT EXISTS (SELECT 1 FROM fact_picture WHERE picture_id = OLD.picture_id) 96 AND NOT EXISTS (SELECT 1 FROM journal_picture WHERE picture_id = OLD.picture_id); 97END; 98 99DROP TRIGGER tombstone_forgets; 100DROP TRIGGER journal_line_not_of_the_forgotten; 101 102-- Version 2's trigger, with one statement more: a parent forgetting drops what waits to be reflected on. 103CREATE TRIGGER tombstone_forgets AFTER INSERT ON tombstone 104BEGIN 105 INSERT OR IGNORE INTO tombstone_picture (gid, picture_id) SELECT gid, picture_id FROM fact_picture WHERE gid = NEW.gid; 106 UPDATE journal_line SET entry = json_set(entry, '$.event.Remembered.fact', '') 107 WHERE json_type(entry, '$.event.Remembered.fact') = 'text' AND json_extract(entry, '$.event.Remembered.fact') <> '' 108 AND (json_extract(entry, '$.event.Remembered.gid') = NEW.gid 109 OR (COALESCE(json_extract(entry, '$.event.Remembered.gid'), '') = '' AND json_extract(entry, '$.event.Remembered.fact') IN (SELECT text FROM fact WHERE gid = NEW.gid UNION SELECT text FROM fact_revision WHERE gid = NEW.gid))); 110 UPDATE journal_line SET entry = json_set(entry, '$.event.PutAway.fact', '') 111 WHERE json_type(entry, '$.event.PutAway.fact') = 'text' AND json_extract(entry, '$.event.PutAway.fact') <> '' 112 AND (json_extract(entry, '$.event.PutAway.gid') = NEW.gid 113 OR (COALESCE(json_extract(entry, '$.event.PutAway.gid'), '') = '' AND json_extract(entry, '$.event.PutAway.fact') IN (SELECT text FROM fact WHERE gid = NEW.gid UNION SELECT text FROM fact_revision WHERE gid = NEW.gid))); 114 UPDATE journal_line SET entry = json_set(entry, '$.event.Restored.fact', '') 115 WHERE json_type(entry, '$.event.Restored.fact') = 'text' AND json_extract(entry, '$.event.Restored.fact') <> '' 116 AND (json_extract(entry, '$.event.Restored.gid') = NEW.gid 117 OR (COALESCE(json_extract(entry, '$.event.Restored.gid'), '') = '' AND json_extract(entry, '$.event.Restored.fact') IN (SELECT text FROM fact WHERE gid = NEW.gid UNION SELECT text FROM fact_revision WHERE gid = NEW.gid))); 118 UPDATE journal_line SET entry = json_set(entry, '$.event.NotRemembered.fact', '') 119 WHERE json_type(entry, '$.event.NotRemembered.fact') = 'text' AND json_extract(entry, '$.event.NotRemembered.fact') <> '' 120 AND (json_extract(entry, '$.event.NotRemembered.gid') = NEW.gid 121 OR (COALESCE(json_extract(entry, '$.event.NotRemembered.gid'), '') = '' AND json_extract(entry, '$.event.NotRemembered.fact') IN (SELECT text FROM fact WHERE gid = NEW.gid UNION SELECT text FROM fact_revision WHERE gid = NEW.gid))); 122 UPDATE journal_line SET entry = json_set(entry, '$.event.Recalled.facts', json(( 123 SELECT json_group_array(CASE WHEN g.value = NEW.gid OR (g.value IS NULL AND f.value IN (SELECT text FROM fact WHERE gid = NEW.gid UNION SELECT text FROM fact_revision WHERE gid = NEW.gid)) THEN '' ELSE f.value END) 124 FROM json_each(journal_line.entry, '$.event.Recalled.facts') f 125 LEFT JOIN json_each(journal_line.entry, '$.event.Recalled.gids') g ON g.key = f.key))) 126 WHERE json_type(entry, '$.event.Recalled.facts') = 'array' 127 AND EXISTS (SELECT 1 FROM json_each(journal_line.entry, '$.event.Recalled.facts') f 128 LEFT JOIN json_each(journal_line.entry, '$.event.Recalled.gids') g ON g.key = f.key 129 WHERE f.value <> '' AND (g.value = NEW.gid OR (g.value IS NULL AND f.value IN (SELECT text FROM fact WHERE gid = NEW.gid UNION SELECT text FROM fact_revision WHERE gid = NEW.gid)))); 130 UPDATE journal_line SET entry = json_set(entry, '$.event.PictureSeen.description', '') 131 WHERE json_type(entry, '$.event.PictureSeen.description') = 'text' AND json_extract(entry, '$.event.PictureSeen.description') <> '' 132 AND EXISTS (SELECT 1 FROM json_each(journal_line.entry, '$.event.PictureSeen.pictures') p 133 WHERE p.value IN (SELECT picture_id FROM tombstone_picture WHERE gid = NEW.gid)); 134 -- A parent forgot it (a copy dropped as a duplicate has no time, and is not that): what waits to be thought about 135 -- is dropped, whole, since a waiting exchange that said the memory again would learn it back. What was said in the 136 -- turns the memory was told in is emptied when their identities are written (`tombstone_turn`), not here. 137 DELETE FROM pending_reflection WHERE NEW.at_ms <> 0; 138 DELETE FROM fact WHERE gid = NEW.gid; 139END; 140 141-- Version 2's trigger, with the turns of forgotten memories as well: a line of such a turn that arrives after the 142-- forgetting (from a device that had not heard of it) is emptied as it is written. 143CREATE TRIGGER journal_line_not_of_the_forgotten AFTER INSERT ON journal_line 144BEGIN 145 UPDATE journal_line SET entry = json_set(entry, '$.event.Remembered.fact', '') 146 WHERE position = NEW.position AND json_type(entry, '$.event.Remembered.fact') = 'text' AND json_extract(entry, '$.event.Remembered.fact') <> '' 147 AND json_extract(entry, '$.event.Remembered.gid') IN (SELECT gid FROM tombstone); 148 UPDATE journal_line SET entry = json_set(entry, '$.event.PutAway.fact', '') 149 WHERE position = NEW.position AND json_type(entry, '$.event.PutAway.fact') = 'text' AND json_extract(entry, '$.event.PutAway.fact') <> '' 150 AND json_extract(entry, '$.event.PutAway.gid') IN (SELECT gid FROM tombstone); 151 UPDATE journal_line SET entry = json_set(entry, '$.event.Restored.fact', '') 152 WHERE position = NEW.position AND json_type(entry, '$.event.Restored.fact') = 'text' AND json_extract(entry, '$.event.Restored.fact') <> '' 153 AND json_extract(entry, '$.event.Restored.gid') IN (SELECT gid FROM tombstone); 154 UPDATE journal_line SET entry = json_set(entry, '$.event.Recalled.facts', json(( 155 SELECT json_group_array(CASE WHEN g.value IN (SELECT gid FROM tombstone) THEN '' ELSE f.value END) 156 FROM json_each(journal_line.entry, '$.event.Recalled.facts') f 157 LEFT JOIN json_each(journal_line.entry, '$.event.Recalled.gids') g ON g.key = f.key))) 158 WHERE position = NEW.position AND json_type(entry, '$.event.Recalled.facts') = 'array' 159 AND EXISTS (SELECT 1 FROM json_each(journal_line.entry, '$.event.Recalled.facts') f 160 LEFT JOIN json_each(journal_line.entry, '$.event.Recalled.gids') g ON g.key = f.key 161 WHERE f.value <> '' AND (g.value IN (SELECT gid FROM tombstone))); 162 UPDATE journal_line SET entry = json_set(entry, '$.event.PictureSeen.description', '') 163 WHERE position = NEW.position AND json_type(entry, '$.event.PictureSeen.description') = 'text' AND json_extract(entry, '$.event.PictureSeen.description') <> '' 164 AND EXISTS (SELECT 1 FROM json_each(journal_line.entry, '$.event.PictureSeen.pictures') p 165 WHERE p.value IN (SELECT picture_id FROM tombstone_picture)); 166 UPDATE journal_line SET entry = json_remove(entry, '$.event.Forgot.fact') WHERE position = NEW.position AND json_type(entry, '$.event.Forgot.fact') IS NOT NULL; 167 UPDATE journal_line SET entry = json_set(entry, '$.event.Heard.text', '') 168 WHERE position = NEW.position AND turn IN (SELECT turn FROM tombstone_turn) AND json_type(entry, '$.event.Heard.text') = 'text' AND json_extract(entry, '$.event.Heard.text') <> ''; 169 UPDATE journal_line SET entry = json_set(entry, '$.event.Heard.pictures', json('[]')) 170 WHERE position = NEW.position AND turn IN (SELECT turn FROM tombstone_turn) AND json_array_length(entry, '$.event.Heard.pictures') > 0; 171 UPDATE journal_line SET entry = json_set(entry, '$.event.ModelWrote.text', '') 172 WHERE position = NEW.position AND turn IN (SELECT turn FROM tombstone_turn) AND json_type(entry, '$.event.ModelWrote.text') = 'text' AND json_extract(entry, '$.event.ModelWrote.text') <> ''; 173 UPDATE journal_line SET entry = json_set(entry, '$.event.Said.text', '') 174 WHERE position = NEW.position AND turn IN (SELECT turn FROM tombstone_turn) AND json_type(entry, '$.event.Said.text') = 'text' AND json_extract(entry, '$.event.Said.text') <> ''; 175 UPDATE journal_line SET entry = json_set(entry, '$.event.PictureSeen.description', '') 176 WHERE position = NEW.position AND turn IN (SELECT turn FROM tombstone_turn) AND json_type(entry, '$.event.PictureSeen.description') = 'text' AND json_extract(entry, '$.event.PictureSeen.description') <> ''; 177 UPDATE journal_line SET entry = json_set(entry, '$.event.NotRemembered.fact', '') 178 WHERE position = NEW.position AND turn IN (SELECT turn FROM tombstone_turn) AND json_type(entry, '$.event.NotRemembered.fact') = 'text' AND json_extract(entry, '$.event.NotRemembered.fact') <> ''; 179END; 180