Mercurial > hg > orthanc-databases
comparison PostgreSQL/Plugins/SQL/PrepareIndex.sql @ 569:f18e46d7dbf8 attach-custom-data
merged find-refactoring -> attach-custom-data
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Tue, 24 Sep 2024 15:04:21 +0200 |
parents | 11c6bcc9d1f2 |
children |
comparison
equal
deleted
inserted
replaced
368:82f73188b58d | 569:f18e46d7dbf8 |
---|---|
1 -- This SQL file creates a DB in Rev2 directly | |
2 -- It is also run after upgrade scripts to create new tables and or create/replace triggers and functions. | |
3 -- This script is self contained, it contains everything that needs to be run to create an Orthanc DB. | |
4 -- Note to developers: | |
5 -- - it is and must stay idempotent. | |
6 -- - it is executed when the DB is "locked", only one Orthanc instance can execute it at a given time. | |
7 | |
8 CREATE TABLE IF NOT EXISTS GlobalProperties( | |
9 property INTEGER PRIMARY KEY, | |
10 value TEXT | |
11 ); | |
12 | |
13 CREATE TABLE IF NOT EXISTS Resources( | |
14 internalId BIGSERIAL NOT NULL PRIMARY KEY, | |
15 resourceType INTEGER NOT NULL, | |
16 publicId VARCHAR(64) NOT NULL, | |
17 parentId BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, | |
18 CONSTRAINT UniquePublicId UNIQUE (publicId) | |
19 ); | |
20 | |
21 CREATE TABLE IF NOT EXISTS MainDicomTags( | |
22 id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, | |
23 tagGroup INTEGER, | |
24 tagElement INTEGER, | |
25 value TEXT, | |
26 PRIMARY KEY(id, tagGroup, tagElement) | |
27 ); | |
28 | |
29 CREATE TABLE IF NOT EXISTS DicomIdentifiers( | |
30 id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, | |
31 tagGroup INTEGER, | |
32 tagElement INTEGER, | |
33 value TEXT, | |
34 PRIMARY KEY(id, tagGroup, tagElement) | |
35 ); | |
36 | |
37 CREATE TABLE IF NOT EXISTS Metadata( | |
38 id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, | |
39 type INTEGER NOT NULL, | |
40 value TEXT, | |
41 revision INTEGER, | |
42 PRIMARY KEY(id, type) | |
43 ); | |
44 | |
45 CREATE TABLE IF NOT EXISTS AttachedFiles( | |
46 id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, | |
47 fileType INTEGER, | |
48 uuid VARCHAR(64) NOT NULL, | |
49 compressedSize BIGINT, | |
50 uncompressedSize BIGINT, | |
51 compressionType INTEGER, | |
52 uncompressedHash VARCHAR(40), | |
53 compressedHash VARCHAR(40), | |
54 revision INTEGER, | |
55 customData TEXT, -- new in schema rev 3 | |
56 PRIMARY KEY(id, fileType) | |
57 ); | |
58 | |
59 CREATE TABLE IF NOT EXISTS Changes( | |
60 seq BIGSERIAL NOT NULL PRIMARY KEY, | |
61 changeType INTEGER, | |
62 internalId BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, | |
63 resourceType INTEGER, | |
64 date VARCHAR(64) | |
65 ); | |
66 | |
67 CREATE TABLE IF NOT EXISTS ExportedResources( | |
68 seq BIGSERIAL NOT NULL PRIMARY KEY, | |
69 resourceType INTEGER, | |
70 publicId VARCHAR(64), | |
71 remoteModality TEXT, | |
72 patientId VARCHAR(64), | |
73 studyInstanceUid TEXT, | |
74 seriesInstanceUid TEXT, | |
75 sopInstanceUid TEXT, | |
76 date VARCHAR(64) | |
77 ); | |
78 | |
79 CREATE TABLE IF NOT EXISTS PatientRecyclingOrder( | |
80 seq BIGSERIAL NOT NULL PRIMARY KEY, | |
81 patientId BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, | |
82 CONSTRAINT UniquePatientId UNIQUE (patientId) | |
83 ); | |
84 | |
85 CREATE TABLE IF NOT EXISTS Labels( | |
86 id BIGINT REFERENCES Resources(internalId) ON DELETE CASCADE, | |
87 label TEXT, | |
88 PRIMARY KEY(id, label) | |
89 ); | |
90 | |
91 CREATE TABLE IF NOT EXISTS GlobalIntegers( | |
92 key INTEGER PRIMARY KEY, | |
93 value BIGINT); | |
94 -- GlobalIntegers keys: | |
95 -- 0: CompressedSize | |
96 -- 1: UncompressedSize | |
97 -- 2: PatientsCount | |
98 -- 3: StudiesCount | |
99 -- 4: SeriesCount | |
100 -- 5: InstancesCount | |
101 -- 6: ChangeSeq | |
102 -- 7: PatientRecyclingOrderSeq | |
103 | |
104 CREATE TABLE IF NOT EXISTS ServerProperties( | |
105 server VARCHAR(64) NOT NULL, | |
106 property INTEGER, value TEXT, | |
107 PRIMARY KEY(server, property) | |
108 ); | |
109 | |
110 CREATE INDEX IF NOT EXISTS ChildrenIndex ON Resources(parentId); | |
111 CREATE INDEX IF NOT EXISTS PublicIndex ON Resources(publicId); | |
112 CREATE INDEX IF NOT EXISTS ResourceTypeIndex ON Resources(resourceType); | |
113 CREATE INDEX IF NOT EXISTS PatientRecyclingIndex ON PatientRecyclingOrder(patientId); | |
114 | |
115 CREATE INDEX IF NOT EXISTS MainDicomTagsIndex ON MainDicomTags(id); | |
116 CREATE INDEX IF NOT EXISTS DicomIdentifiersIndex1 ON DicomIdentifiers(id); | |
117 CREATE INDEX IF NOT EXISTS DicomIdentifiersIndex2 ON DicomIdentifiers(tagGroup, tagElement); | |
118 CREATE INDEX IF NOT EXISTS DicomIdentifiersIndexValues ON DicomIdentifiers(value); | |
119 | |
120 CREATE INDEX IF NOT EXISTS ChangesIndex ON Changes(internalId); | |
121 CREATE INDEX IF NOT EXISTS LabelsIndex1 ON LABELS(id); | |
122 CREATE INDEX IF NOT EXISTS LabelsIndex2 ON LABELS(label); | |
123 | |
124 ------------------- Trigram index creation ------------------- | |
125 | |
126 | |
127 -- Apply fix for performance issue (speed up wildcard search by using GIN trigrams). This implements the patch suggested | |
128 -- in issue #47, BUT we also keep the original "DicomIdentifiersIndexValues", as it leads to better | |
129 -- performance for "strict" searches (i.e. searches involving no wildcard). | |
130 -- https://www.postgresql.org/docs/current/static/pgtrgm.html | |
131 -- https://orthanc.uclouvain.be/bugs/show_bug.cgi?id=47 | |
132 | |
133 DO $body$ | |
134 begin | |
135 IF EXISTS (SELECT 1 FROM pg_available_extensions WHERE name='pg_trgm') THEN | |
136 CREATE EXTENSION IF NOT EXISTS pg_trgm; | |
137 CREATE INDEX IF NOT EXISTS DicomIdentifiersIndexValues2 ON DicomIdentifiers USING gin(value gin_trgm_ops); | |
138 ELSE | |
139 RAISE NOTICE 'pg_trgm extension is not available on you system'; | |
140 END IF; | |
141 END $body$; | |
142 | |
143 | |
144 ------------------- PatientAdded trigger & PatientRecyclingOrder ------------------- | |
145 DROP TRIGGER IF EXISTS PatientAdded ON Resources; | |
146 | |
147 CREATE OR REPLACE FUNCTION PatientAddedOrUpdated( | |
148 IN patient_id BIGINT, | |
149 IN is_update BIGINT | |
150 ) | |
151 RETURNS VOID AS $body$ | |
152 BEGIN | |
153 DECLARE | |
154 newSeq BIGINT; | |
155 BEGIN | |
156 UPDATE GlobalIntegers SET value = value + 1 WHERE key = 7 RETURNING value INTO newSeq; | |
157 IF is_update > 0 THEN | |
158 -- Note: Protected patients are not listed in this table ! So, they won't be updated | |
159 UPDATE PatientRecyclingOrder SET seq = newSeq WHERE PatientRecyclingOrder.patientId = patient_id; | |
160 ELSE | |
161 INSERT INTO PatientRecyclingOrder VALUES (newSeq, patient_id); | |
162 END IF; | |
163 END; | |
164 END; | |
165 $body$ LANGUAGE plpgsql; | |
166 | |
167 CREATE OR REPLACE FUNCTION PatientAddedFunc() | |
168 RETURNS TRIGGER AS $body$ | |
169 BEGIN | |
170 -- The "0" corresponds to "OrthancPluginResourceType_Patient" | |
171 IF new.resourceType = 0 THEN | |
172 PERFORM PatientAddedOrUpdated(new.internalId, 0); | |
173 END IF; | |
174 RETURN NULL; | |
175 END; | |
176 $body$ LANGUAGE plpgsql; | |
177 | |
178 CREATE TRIGGER PatientAdded | |
179 AFTER INSERT ON Resources | |
180 FOR EACH ROW | |
181 EXECUTE PROCEDURE PatientAddedFunc(); | |
182 | |
183 -- initial population of PatientRecyclingOrderSeq | |
184 INSERT INTO GlobalIntegers | |
185 SELECT 7, CAST(COALESCE(MAX(seq), 0) AS BIGINT) FROM PatientRecyclingOrder | |
186 ON CONFLICT DO NOTHING; | |
187 | |
188 | |
189 ------------------- ResourceDeleted trigger ------------------- | |
190 DROP TRIGGER IF EXISTS ResourceDeleted ON Resources; | |
191 | |
192 -- The following trigger combines 2 triggers from SQLite: | |
193 -- ResourceDeleted + ResourceDeletedParentCleaning | |
194 CREATE OR REPLACE FUNCTION ResourceDeletedFunc() | |
195 RETURNS TRIGGER AS $body$ | |
196 BEGIN | |
197 -- RAISE NOTICE 'ResourceDeletedFunc %', old.publicId; | |
198 INSERT INTO DeletedResources VALUES (old.resourceType, old.publicId); | |
199 | |
200 -- If this resource is the latest child, delete the parent | |
201 DELETE FROM Resources WHERE internalId = old.parentId | |
202 AND NOT EXISTS (SELECT 1 FROM Resources WHERE parentId = old.parentId); | |
203 RETURN NULL; | |
204 END; | |
205 $body$ LANGUAGE plpgsql; | |
206 | |
207 CREATE TRIGGER ResourceDeleted | |
208 AFTER DELETE ON Resources | |
209 FOR EACH ROW | |
210 EXECUTE PROCEDURE ResourceDeletedFunc(); | |
211 | |
212 | |
213 ------------------- DeleteResource function ------------------- | |
214 | |
215 CREATE OR REPLACE FUNCTION DeleteResource( | |
216 IN id BIGINT, | |
217 OUT remaining_ancestor_resource_type INTEGER, | |
218 OUT remaining_anncestor_public_id TEXT) AS $body$ | |
219 | |
220 DECLARE | |
221 deleted_row RECORD; | |
222 locked_row RECORD; | |
223 | |
224 BEGIN | |
225 | |
226 SET client_min_messages = warning; -- suppress NOTICE: relation "deletedresources" already exists, skipping | |
227 | |
228 -- note: temporary tables are created at session (connection) level -> they are likely to exist | |
229 -- these tables are used by the triggers | |
230 CREATE TEMPORARY TABLE IF NOT EXISTS DeletedResources( | |
231 resourceType INTEGER NOT NULL, | |
232 publicId VARCHAR(64) NOT NULL | |
233 ); | |
234 | |
235 RESET client_min_messages; | |
236 | |
237 -- clear the temporary table in case it has been created earlier in the session | |
238 DELETE FROM DeletedResources; | |
239 | |
240 -- create/clear the DeletedFiles temporary table | |
241 PERFORM CreateDeletedFilesTemporaryTable(); | |
242 | |
243 -- Before deleting an object, we need to lock its parent until the end of the transaction to avoid that | |
244 -- 2 threads deletes the last 2 instances of a series at the same time -> none of them would realize | |
245 -- that they are deleting the last instance and the parent resources would not be deleted. | |
246 -- Locking only the immediate parent is sufficient to prevent from this. | |
247 SELECT * INTO locked_row FROM resources WHERE internalid = (SELECT parentid FROM resources WHERE internalid = id) FOR UPDATE; | |
248 | |
249 -- delete the resource itself | |
250 DELETE FROM Resources WHERE internalId=id RETURNING * INTO deleted_row; | |
251 -- note: there is a ResourceDeletedFunc trigger that will execute here and delete the parents if there are no remaining children + | |
252 | |
253 -- If this resource still has siblings, keep track of the remaining parent | |
254 -- (a parent that must not be deleted but whose LastUpdate must be updated) | |
255 SELECT resourceType, publicId INTO remaining_ancestor_resource_type, remaining_anncestor_public_id | |
256 FROM Resources | |
257 WHERE internalId = deleted_row.parentId | |
258 AND EXISTS (SELECT 1 FROM Resources WHERE parentId = deleted_row.parentId); | |
259 | |
260 END; | |
261 | |
262 $body$ LANGUAGE plpgsql; | |
263 | |
264 CREATE OR REPLACE FUNCTION CreateDeletedFilesTemporaryTable( | |
265 ) RETURNS VOID AS $body$ | |
266 | |
267 BEGIN | |
268 | |
269 SET client_min_messages = warning; -- suppress NOTICE: relation "deletedresources" already exists, skipping | |
270 | |
271 -- note: temporary tables are created at session (connection) level -> they are likely to exist | |
272 CREATE TEMPORARY TABLE IF NOT EXISTS DeletedFiles( | |
273 uuid VARCHAR(64) NOT NULL, | |
274 fileType INTEGER, | |
275 compressedSize BIGINT, | |
276 uncompressedSize BIGINT, | |
277 compressionType INTEGER, | |
278 uncompressedHash VARCHAR(40), | |
279 compressedHash VARCHAR(40), | |
280 revision INTEGER, | |
281 customData TEXT | |
282 ); | |
283 | |
284 RESET client_min_messages; | |
285 | |
286 -- clear the temporary table in case it has been created earlier in the session | |
287 DELETE FROM DeletedFiles; | |
288 END; | |
289 | |
290 $body$ LANGUAGE plpgsql; | |
291 | |
292 | |
293 CREATE OR REPLACE FUNCTION AttachedFileDeletedFunc() | |
294 RETURNS TRIGGER AS $body$ | |
295 BEGIN | |
296 INSERT INTO DeletedFiles VALUES | |
297 (old.uuid, old.filetype, old.compressedSize, | |
298 old.uncompressedSize, old.compressionType, | |
299 old.uncompressedHash, old.compressedHash, | |
300 old.revision, old.customData); | |
301 RETURN NULL; | |
302 END; | |
303 $body$ LANGUAGE plpgsql; | |
304 | |
305 DROP TRIGGER IF EXISTS AttachedFileDeleted on AttachedFiles; | |
306 CREATE TRIGGER AttachedFileDeleted | |
307 AFTER DELETE ON AttachedFiles | |
308 FOR EACH ROW | |
309 EXECUTE PROCEDURE AttachedFileDeletedFunc(); | |
310 | |
311 | |
312 ------------------- Fast Statistics ------------------- | |
313 | |
314 -- initial population of GlobalIntegers if not already there | |
315 INSERT INTO GlobalIntegers | |
316 SELECT 0, CAST(COALESCE(SUM(compressedSize), 0) AS BIGINT) FROM AttachedFiles | |
317 ON CONFLICT DO NOTHING; | |
318 | |
319 INSERT INTO GlobalIntegers | |
320 SELECT 1, CAST(COALESCE(SUM(uncompressedSize), 0) AS BIGINT) FROM AttachedFiles | |
321 ON CONFLICT DO NOTHING; | |
322 | |
323 INSERT INTO GlobalIntegers | |
324 SELECT 2, CAST(COALESCE(COUNT(*), 0) AS BIGINT) FROM Resources WHERE resourceType = 0 -- Count patients | |
325 ON CONFLICT DO NOTHING; | |
326 | |
327 INSERT INTO GlobalIntegers | |
328 SELECT 3, CAST(COALESCE(COUNT(*), 0) AS BIGINT) FROM Resources WHERE resourceType = 1 -- Count studies | |
329 ON CONFLICT DO NOTHING; | |
330 | |
331 INSERT INTO GlobalIntegers | |
332 SELECT 4, CAST(COALESCE(COUNT(*), 0) AS BIGINT) FROM Resources WHERE resourceType = 2 -- Count series | |
333 ON CONFLICT DO NOTHING; | |
334 | |
335 INSERT INTO GlobalIntegers | |
336 SELECT 5, CAST(COALESCE(COUNT(*), 0) AS BIGINT) FROM Resources WHERE resourceType = 3 -- Count instances | |
337 ON CONFLICT DO NOTHING; | |
338 | |
339 | |
340 -- this table stores all changes that needs to be performed to the GlobalIntegers table | |
341 -- This way, each transaction can add row independently in this table without having to lock | |
342 -- any row (which was the case with previous FastTotalSize). | |
343 -- These changes will be applied at regular interval by an external thread or when someone | |
344 -- requests the statistics | |
345 CREATE TABLE IF NOT EXISTS GlobalIntegersChanges( | |
346 key INTEGER, | |
347 value BIGINT); | |
348 | |
349 CREATE OR REPLACE FUNCTION UpdateSingleStatistic( | |
350 IN statistics_key INTEGER, | |
351 OUT new_value BIGINT | |
352 ) AS $body$ | |
353 BEGIN | |
354 | |
355 -- Delete the current changes, sum them and update the GlobalIntegers row. | |
356 -- New rows can be added in the meantime, they won't be deleted or summed. | |
357 WITH deleted_rows AS ( | |
358 DELETE FROM GlobalIntegersChanges | |
359 WHERE GlobalIntegersChanges.key = statistics_key | |
360 RETURNING value | |
361 ) | |
362 UPDATE GlobalIntegers | |
363 SET value = value + ( | |
364 SELECT COALESCE(SUM(value), 0) | |
365 FROM deleted_rows | |
366 ) | |
367 WHERE GlobalIntegers.key = statistics_key | |
368 RETURNING value INTO new_value; | |
369 | |
370 END; | |
371 $body$ LANGUAGE plpgsql; | |
372 | |
373 | |
374 CREATE OR REPLACE FUNCTION UpdateStatistics( | |
375 OUT patients_cunt BIGINT, | |
376 OUT studies_count BIGINT, | |
377 OUT series_count BIGINT, | |
378 OUT instances_count BIGINT, | |
379 OUT total_compressed_size BIGINT, | |
380 OUT total_uncompressed_size BIGINT | |
381 ) AS $body$ | |
382 BEGIN | |
383 | |
384 SELECT UpdateSingleStatistic(0) INTO total_compressed_size; | |
385 SELECT UpdateSingleStatistic(1) INTO total_uncompressed_size; | |
386 SELECT UpdateSingleStatistic(2) INTO patients_cunt; | |
387 SELECT UpdateSingleStatistic(3) INTO studies_count; | |
388 SELECT UpdateSingleStatistic(4) INTO series_count; | |
389 SELECT UpdateSingleStatistic(5) INTO instances_count; | |
390 | |
391 END; | |
392 $body$ LANGUAGE plpgsql; | |
393 | |
394 | |
395 CREATE OR REPLACE FUNCTION IncrementResourcesTrackerFunc() | |
396 RETURNS TRIGGER AS $$ | |
397 BEGIN | |
398 INSERT INTO GlobalIntegersChanges VALUES(new.resourceType + 2, 1); | |
399 RETURN NULL; | |
400 END; | |
401 $$ LANGUAGE plpgsql; | |
402 | |
403 CREATE OR REPLACE FUNCTION DecrementResourcesTrackerFunc() | |
404 RETURNS TRIGGER AS $$ | |
405 BEGIN | |
406 INSERT INTO GlobalIntegersChanges VALUES(old.resourceType + 2, -1); | |
407 RETURN NULL; | |
408 END; | |
409 $$ LANGUAGE plpgsql; | |
410 | |
411 | |
412 CREATE OR REPLACE FUNCTION AttachedFileIncrementSizeFunc() | |
413 RETURNS TRIGGER AS $body$ | |
414 BEGIN | |
415 INSERT INTO GlobalIntegersChanges VALUES(0, new.compressedSize); | |
416 INSERT INTO GlobalIntegersChanges VALUES(1, new.uncompressedSize); | |
417 RETURN NULL; | |
418 END; | |
419 $body$ LANGUAGE plpgsql; | |
420 | |
421 CREATE OR REPLACE FUNCTION AttachedFileDecrementSizeFunc() | |
422 RETURNS TRIGGER AS $body$ | |
423 BEGIN | |
424 INSERT INTO GlobalIntegersChanges VALUES(0, -old.compressedSize); | |
425 INSERT INTO GlobalIntegersChanges VALUES(1, -old.uncompressedSize); | |
426 RETURN NULL; | |
427 END; | |
428 $body$ LANGUAGE plpgsql; | |
429 | |
430 DROP TRIGGER IF EXISTS AttachedFileIncrementSize on AttachedFiles; | |
431 CREATE TRIGGER AttachedFileIncrementSize | |
432 AFTER INSERT ON AttachedFiles | |
433 FOR EACH ROW | |
434 EXECUTE PROCEDURE AttachedFileIncrementSizeFunc(); | |
435 | |
436 DROP TRIGGER IF EXISTS AttachedFileDecrementSize on AttachedFiles; | |
437 CREATE TRIGGER AttachedFileDecrementSize | |
438 AFTER DELETE ON AttachedFiles | |
439 FOR EACH ROW | |
440 EXECUTE PROCEDURE AttachedFileDecrementSizeFunc(); | |
441 | |
442 DROP TRIGGER IF EXISTS IncrementResourcesTracker on Resources; | |
443 CREATE TRIGGER IncrementResourcesTracker | |
444 AFTER INSERT ON Resources | |
445 FOR EACH ROW | |
446 EXECUTE PROCEDURE IncrementResourcesTrackerFunc(); | |
447 | |
448 DROP TRIGGER IF EXISTS DecrementResourcesTracker on Resources; | |
449 CREATE TRIGGER DecrementResourcesTracker | |
450 AFTER DELETE ON Resources | |
451 FOR EACH ROW | |
452 EXECUTE PROCEDURE DecrementResourcesTrackerFunc(); | |
453 | |
454 | |
455 ------------------- InsertOrUpdateMetadata function ------------------- | |
456 CREATE OR REPLACE FUNCTION InsertOrUpdateMetadata(resource_ids BIGINT[], | |
457 metadata_types INTEGER[], | |
458 metadata_values TEXT[], | |
459 revisions INTEGER[]) | |
460 RETURNS VOID AS $body$ | |
461 BEGIN | |
462 FOR i IN 1 .. ARRAY_LENGTH(resource_ids, 1) LOOP | |
463 -- RAISE NOTICE 'Parameter %: % % %', i, resource_ids[i], metadata_types[i], metadata_values[i]; | |
464 INSERT INTO Metadata VALUES(resource_ids[i], metadata_types[i], metadata_values[i], revisions[i]) | |
465 ON CONFLICT (id, type) DO UPDATE SET value = EXCLUDED.value, revision = EXCLUDED.revision; | |
466 END LOOP; | |
467 | |
468 END; | |
469 $body$ LANGUAGE plpgsql; | |
470 | |
471 | |
472 ------------------- GetLastChange function ------------------- | |
473 DROP TRIGGER IF EXISTS InsertedChange ON Changes; | |
474 | |
475 -- insert the value if not already there | |
476 INSERT INTO GlobalIntegers | |
477 SELECT 6, CAST(COALESCE(MAX(seq), 0) AS BIGINT) FROM Changes | |
478 ON CONFLICT DO NOTHING; | |
479 | |
480 CREATE OR REPLACE FUNCTION InsertedChangeFunc() | |
481 RETURNS TRIGGER AS $body$ | |
482 BEGIN | |
483 UPDATE GlobalIntegers SET value = new.seq WHERE key = 6; | |
484 RETURN NULL; | |
485 END; | |
486 $body$ LANGUAGE plpgsql; | |
487 | |
488 CREATE TRIGGER InsertedChange | |
489 AFTER INSERT ON Changes | |
490 FOR EACH ROW | |
491 EXECUTE PROCEDURE InsertedChangeFunc(); | |
492 | |
493 | |
494 ------------------- CreateInstance function ------------------- | |
495 CREATE OR REPLACE FUNCTION CreateInstance( | |
496 IN patient_public_id TEXT, | |
497 IN study_public_id TEXT, | |
498 IN series_public_id TEXT, | |
499 IN instance_public_id TEXT, | |
500 OUT is_new_patient BIGINT, | |
501 OUT is_new_study BIGINT, | |
502 OUT is_new_series BIGINT, | |
503 OUT is_new_instance BIGINT, | |
504 OUT patient_internal_id BIGINT, | |
505 OUT study_internal_id BIGINT, | |
506 OUT series_internal_id BIGINT, | |
507 OUT instance_internal_id BIGINT) AS $body$ | |
508 | |
509 BEGIN | |
510 is_new_patient := 1; | |
511 is_new_study := 1; | |
512 is_new_series := 1; | |
513 is_new_instance := 1; | |
514 | |
515 BEGIN | |
516 INSERT INTO "resources" VALUES (DEFAULT, 0, patient_public_id, NULL) RETURNING internalid INTO patient_internal_id; | |
517 EXCEPTION | |
518 WHEN unique_violation THEN | |
519 is_new_patient := 0; | |
520 SELECT internalid INTO patient_internal_id FROM "resources" WHERE publicId = patient_public_id FOR UPDATE; -- also locks the resource and its parent to prevent from deletion while we complete this transaction | |
521 END; | |
522 | |
523 BEGIN | |
524 INSERT INTO "resources" VALUES (DEFAULT, 1, study_public_id, patient_internal_id) RETURNING internalid INTO study_internal_id; | |
525 EXCEPTION | |
526 WHEN unique_violation THEN | |
527 is_new_study := 0; | |
528 SELECT internalid INTO study_internal_id FROM "resources" WHERE publicId = study_public_id FOR UPDATE; -- also locks the resource and its parent to prevent from deletion while we complete this transaction END; | |
529 END; | |
530 | |
531 BEGIN | |
532 INSERT INTO "resources" VALUES (DEFAULT, 2, series_public_id, study_internal_id) RETURNING internalid INTO series_internal_id; | |
533 EXCEPTION | |
534 WHEN unique_violation THEN | |
535 is_new_series := 0; | |
536 SELECT internalid INTO series_internal_id FROM "resources" WHERE publicId = series_public_id FOR UPDATE; -- also locks the resource and its parent to prevent from deletion while we complete this transaction END; | |
537 END; | |
538 | |
539 BEGIN | |
540 INSERT INTO "resources" VALUES (DEFAULT, 3, instance_public_id, series_internal_id) RETURNING internalid INTO instance_internal_id; | |
541 EXCEPTION | |
542 WHEN unique_violation THEN | |
543 is_new_instance := 0; | |
544 SELECT internalid INTO instance_internal_id FROM "resources" WHERE publicId = instance_public_id FOR UPDATE; -- also locks the resource and its parent to prevent from deletion while we complete this transaction | |
545 END; | |
546 | |
547 IF is_new_instance > 0 THEN | |
548 -- Move the patient to the end of the recycling order. | |
549 PERFORM PatientAddedOrUpdated(patient_internal_id, 1); | |
550 END IF; | |
551 END; | |
552 | |
553 $body$ LANGUAGE plpgsql; | |
554 | |
555 | |
556 | |
557 -- set the global properties that actually documents the DB version, revision and some of the capabilities | |
558 DELETE FROM GlobalProperties WHERE property IN (1, 4, 6, 10, 11, 12, 13); | |
559 INSERT INTO GlobalProperties VALUES (1, 6); -- GlobalProperty_DatabaseSchemaVersion | |
560 INSERT INTO GlobalProperties VALUES (4, 3); -- GlobalProperty_DatabasePatchLevel | |
561 INSERT INTO GlobalProperties VALUES (6, 1); -- GlobalProperty_GetTotalSizeIsFast | |
562 INSERT INTO GlobalProperties VALUES (10, 1); -- GlobalProperty_HasTrigramIndex | |
563 INSERT INTO GlobalProperties VALUES (11, 3); -- GlobalProperty_HasCreateInstance -- this is actually the 3rd version of HasCreateInstance | |
564 INSERT INTO GlobalProperties VALUES (12, 1); -- GlobalProperty_HasFastCountResources | |
565 INSERT INTO GlobalProperties VALUES (13, 1); -- GlobalProperty_GetLastChangeIndex |