@hraness/peopleblade 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -7
- package/THIRD_PARTY_NOTICES.md +2 -2
- package/dist/cli.ts +31 -0
- package/dist/cloud-sync-client.js +1836 -0
- package/dist/migrations/016_source_binding_metadata_incarnations.sql +205 -0
- package/dist/migrations/017_person_note_content_revisions.sql +151 -0
- package/dist/peopleblade.js +2704 -690
- package/package.json +3 -2
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
PRAGMA foreign_keys = ON;
|
|
2
|
+
|
|
3
|
+
-- Schema v13 admitted same-identity guards only to reject them later. Purge
|
|
4
|
+
-- those impossible predecessors while the v14 invalidation trigger still
|
|
5
|
+
-- recognizes them, before metadata-only successors become valid.
|
|
6
|
+
DELETE FROM source_binding_transition_guards
|
|
7
|
+
WHERE NOT EXISTS (
|
|
8
|
+
SELECT 1 FROM source_binding_incarnations successor
|
|
9
|
+
WHERE successor.source_binding_id=source_binding_transition_guards.source_binding_id
|
|
10
|
+
AND successor.ordinal=source_binding_transition_guards.next_ordinal
|
|
11
|
+
)
|
|
12
|
+
AND EXISTS (
|
|
13
|
+
SELECT 1 FROM source_binding_incarnations predecessor
|
|
14
|
+
WHERE predecessor.id=source_binding_transition_guards.retiring_incarnation_id
|
|
15
|
+
AND predecessor.source_binding_id=source_binding_transition_guards.source_binding_id
|
|
16
|
+
AND predecessor.authority=source_binding_transition_guards.authority
|
|
17
|
+
AND predecessor.retired_at IS NULL
|
|
18
|
+
AND predecessor.subject_sha256=source_binding_transition_guards.next_subject_sha256
|
|
19
|
+
AND predecessor.auth_sha256=source_binding_transition_guards.next_auth_sha256
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
DROP TRIGGER source_binding_transition_guards_snapshot_insert;
|
|
23
|
+
|
|
24
|
+
CREATE TRIGGER source_binding_transition_guards_snapshot_insert
|
|
25
|
+
BEFORE INSERT ON source_binding_transition_guards FOR EACH ROW
|
|
26
|
+
WHEN NOT EXISTS (
|
|
27
|
+
SELECT 1 FROM source_binding_incarnations incarnation
|
|
28
|
+
JOIN source_bindings binding ON binding.id=incarnation.source_binding_id
|
|
29
|
+
WHERE incarnation.id=NEW.retiring_incarnation_id
|
|
30
|
+
AND incarnation.source_binding_id=NEW.source_binding_id
|
|
31
|
+
AND incarnation.retired_at IS NULL
|
|
32
|
+
AND incarnation.ordinal+1=NEW.next_ordinal
|
|
33
|
+
AND incarnation.authority=NEW.authority
|
|
34
|
+
AND julianday(NEW.observed_at)>=julianday(incarnation.last_seen_at)
|
|
35
|
+
AND incarnation.last_seen_at=NEW.expected_last_seen_at
|
|
36
|
+
AND incarnation.metadata_json=NEW.expected_metadata_json
|
|
37
|
+
AND binding.authority=NEW.authority
|
|
38
|
+
AND binding.subject_sha256=incarnation.subject_sha256
|
|
39
|
+
AND binding.auth_sha256=incarnation.auth_sha256
|
|
40
|
+
AND binding.last_seen_at=NEW.expected_last_seen_at
|
|
41
|
+
AND binding.metadata_json=NEW.expected_metadata_json
|
|
42
|
+
AND binding.complete_realm_inventory_observed_at
|
|
43
|
+
IS NEW.expected_complete_realm_inventory_observed_at
|
|
44
|
+
AND binding.complete_realm_inventory_sha256
|
|
45
|
+
IS NEW.expected_complete_realm_inventory_sha256
|
|
46
|
+
AND (
|
|
47
|
+
(
|
|
48
|
+
(incarnation.subject_sha256<>NEW.next_subject_sha256
|
|
49
|
+
OR incarnation.auth_sha256<>NEW.next_auth_sha256)
|
|
50
|
+
AND NOT EXISTS (
|
|
51
|
+
SELECT 1 FROM source_binding_incarnations owner
|
|
52
|
+
WHERE owner.authority=NEW.authority
|
|
53
|
+
AND owner.subject_sha256=NEW.next_subject_sha256
|
|
54
|
+
AND owner.source_binding_id<>NEW.source_binding_id
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
OR (
|
|
58
|
+
incarnation.subject_sha256=NEW.next_subject_sha256
|
|
59
|
+
AND incarnation.auth_sha256=NEW.next_auth_sha256
|
|
60
|
+
AND incarnation.metadata_json<>NEW.metadata_json
|
|
61
|
+
AND NEW.expected_complete_realm_inventory_observed_at IS NOT NULL
|
|
62
|
+
AND NEW.expected_complete_realm_inventory_sha256 IS NOT NULL
|
|
63
|
+
AND NEW.complete_realm_inventory_sha256
|
|
64
|
+
=NEW.expected_complete_realm_inventory_sha256
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
BEGIN SELECT RAISE(ABORT, 'source binding transition guard does not match current reviewed state'); END;
|
|
69
|
+
|
|
70
|
+
DROP TRIGGER source_binding_incarnations_chain_insert;
|
|
71
|
+
|
|
72
|
+
CREATE TRIGGER source_binding_incarnations_chain_insert
|
|
73
|
+
BEFORE INSERT ON source_binding_incarnations FOR EACH ROW
|
|
74
|
+
WHEN NEW.transition_kind='explicit-rebind' AND NOT EXISTS (
|
|
75
|
+
SELECT 1 FROM source_binding_incarnations previous
|
|
76
|
+
WHERE previous.id=NEW.previous_incarnation_id
|
|
77
|
+
AND previous.source_binding_id=NEW.source_binding_id
|
|
78
|
+
AND previous.authority=NEW.authority
|
|
79
|
+
AND previous.ordinal=NEW.ordinal-1
|
|
80
|
+
AND previous.retired_at IS NOT NULL
|
|
81
|
+
AND (
|
|
82
|
+
previous.subject_sha256<>NEW.subject_sha256
|
|
83
|
+
OR previous.auth_sha256<>NEW.auth_sha256
|
|
84
|
+
OR previous.metadata_json<>NEW.metadata_json
|
|
85
|
+
)
|
|
86
|
+
AND EXISTS (
|
|
87
|
+
SELECT 1 FROM source_bindings binding
|
|
88
|
+
WHERE binding.id=NEW.source_binding_id AND binding.authority=NEW.authority
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
BEGIN SELECT RAISE(ABORT, 'source binding incarnation predecessor is invalid'); END;
|
|
92
|
+
|
|
93
|
+
-- Schema v13 mirrored an older writer's root metadata into the current
|
|
94
|
+
-- incarnation. Preserve that compatibility only while the writer is not
|
|
95
|
+
-- attempting to overwrite an already-installed successor. The atomic
|
|
96
|
+
-- transition trigger inserts the successor before updating the root shadow, so
|
|
97
|
+
-- its target metadata already equals the one current incarnation here.
|
|
98
|
+
CREATE TRIGGER source_bindings_current_incarnation_metadata_guard
|
|
99
|
+
BEFORE UPDATE OF metadata_json ON source_bindings FOR EACH ROW
|
|
100
|
+
WHEN EXISTS (
|
|
101
|
+
SELECT 1 FROM source_binding_incarnations current
|
|
102
|
+
WHERE current.source_binding_id=NEW.id
|
|
103
|
+
AND current.authority=NEW.authority
|
|
104
|
+
AND current.retired_at IS NULL
|
|
105
|
+
AND current.metadata_json<>NEW.metadata_json
|
|
106
|
+
)
|
|
107
|
+
BEGIN SELECT RAISE(ABORT, 'source binding metadata must shadow its current incarnation'); END;
|
|
108
|
+
|
|
109
|
+
DROP TRIGGER source_binding_transition_guards_consumed_delete;
|
|
110
|
+
|
|
111
|
+
CREATE TRIGGER source_binding_transition_guards_consumed_delete
|
|
112
|
+
BEFORE DELETE ON source_binding_transition_guards FOR EACH ROW
|
|
113
|
+
WHEN NOT (
|
|
114
|
+
(
|
|
115
|
+
EXISTS (
|
|
116
|
+
SELECT 1 FROM source_binding_incarnations predecessor
|
|
117
|
+
WHERE predecessor.id=OLD.retiring_incarnation_id
|
|
118
|
+
AND predecessor.source_binding_id=OLD.source_binding_id
|
|
119
|
+
AND predecessor.authority=OLD.authority
|
|
120
|
+
AND predecessor.ordinal=OLD.next_ordinal-1
|
|
121
|
+
AND predecessor.retired_at=OLD.observed_at
|
|
122
|
+
AND predecessor.last_seen_at=OLD.expected_last_seen_at
|
|
123
|
+
AND predecessor.metadata_json=OLD.expected_metadata_json
|
|
124
|
+
)
|
|
125
|
+
AND EXISTS (
|
|
126
|
+
SELECT 1 FROM source_binding_incarnations successor
|
|
127
|
+
WHERE successor.source_binding_id=OLD.source_binding_id
|
|
128
|
+
AND successor.authority=OLD.authority
|
|
129
|
+
AND successor.ordinal=OLD.next_ordinal
|
|
130
|
+
AND successor.subject_sha256=OLD.next_subject_sha256
|
|
131
|
+
AND successor.auth_sha256=OLD.next_auth_sha256
|
|
132
|
+
AND successor.previous_incarnation_id=OLD.retiring_incarnation_id
|
|
133
|
+
AND successor.transition_kind='explicit-rebind'
|
|
134
|
+
AND successor.transition_evidence_sha256=OLD.transition_evidence_sha256
|
|
135
|
+
AND successor.transition_evidence_json=OLD.transition_evidence_json
|
|
136
|
+
AND successor.first_seen_at=OLD.observed_at
|
|
137
|
+
AND successor.last_seen_at=OLD.observed_at
|
|
138
|
+
AND successor.retired_at IS NULL
|
|
139
|
+
AND successor.metadata_json=OLD.metadata_json
|
|
140
|
+
)
|
|
141
|
+
AND EXISTS (
|
|
142
|
+
SELECT 1 FROM source_bindings binding
|
|
143
|
+
WHERE binding.id=OLD.source_binding_id
|
|
144
|
+
AND binding.authority=OLD.authority
|
|
145
|
+
AND binding.subject_sha256=OLD.next_subject_sha256
|
|
146
|
+
AND binding.auth_sha256=OLD.next_auth_sha256
|
|
147
|
+
AND binding.last_seen_at=OLD.observed_at
|
|
148
|
+
AND binding.complete_realm_inventory_observed_at=OLD.observed_at
|
|
149
|
+
AND binding.complete_realm_inventory_sha256=OLD.complete_realm_inventory_sha256
|
|
150
|
+
AND binding.metadata_json=OLD.metadata_json
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
OR (
|
|
154
|
+
NOT EXISTS (
|
|
155
|
+
SELECT 1 FROM source_binding_incarnations successor
|
|
156
|
+
WHERE successor.source_binding_id=OLD.source_binding_id
|
|
157
|
+
AND successor.ordinal=OLD.next_ordinal
|
|
158
|
+
)
|
|
159
|
+
AND EXISTS (
|
|
160
|
+
SELECT 1 FROM source_binding_incarnations predecessor
|
|
161
|
+
JOIN source_bindings binding ON binding.id=predecessor.source_binding_id
|
|
162
|
+
WHERE predecessor.id=OLD.retiring_incarnation_id
|
|
163
|
+
AND predecessor.source_binding_id=OLD.source_binding_id
|
|
164
|
+
AND predecessor.authority=OLD.authority
|
|
165
|
+
AND predecessor.ordinal=OLD.next_ordinal-1
|
|
166
|
+
AND predecessor.retired_at IS NULL
|
|
167
|
+
AND binding.authority=OLD.authority
|
|
168
|
+
AND binding.subject_sha256=predecessor.subject_sha256
|
|
169
|
+
AND binding.auth_sha256=predecessor.auth_sha256
|
|
170
|
+
AND (
|
|
171
|
+
(
|
|
172
|
+
predecessor.subject_sha256=OLD.next_subject_sha256
|
|
173
|
+
AND predecessor.auth_sha256=OLD.next_auth_sha256
|
|
174
|
+
AND predecessor.metadata_json=OLD.metadata_json
|
|
175
|
+
)
|
|
176
|
+
OR julianday(OLD.observed_at)<julianday(predecessor.last_seen_at)
|
|
177
|
+
OR predecessor.last_seen_at IS NOT OLD.expected_last_seen_at
|
|
178
|
+
OR predecessor.metadata_json IS NOT OLD.expected_metadata_json
|
|
179
|
+
OR binding.last_seen_at IS NOT OLD.expected_last_seen_at
|
|
180
|
+
OR binding.metadata_json IS NOT OLD.expected_metadata_json
|
|
181
|
+
OR binding.complete_realm_inventory_observed_at
|
|
182
|
+
IS NOT OLD.expected_complete_realm_inventory_observed_at
|
|
183
|
+
OR binding.complete_realm_inventory_sha256
|
|
184
|
+
IS NOT OLD.expected_complete_realm_inventory_sha256
|
|
185
|
+
OR (
|
|
186
|
+
predecessor.subject_sha256=OLD.next_subject_sha256
|
|
187
|
+
AND predecessor.auth_sha256=OLD.next_auth_sha256
|
|
188
|
+
AND (
|
|
189
|
+
OLD.expected_complete_realm_inventory_observed_at IS NULL
|
|
190
|
+
OR OLD.expected_complete_realm_inventory_sha256 IS NULL
|
|
191
|
+
OR OLD.complete_realm_inventory_sha256
|
|
192
|
+
IS NOT OLD.expected_complete_realm_inventory_sha256
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
OR EXISTS (
|
|
196
|
+
SELECT 1 FROM source_binding_incarnations owner
|
|
197
|
+
WHERE owner.authority=OLD.authority
|
|
198
|
+
AND owner.subject_sha256=OLD.next_subject_sha256
|
|
199
|
+
AND owner.source_binding_id<>OLD.source_binding_id
|
|
200
|
+
)
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
BEGIN SELECT RAISE(ABORT, 'source binding transition guard can only be deleted after exact consumption or proven invalidation'); END;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
PRAGMA foreign_keys = ON;
|
|
2
|
+
|
|
3
|
+
-- User edits are overlays; original provider content and attribution stay immutable.
|
|
4
|
+
CREATE TABLE person_note_content_revisions (
|
|
5
|
+
note_id INTEGER NOT NULL REFERENCES person_notes(id) ON DELETE RESTRICT,
|
|
6
|
+
revision INTEGER NOT NULL CHECK(revision BETWEEN 1 AND 9007199254740991),
|
|
7
|
+
expected_revision INTEGER NOT NULL CHECK(expected_revision BETWEEN 0 AND 9007199254740990),
|
|
8
|
+
request_id TEXT NOT NULL UNIQUE CHECK(length(request_id) = 36),
|
|
9
|
+
expected_context_sha256 TEXT NOT NULL CHECK(
|
|
10
|
+
length(expected_context_sha256) = 64
|
|
11
|
+
AND expected_context_sha256 NOT GLOB '*[^0-9a-f]*'
|
|
12
|
+
),
|
|
13
|
+
context_json TEXT NOT NULL CHECK(json_valid(context_json)),
|
|
14
|
+
title TEXT CHECK(title IS NULL OR (length(title) BETWEEN 1 AND 1024 AND instr(title, char(0)) = 0)),
|
|
15
|
+
body TEXT NOT NULL CHECK(length(CAST(body AS BLOB)) BETWEEN 1 AND 65536 AND instr(body, char(0)) = 0),
|
|
16
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
17
|
+
PRIMARY KEY(note_id, revision),
|
|
18
|
+
CHECK(revision = expected_revision + 1)
|
|
19
|
+
) STRICT;
|
|
20
|
+
|
|
21
|
+
CREATE TRIGGER person_note_content_revision_valid
|
|
22
|
+
BEFORE INSERT ON person_note_content_revisions FOR EACH ROW
|
|
23
|
+
BEGIN
|
|
24
|
+
SELECT CASE WHEN
|
|
25
|
+
EXISTS (SELECT 1 FROM person_note_content_revisions prior
|
|
26
|
+
WHERE prior.request_id = NEW.request_id
|
|
27
|
+
OR (prior.note_id = NEW.note_id AND prior.revision = NEW.revision))
|
|
28
|
+
OR NEW.expected_revision IS NOT coalesce(
|
|
29
|
+
(SELECT max(prior.revision) FROM person_note_content_revisions prior WHERE prior.note_id = NEW.note_id), 0)
|
|
30
|
+
OR NOT EXISTS (SELECT 1 FROM person_notes WHERE id = NEW.note_id)
|
|
31
|
+
OR json_extract(NEW.context_json, '$.schemaVersion') IS NOT 1
|
|
32
|
+
OR json_extract(NEW.context_json, '$.databaseInstanceId') IS NOT
|
|
33
|
+
(SELECT instance_id FROM local_state WHERE singleton = 1)
|
|
34
|
+
OR json_extract(NEW.context_json, '$.noteId') IS NOT NEW.note_id
|
|
35
|
+
OR json_extract(NEW.context_json, '$.revision') IS NOT NEW.expected_revision
|
|
36
|
+
OR json_extract(NEW.context_json, '$.originalPersonId') IS NOT
|
|
37
|
+
(SELECT person_id FROM person_notes WHERE id = NEW.note_id)
|
|
38
|
+
OR json_extract(NEW.context_json, '$.attributedPersonId') IS NOT
|
|
39
|
+
(SELECT person_id FROM person_notes_current WHERE id = NEW.note_id)
|
|
40
|
+
OR json_extract(NEW.context_json, '$.attributionRevisionId') IS NOT coalesce(
|
|
41
|
+
(SELECT max(id) FROM person_note_attribution_revisions WHERE note_id = NEW.note_id), 0)
|
|
42
|
+
OR json_extract(NEW.context_json, '$.identityRevision') IS NOT
|
|
43
|
+
(SELECT identity_revision FROM local_revisions WHERE singleton = 1)
|
|
44
|
+
OR json_extract(NEW.context_json, '$.canonicalPersonId') IS NOT
|
|
45
|
+
(SELECT component.canonical_person_id FROM person_identity_components component
|
|
46
|
+
JOIN person_notes_current note ON note.person_id = component.person_id WHERE note.id = NEW.note_id)
|
|
47
|
+
OR json_extract(NEW.context_json, '$.componentPersonIds') IS NOT (
|
|
48
|
+
SELECT json_group_array(person_id) FROM (
|
|
49
|
+
SELECT member.person_id FROM person_identity_components member
|
|
50
|
+
WHERE member.canonical_person_id = (
|
|
51
|
+
SELECT component.canonical_person_id FROM person_identity_components component
|
|
52
|
+
JOIN person_notes_current note ON note.person_id = component.person_id WHERE note.id = NEW.note_id
|
|
53
|
+
) ORDER BY member.person_id
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
THEN RAISE(ABORT, 'person note revision context is not current') END;
|
|
57
|
+
END;
|
|
58
|
+
|
|
59
|
+
CREATE TABLE person_note_creation_requests (
|
|
60
|
+
request_id TEXT PRIMARY KEY CHECK(length(request_id) = 36),
|
|
61
|
+
note_id INTEGER NOT NULL UNIQUE REFERENCES person_notes(id) ON DELETE RESTRICT,
|
|
62
|
+
requested_person_id INTEGER NOT NULL REFERENCES people(id) ON DELETE RESTRICT,
|
|
63
|
+
expected_context_sha256 TEXT NOT NULL CHECK(
|
|
64
|
+
length(expected_context_sha256) = 64 AND expected_context_sha256 NOT GLOB '*[^0-9a-f]*'
|
|
65
|
+
),
|
|
66
|
+
context_json TEXT NOT NULL CHECK(json_valid(context_json)),
|
|
67
|
+
occurred_at_input TEXT NOT NULL CHECK(length(occurred_at_input) BETWEEN 1 AND 80)
|
|
68
|
+
) STRICT;
|
|
69
|
+
|
|
70
|
+
CREATE TRIGGER person_note_creation_request_valid
|
|
71
|
+
BEFORE INSERT ON person_note_creation_requests FOR EACH ROW
|
|
72
|
+
BEGIN
|
|
73
|
+
SELECT CASE WHEN
|
|
74
|
+
EXISTS (SELECT 1 FROM person_note_creation_requests prior
|
|
75
|
+
WHERE prior.request_id = NEW.request_id OR prior.note_id = NEW.note_id)
|
|
76
|
+
OR json_extract(NEW.context_json, '$.schemaVersion') IS NOT 1
|
|
77
|
+
OR json_extract(NEW.context_json, '$.databaseInstanceId') IS NOT
|
|
78
|
+
(SELECT instance_id FROM local_state WHERE singleton = 1)
|
|
79
|
+
OR json_extract(NEW.context_json, '$.personId') IS NOT NEW.requested_person_id
|
|
80
|
+
OR json_extract(NEW.context_json, '$.identityRevision') IS NOT
|
|
81
|
+
(SELECT identity_revision FROM local_revisions WHERE singleton = 1)
|
|
82
|
+
OR json_extract(NEW.context_json, '$.canonicalPersonId') IS NOT
|
|
83
|
+
(SELECT canonical_person_id FROM person_identity_components WHERE person_id = NEW.requested_person_id)
|
|
84
|
+
OR json_extract(NEW.context_json, '$.componentPersonIds') IS NOT (
|
|
85
|
+
SELECT json_group_array(person_id) FROM (
|
|
86
|
+
SELECT member.person_id FROM person_identity_components member
|
|
87
|
+
WHERE member.canonical_person_id = (
|
|
88
|
+
SELECT canonical_person_id FROM person_identity_components WHERE person_id = NEW.requested_person_id
|
|
89
|
+
) ORDER BY member.person_id
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
OR NOT EXISTS (SELECT 1 FROM person_notes note
|
|
93
|
+
WHERE note.id = NEW.note_id AND note.source = 'manual' AND note.source_id = NEW.request_id
|
|
94
|
+
AND note.person_id = json_extract(NEW.context_json, '$.canonicalPersonId'))
|
|
95
|
+
THEN RAISE(ABORT, 'person note creation context is not current') END;
|
|
96
|
+
END;
|
|
97
|
+
|
|
98
|
+
CREATE TRIGGER person_note_creation_requests_immutable_update
|
|
99
|
+
BEFORE UPDATE ON person_note_creation_requests FOR EACH ROW
|
|
100
|
+
BEGIN SELECT RAISE(ABORT, 'person note creation requests are immutable'); END;
|
|
101
|
+
CREATE TRIGGER person_note_creation_requests_immutable_delete
|
|
102
|
+
BEFORE DELETE ON person_note_creation_requests FOR EACH ROW
|
|
103
|
+
BEGIN SELECT RAISE(ABORT, 'person note creation requests are immutable'); END;
|
|
104
|
+
|
|
105
|
+
CREATE TRIGGER person_note_content_revisions_immutable_update
|
|
106
|
+
BEFORE UPDATE ON person_note_content_revisions FOR EACH ROW
|
|
107
|
+
BEGIN SELECT RAISE(ABORT, 'person note content revisions are immutable'); END;
|
|
108
|
+
CREATE TRIGGER person_note_content_revisions_immutable_delete
|
|
109
|
+
BEFORE DELETE ON person_note_content_revisions FOR EACH ROW
|
|
110
|
+
BEGIN SELECT RAISE(ABORT, 'person note content revisions are immutable'); END;
|
|
111
|
+
|
|
112
|
+
CREATE VIEW person_notes_effective AS
|
|
113
|
+
SELECT note.id, note.person_id, note.occurred_at, note.source, note.source_id,
|
|
114
|
+
CASE WHEN revision.revision IS NULL THEN note.title ELSE revision.title END AS title,
|
|
115
|
+
coalesce(revision.body, note.body) AS body,
|
|
116
|
+
note.metadata_json, note.created_at,
|
|
117
|
+
coalesce(revision.revision, 0) AS revision,
|
|
118
|
+
revision.created_at AS revised_at
|
|
119
|
+
FROM person_notes_current note
|
|
120
|
+
LEFT JOIN person_note_content_revisions revision ON revision.note_id = note.id
|
|
121
|
+
AND revision.revision = (
|
|
122
|
+
SELECT max(latest.revision) FROM person_note_content_revisions latest WHERE latest.note_id = note.id
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
CREATE VIRTUAL TABLE person_notes_effective_fts USING fts5(
|
|
126
|
+
title, body, content='person_notes_effective', content_rowid='id',
|
|
127
|
+
tokenize='unicode61 remove_diacritics 2'
|
|
128
|
+
);
|
|
129
|
+
INSERT INTO person_notes_effective_fts(rowid, title, body)
|
|
130
|
+
SELECT id, coalesce(title, ''), body FROM person_notes_effective;
|
|
131
|
+
|
|
132
|
+
CREATE TRIGGER person_notes_effective_fts_original_insert
|
|
133
|
+
AFTER INSERT ON person_notes FOR EACH ROW
|
|
134
|
+
BEGIN
|
|
135
|
+
INSERT INTO person_notes_effective_fts(rowid, title, body)
|
|
136
|
+
VALUES (NEW.id, coalesce(NEW.title, ''), NEW.body);
|
|
137
|
+
END;
|
|
138
|
+
|
|
139
|
+
CREATE TRIGGER person_notes_effective_fts_revision_insert
|
|
140
|
+
AFTER INSERT ON person_note_content_revisions FOR EACH ROW
|
|
141
|
+
BEGIN
|
|
142
|
+
INSERT INTO person_notes_effective_fts(person_notes_effective_fts, rowid, title, body)
|
|
143
|
+
SELECT 'delete', NEW.note_id, coalesce(note.title, ''), note.body
|
|
144
|
+
FROM person_notes note WHERE note.id = NEW.note_id AND NEW.expected_revision = 0;
|
|
145
|
+
INSERT INTO person_notes_effective_fts(person_notes_effective_fts, rowid, title, body)
|
|
146
|
+
SELECT 'delete', NEW.note_id, coalesce(prior.title, ''), prior.body
|
|
147
|
+
FROM person_note_content_revisions prior
|
|
148
|
+
WHERE prior.note_id = NEW.note_id AND prior.revision = NEW.expected_revision;
|
|
149
|
+
INSERT INTO person_notes_effective_fts(rowid, title, body)
|
|
150
|
+
VALUES (NEW.note_id, coalesce(NEW.title, ''), NEW.body);
|
|
151
|
+
END;
|