@nsshunt/stsdatamanagement 1.18.196 → 1.18.199

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.
@@ -0,0 +1,2176 @@
1
+ -- =============================================================================
2
+ -- stsrest01 - Generic Resource / Entity Database Model
3
+ -- =============================================================================
4
+ --
5
+ -- PURPOSE
6
+ -- -------
7
+ -- This schema implements a deliberately generic, versioned resource/entity
8
+ -- model.
9
+ --
10
+ -- A RESOURCE is a logical/conceptual container identified by resname.
11
+ -- A RESOURCE version is represented by a new stsresource row and therefore a
12
+ -- new stsresource.oid.
13
+ --
14
+ -- An ENTITY belongs to the RESOURCE CONCEPT, not permanently to one historical
15
+ -- resource row. For that reason, whenever the current resource version changes,
16
+ -- all entity history is moved to the new current resource OID.
17
+ --
18
+ -- This is intentional and is important to the design:
19
+ --
20
+ -- resource v1 (oid 10)
21
+ -- entity history -> resourceoid 10
22
+ --
23
+ -- resource updated to v2 (oid 11)
24
+ -- entity history -> resourceoid 11
25
+ --
26
+ -- resource deleted as v3 (oid 12)
27
+ -- entity history -> resourceoid 12
28
+ --
29
+ -- resource recreated / reinstated as v4 (oid 13)
30
+ -- entity history -> resourceoid 13
31
+ --
32
+ -- A delete therefore does NOT terminate the logical resource concept.
33
+ -- Recreating a deleted resource continues the resource version sequence and
34
+ -- restores the same concept, including its entity history.
35
+ --
36
+ -- dbaction values
37
+ -- ---------------
38
+ -- 1 = create / recreate
39
+ -- 2 = update
40
+ -- 3 = logical delete
41
+ --
42
+ -- VERSIONING RULES
43
+ -- ----------------
44
+ -- * Historical rows have validto IS NOT NULL.
45
+ -- * Exactly one row per resource concept may have validto IS NULL.
46
+ -- * A deleted resource is still the current resource version; it has
47
+ -- validto IS NULL and dbaction = 3.
48
+ -- * Exactly one row per entity concept may have validto IS NULL.
49
+ -- * A deleted entity is likewise represented by a current tombstone row with
50
+ -- dbaction = 3.
51
+ -- * Recreating a deleted resource/entity closes the tombstone and creates the
52
+ -- next version. Version numbers never reset to 1.
53
+ --
54
+ -- CONCURRENCY RULES
55
+ -- -----------------
56
+ -- Mutating functions lock the current resource/entity rows with FOR UPDATE
57
+ -- before changing versions. Partial unique indexes provide a final database
58
+ -- invariant for concurrent creates where there is no existing row to lock.
59
+ --
60
+ -- JSON
61
+ -- ----
62
+ -- resdesc and entvalue retain the supplied JSON text.
63
+ -- resdesc_jsonb and entvalue_jsonb retain the parsed JSON representation for
64
+ -- indexing/searching without making the model resource-type-specific.
65
+ --
66
+ -- =============================================================================
67
+
68
+
69
+ -- =============================================================================
70
+ -- Sequences
71
+ -- =============================================================================
72
+
73
+ -- Generates immutable surrogate OIDs for stsresource rows.
74
+ --
75
+ -- An OID identifies one database row/version; it is NOT the permanent logical
76
+ -- identity of a resource. resname is the logical resource concept identifier.
77
+ CREATE SEQUENCE public.stsresource_oid_seq
78
+ AS bigint
79
+ INCREMENT 1
80
+ START 1
81
+ MINVALUE 1
82
+ CACHE 1;
83
+
84
+ ALTER SEQUENCE public.stsresource_oid_seq
85
+ OWNER TO postgres;
86
+
87
+
88
+ -- Generates immutable surrogate OIDs for stsentity rows.
89
+ --
90
+ -- An entity OID identifies one entity version row.
91
+ CREATE SEQUENCE public.stsentity_oid_seq
92
+ AS bigint
93
+ INCREMENT 1
94
+ START 1
95
+ MINVALUE 1
96
+ CACHE 1;
97
+
98
+ ALTER SEQUENCE public.stsentity_oid_seq
99
+ OWNER TO postgres;
100
+
101
+
102
+ -- =============================================================================
103
+ -- stsresource
104
+ -- =============================================================================
105
+
106
+ -- Stores the complete version history for every generic resource concept.
107
+ --
108
+ -- resname
109
+ -- Logical identifier of the resource concept.
110
+ --
111
+ -- resdesc
112
+ -- Original JSON text supplied by the caller.
113
+ --
114
+ -- resdesc_jsonb
115
+ -- Parsed JSON representation of resdesc. This permits generic JSONB
116
+ -- searching/indexing while preserving the original text.
117
+ --
118
+ -- vnum
119
+ -- Monotonically increasing version number within a resname.
120
+ --
121
+ -- validfrom / validto
122
+ -- Temporal validity interval for this version. The current version has
123
+ -- validto IS NULL.
124
+ --
125
+ -- dbaction
126
+ -- 1=create/recreate, 2=update, 3=logical delete.
127
+ --
128
+ -- dbactionuser
129
+ -- Application/user identity responsible for the database change.
130
+ CREATE TABLE public.stsresource
131
+ (
132
+ oid bigint NOT NULL
133
+ DEFAULT nextval('public.stsresource_oid_seq'::regclass),
134
+
135
+ resname character varying(128)
136
+ COLLATE pg_catalog."default"
137
+ NOT NULL,
138
+
139
+ resdesc character varying(32768)
140
+ COLLATE pg_catalog."default",
141
+
142
+ resdesc_jsonb jsonb,
143
+
144
+ vnum bigint NOT NULL,
145
+
146
+ validfrom timestamp with time zone NOT NULL,
147
+ validto timestamp with time zone,
148
+
149
+ dbaction smallint NOT NULL,
150
+
151
+ dbactionuser character varying(64)
152
+ COLLATE pg_catalog."default"
153
+ NOT NULL,
154
+
155
+ CONSTRAINT stsresource_pkey
156
+ PRIMARY KEY (oid),
157
+
158
+ CONSTRAINT stsresource_dbaction_check
159
+ CHECK (dbaction IN (1, 2, 3))
160
+ )
161
+ TABLESPACE pg_default;
162
+
163
+ ALTER TABLE public.stsresource
164
+ OWNER TO postgres;
165
+
166
+ -- Make the sequence lifecycle follow its dedicated table column.
167
+ ALTER SEQUENCE public.stsresource_oid_seq
168
+ OWNED BY public.stsresource.oid;
169
+
170
+
171
+ -- -----------------------------------------------------------------------------
172
+ -- stsresource indexes
173
+ -- -----------------------------------------------------------------------------
174
+
175
+ -- A resource concept may have only one row for each version number.
176
+ CREATE UNIQUE INDEX stsresource_resname_vnum
177
+ ON public.stsresource USING btree
178
+ (
179
+ resname COLLATE pg_catalog."default" ASC NULLS LAST,
180
+ vnum ASC NULLS LAST
181
+ )
182
+ TABLESPACE pg_default;
183
+
184
+
185
+ -- Database invariant: exactly zero or one CURRENT row may exist for resname.
186
+ --
187
+ -- IMPORTANT:
188
+ -- Do NOT filter dbaction = 3 here.
189
+ --
190
+ -- A logically deleted resource is still the current resource version until it
191
+ -- is recreated. This index prevents a deleted tombstone and an active row from
192
+ -- both being current at the same time.
193
+ CREATE UNIQUE INDEX idx_stsresource_resname_one_current
194
+ ON public.stsresource (resname)
195
+ WHERE validto IS NULL;
196
+
197
+
198
+ -- Supports general resource history/current-version lookup.
199
+ --
200
+ -- This index intentionally includes deleted current records.
201
+ CREATE INDEX stsresource_resname_validto
202
+ ON public.stsresource USING btree
203
+ (
204
+ resname COLLATE pg_catalog."default" ASC NULLS LAST,
205
+ validto DESC NULLS LAST
206
+ )
207
+ TABLESPACE pg_default;
208
+
209
+
210
+ -- Supports normal application reads and keyset/cursor pagination over active
211
+ -- resources only.
212
+ --
213
+ -- oid is included after resname to provide a deterministic ordered key.
214
+ CREATE INDEX idx_stsresource_resname_current
215
+ ON public.stsresource (resname, oid)
216
+ WHERE validto IS NULL
217
+ AND dbaction != 3;
218
+
219
+
220
+ -- =============================================================================
221
+ -- stsentity
222
+ -- =============================================================================
223
+
224
+ -- Stores the complete version history for entities belonging to a resource
225
+ -- concept.
226
+ --
227
+ -- resourceoid
228
+ -- Points to the CURRENT stsresource row for the resource concept.
229
+ --
230
+ -- This value intentionally changes whenever the resource gets a new
231
+ -- version. All entity history is migrated to the new resource OID by the
232
+ -- resource update/delete/recreate functions.
233
+ --
234
+ -- entname
235
+ -- Logical identifier of the entity within its resource concept.
236
+ --
237
+ -- entvalue
238
+ -- Original JSON text supplied by the caller.
239
+ --
240
+ -- entvalue_jsonb
241
+ -- Parsed JSON representation of entvalue.
242
+ --
243
+ -- vnum
244
+ -- Monotonically increasing entity version number.
245
+ --
246
+ -- validfrom / validto
247
+ -- Temporal validity interval for this entity version. The current version
248
+ -- has validto IS NULL.
249
+ --
250
+ -- dbaction
251
+ -- 1=create/recreate, 2=update, 3=logical delete.
252
+ CREATE TABLE public.stsentity
253
+ (
254
+ oid bigint NOT NULL
255
+ DEFAULT nextval('public.stsentity_oid_seq'::regclass),
256
+
257
+ resourceoid bigint NOT NULL,
258
+
259
+ entname character varying(128)
260
+ COLLATE pg_catalog."default"
261
+ NOT NULL,
262
+
263
+ entvalue character varying(32768)
264
+ COLLATE pg_catalog."default",
265
+
266
+ entvalue_jsonb jsonb,
267
+
268
+ vnum bigint NOT NULL,
269
+
270
+ validfrom timestamp with time zone NOT NULL,
271
+ validto timestamp with time zone,
272
+
273
+ dbaction smallint NOT NULL,
274
+
275
+ dbactionuser character varying(64)
276
+ COLLATE pg_catalog."default"
277
+ NOT NULL,
278
+
279
+ CONSTRAINT stsentity_pkey
280
+ PRIMARY KEY (oid),
281
+
282
+ CONSTRAINT stsentity_dbaction_check
283
+ CHECK (dbaction IN (1, 2, 3)),
284
+
285
+ -- Physical DELETE is not used by the versioning functions; they perform
286
+ -- logical deletes. ON DELETE CASCADE applies only if a stsresource row is
287
+ -- physically removed, in which case entity rows currently attached to that
288
+ -- resource OID are also physically removed.
289
+ CONSTRAINT fk_stsresource
290
+ FOREIGN KEY (resourceoid)
291
+ REFERENCES public.stsresource (oid)
292
+ MATCH SIMPLE
293
+ ON UPDATE NO ACTION
294
+ ON DELETE CASCADE
295
+ )
296
+ TABLESPACE pg_default;
297
+
298
+ ALTER TABLE public.stsentity
299
+ OWNER TO postgres;
300
+
301
+ -- Make the sequence lifecycle follow its dedicated table column.
302
+ ALTER SEQUENCE public.stsentity_oid_seq
303
+ OWNED BY public.stsentity.oid;
304
+
305
+
306
+ -- -----------------------------------------------------------------------------
307
+ -- stsentity indexes
308
+ -- -----------------------------------------------------------------------------
309
+
310
+ -- Enforces entity version uniqueness within the current resource concept OID.
311
+ --
312
+ -- Because all entity rows are migrated whenever the resource OID changes, the
313
+ -- complete entity version history continues to share the same resourceoid.
314
+ CREATE UNIQUE INDEX stsentity_resourceoid_entname_vnum
315
+ ON public.stsentity USING btree
316
+ (
317
+ resourceoid ASC NULLS LAST,
318
+ entname COLLATE pg_catalog."default" ASC NULLS LAST,
319
+ vnum ASC NULLS LAST
320
+ )
321
+ TABLESPACE pg_default;
322
+
323
+
324
+ -- Supports general entity history/current-version lookup.
325
+ CREATE INDEX stsentity_resourceoid_entname_validto
326
+ ON public.stsentity USING btree
327
+ (
328
+ resourceoid ASC NULLS LAST,
329
+ entname COLLATE pg_catalog."default" ASC NULLS LAST,
330
+ validto DESC NULLS LAST
331
+ )
332
+ TABLESPACE pg_default;
333
+
334
+
335
+ -- Database invariant: exactly zero or one CURRENT entity row may exist for an
336
+ -- entity concept within a resource.
337
+ --
338
+ -- IMPORTANT:
339
+ -- Do NOT filter dbaction = 3.
340
+ --
341
+ -- A logically deleted entity is still the current tombstone and must be closed
342
+ -- before the entity can be recreated.
343
+ CREATE UNIQUE INDEX idx_stsentity_resourceoid_entname_current
344
+ ON public.stsentity (resourceoid, entname)
345
+ WHERE validto IS NULL;
346
+
347
+
348
+ -- =============================================================================
349
+ -- Resource functions
350
+ -- =============================================================================
351
+
352
+
353
+ -- =============================================================================
354
+ -- create_stsresource
355
+ -- =============================================================================
356
+ --
357
+ -- Creates a new resource concept or reinstates a logically deleted resource.
358
+ --
359
+ -- Behaviour:
360
+ --
361
+ -- 1. Parse/validate the supplied JSON.
362
+ -- 2. Look for the current resource row, INCLUDING a deleted tombstone.
363
+ -- 3. If the current row is active, CREATE is rejected as a duplicate.
364
+ -- 4. If the current row is deleted:
365
+ -- * close the deleted version;
366
+ -- * create the next active version;
367
+ -- * move ALL entity history to the new current resource OID.
368
+ -- 5. If no current row exists:
369
+ -- * inspect the historical resource chain;
370
+ -- * continue from MAX/latest vnum rather than resetting to version 1;
371
+ -- * if historical entity rows point at the latest old resource OID, move
372
+ -- them to the new resource OID.
373
+ --
374
+ -- The historical fallback in step 5 also provides safe recovery from an
375
+ -- abnormal database state where history exists but no current row exists.
376
+ CREATE OR REPLACE FUNCTION public.create_stsresource(
377
+ _dbactionuser character varying,
378
+ _resname character varying,
379
+ _resdesc character varying
380
+ )
381
+ RETURNS SETOF public.stsresource
382
+ LANGUAGE plpgsql
383
+ COST 100
384
+ VOLATILE
385
+ ROWS 1
386
+
387
+ AS $BODY$
388
+ DECLARE
389
+ __now timestamptz := now();
390
+
391
+ -- Current resource, when one exists.
392
+ __resoid bigint;
393
+ __vnum bigint;
394
+ __dbaction smallint;
395
+
396
+ -- Last historical resource when no current row exists.
397
+ __historical_oid bigint;
398
+ __historical_vnum bigint;
399
+
400
+ -- Version/OID of the new active row.
401
+ __newvnum bigint;
402
+ __inserted_oid bigint;
403
+
404
+ -- Parsed JSON.
405
+ __resdesc_jsonb jsonb;
406
+ BEGIN
407
+
408
+ -- -------------------------------------------------------------------------
409
+ -- Validate / parse resource JSON
410
+ -- -------------------------------------------------------------------------
411
+
412
+ BEGIN
413
+ __resdesc_jsonb := _resdesc::jsonb;
414
+
415
+ EXCEPTION
416
+ WHEN invalid_text_representation THEN
417
+ RAISE EXCEPTION
418
+ 'Invalid JSON string: %',
419
+ _resdesc
420
+ USING ERRCODE = 'invalid_text_representation';
421
+ END;
422
+
423
+
424
+ -- -------------------------------------------------------------------------
425
+ -- Resolve and lock the current resource, including deleted tombstones.
426
+ --
427
+ -- The lock serializes update/delete/recreate operations for an existing
428
+ -- resource concept.
429
+ -- -------------------------------------------------------------------------
430
+
431
+ BEGIN
432
+ SELECT
433
+ r.oid,
434
+ r.vnum,
435
+ r.dbaction
436
+ INTO STRICT
437
+ __resoid,
438
+ __vnum,
439
+ __dbaction
440
+ FROM public.stsresource r
441
+ WHERE r.resname = _resname
442
+ AND r.validto IS NULL
443
+ FOR UPDATE;
444
+
445
+ EXCEPTION
446
+
447
+ WHEN no_data_found THEN
448
+ __resoid := NULL;
449
+ __vnum := NULL;
450
+ __dbaction := NULL;
451
+
452
+ WHEN too_many_rows THEN
453
+ RAISE
454
+ 'Too many current stsresource records for resource %',
455
+ _resname
456
+ USING ERRCODE = 'cardinality_violation';
457
+
458
+ END;
459
+
460
+
461
+ -- -------------------------------------------------------------------------
462
+ -- A current resource exists.
463
+ -- -------------------------------------------------------------------------
464
+
465
+ IF __resoid IS NOT NULL THEN
466
+
467
+ -- An active resource already exists, so CREATE is invalid.
468
+ IF __dbaction != 3 THEN
469
+ RAISE
470
+ 'Duplicate resname within stsresource: %',
471
+ _resname
472
+ USING ERRCODE = 'unique_violation';
473
+ END IF;
474
+
475
+
476
+ -- ---------------------------------------------------------------------
477
+ -- Reinstating a deleted resource concept.
478
+ --
479
+ -- Close the delete tombstone and continue the resource version chain.
480
+ -- ---------------------------------------------------------------------
481
+
482
+ UPDATE public.stsresource
483
+ SET validto = __now
484
+ WHERE oid = __resoid;
485
+
486
+ __newvnum := __vnum + 1;
487
+
488
+ INSERT INTO public.stsresource(
489
+ resname,
490
+ resdesc,
491
+ resdesc_jsonb,
492
+ vnum,
493
+ dbaction,
494
+ dbactionuser,
495
+ validfrom
496
+ )
497
+ VALUES (
498
+ _resname,
499
+ _resdesc,
500
+ __resdesc_jsonb,
501
+ __newvnum,
502
+ 1,
503
+ _dbactionuser,
504
+ __now
505
+ )
506
+ RETURNING oid
507
+ INTO __inserted_oid;
508
+
509
+
510
+ -- ---------------------------------------------------------------------
511
+ -- RESOURCE-CONCEPT SEMANTICS
512
+ --
513
+ -- The entities belong to the resource concept, not to the deleted
514
+ -- resource version row. Move the complete entity history to the newly
515
+ -- reinstated resource OID.
516
+ -- ---------------------------------------------------------------------
517
+
518
+ UPDATE public.stsentity
519
+ SET resourceoid = __inserted_oid
520
+ WHERE resourceoid = __resoid;
521
+
522
+
523
+ ELSE
524
+
525
+ -- ---------------------------------------------------------------------
526
+ -- No current row exists.
527
+ --
528
+ -- Normally this is the first creation and there is no history.
529
+ --
530
+ -- If history exists without a current row, lock the latest historical
531
+ -- version and continue its version sequence instead of incorrectly
532
+ -- resetting to version 1.
533
+ -- ---------------------------------------------------------------------
534
+
535
+ SELECT
536
+ r.oid,
537
+ r.vnum
538
+ INTO
539
+ __historical_oid,
540
+ __historical_vnum
541
+ FROM public.stsresource r
542
+ WHERE r.resname = _resname
543
+ ORDER BY r.vnum DESC
544
+ LIMIT 1
545
+ FOR UPDATE;
546
+
547
+ __newvnum := COALESCE(__historical_vnum, 0) + 1;
548
+
549
+
550
+ -- Create the new active resource version.
551
+ INSERT INTO public.stsresource(
552
+ resname,
553
+ resdesc,
554
+ resdesc_jsonb,
555
+ vnum,
556
+ dbaction,
557
+ dbactionuser,
558
+ validfrom
559
+ )
560
+ VALUES (
561
+ _resname,
562
+ _resdesc,
563
+ __resdesc_jsonb,
564
+ __newvnum,
565
+ 1,
566
+ _dbactionuser,
567
+ __now
568
+ )
569
+ RETURNING oid
570
+ INTO __inserted_oid;
571
+
572
+
573
+ -- If this was recovery from historical data with no current row,
574
+ -- preserve the resource-concept ownership of any entity history.
575
+ IF __historical_oid IS NOT NULL THEN
576
+ UPDATE public.stsentity
577
+ SET resourceoid = __inserted_oid
578
+ WHERE resourceoid = __historical_oid;
579
+ END IF;
580
+
581
+ END IF;
582
+
583
+
584
+ -- -------------------------------------------------------------------------
585
+ -- Return the newly created/reinstated resource version.
586
+ -- -------------------------------------------------------------------------
587
+
588
+ RETURN QUERY
589
+ SELECT *
590
+ FROM public.stsresource
591
+ WHERE oid = __inserted_oid;
592
+
593
+ END;
594
+ $BODY$;
595
+
596
+ ALTER FUNCTION public.create_stsresource(
597
+ character varying,
598
+ character varying,
599
+ character varying
600
+ )
601
+ OWNER TO postgres;
602
+
603
+
604
+ -- =============================================================================
605
+ -- update_stsresource
606
+ -- =============================================================================
607
+ --
608
+ -- Updates a caller-specified current resource version.
609
+ --
610
+ -- The supplied version acts as an optimistic-concurrency requirement:
611
+ -- the resource must still be current, active, and exactly _resvnum.
612
+ --
613
+ -- Creating the next resource version changes the current resource OID, so all
614
+ -- entity history is moved to the new OID to preserve resource-concept
615
+ -- ownership.
616
+ CREATE OR REPLACE FUNCTION public.update_stsresource(
617
+ _dbactionuser character varying,
618
+ _resname character varying,
619
+ _resvnum bigint,
620
+ _resdesc character varying
621
+ )
622
+ RETURNS SETOF public.stsresource
623
+ LANGUAGE plpgsql
624
+ COST 100
625
+ VOLATILE
626
+ ROWS 1
627
+
628
+ AS $BODY$
629
+ DECLARE
630
+ __now timestamptz := now();
631
+ __resoid bigint;
632
+ __newresoid bigint;
633
+ __resdesc_jsonb jsonb;
634
+ BEGIN
635
+
636
+ -- Validate / parse resource JSON.
637
+ BEGIN
638
+ __resdesc_jsonb := _resdesc::jsonb;
639
+
640
+ EXCEPTION
641
+ WHEN invalid_text_representation THEN
642
+ RAISE EXCEPTION
643
+ 'Invalid JSON string: %',
644
+ _resdesc
645
+ USING ERRCODE = 'invalid_text_representation';
646
+ END;
647
+
648
+
649
+ -- Resolve and lock the requested current, non-deleted resource version.
650
+ BEGIN
651
+ SELECT
652
+ r.oid
653
+ INTO STRICT
654
+ __resoid
655
+ FROM public.stsresource r
656
+ WHERE r.resname = _resname
657
+ AND r.vnum = _resvnum
658
+ AND r.validto IS NULL
659
+ AND r.dbaction != 3
660
+ FOR UPDATE;
661
+
662
+ EXCEPTION
663
+
664
+ WHEN no_data_found THEN
665
+ RAISE
666
+ 'Resource % version % not found, not current, or deleted',
667
+ _resname,
668
+ _resvnum
669
+ USING ERRCODE = 'no_data_found';
670
+
671
+ WHEN too_many_rows THEN
672
+ RAISE
673
+ 'Too many current stsresource records for resource % version %',
674
+ _resname,
675
+ _resvnum
676
+ USING ERRCODE = 'cardinality_violation';
677
+
678
+ END;
679
+
680
+
681
+ -- Close the previous active resource version.
682
+ UPDATE public.stsresource
683
+ SET validto = __now
684
+ WHERE oid = __resoid;
685
+
686
+
687
+ -- Create the next active resource version.
688
+ INSERT INTO public.stsresource(
689
+ resname,
690
+ resdesc,
691
+ resdesc_jsonb,
692
+ vnum,
693
+ dbaction,
694
+ dbactionuser,
695
+ validfrom
696
+ )
697
+ VALUES (
698
+ _resname,
699
+ _resdesc,
700
+ __resdesc_jsonb,
701
+ _resvnum + 1,
702
+ 2,
703
+ _dbactionuser,
704
+ __now
705
+ )
706
+ RETURNING oid
707
+ INTO __newresoid;
708
+
709
+
710
+ -- Move all entity history to the new current resource OID.
711
+ UPDATE public.stsentity
712
+ SET resourceoid = __newresoid
713
+ WHERE resourceoid = __resoid;
714
+
715
+
716
+ -- Return the new resource version.
717
+ RETURN QUERY
718
+ SELECT *
719
+ FROM public.stsresource
720
+ WHERE oid = __newresoid;
721
+
722
+ END;
723
+ $BODY$;
724
+
725
+ ALTER FUNCTION public.update_stsresource(
726
+ character varying,
727
+ character varying,
728
+ bigint,
729
+ character varying
730
+ )
731
+ OWNER TO postgres;
732
+
733
+
734
+ -- =============================================================================
735
+ -- update_stsresource_latest
736
+ -- =============================================================================
737
+ --
738
+ -- Updates the current active resource without requiring the caller to supply
739
+ -- its version number.
740
+ --
741
+ -- oid and vnum are resolved in one locked query to avoid the duplicate lookups
742
+ -- used by the original implementation.
743
+ CREATE OR REPLACE FUNCTION public.update_stsresource_latest(
744
+ _dbactionuser character varying,
745
+ _resname character varying,
746
+ _resdesc character varying
747
+ )
748
+ RETURNS SETOF public.stsresource
749
+ LANGUAGE plpgsql
750
+ COST 100
751
+ VOLATILE
752
+ ROWS 1
753
+
754
+ AS $BODY$
755
+ DECLARE
756
+ __now timestamptz := now();
757
+ __resoid bigint;
758
+ __resvnum bigint;
759
+ __newresoid bigint;
760
+ __resdesc_jsonb jsonb;
761
+ BEGIN
762
+
763
+ -- Validate / parse resource JSON.
764
+ BEGIN
765
+ __resdesc_jsonb := _resdesc::jsonb;
766
+
767
+ EXCEPTION
768
+ WHEN invalid_text_representation THEN
769
+ RAISE EXCEPTION
770
+ 'Invalid JSON string: %',
771
+ _resdesc
772
+ USING ERRCODE = 'invalid_text_representation';
773
+ END;
774
+
775
+
776
+ -- Resolve and lock the current active resource in one operation.
777
+ BEGIN
778
+ SELECT
779
+ r.oid,
780
+ r.vnum
781
+ INTO STRICT
782
+ __resoid,
783
+ __resvnum
784
+ FROM public.stsresource r
785
+ WHERE r.resname = _resname
786
+ AND r.validto IS NULL
787
+ AND r.dbaction != 3
788
+ FOR UPDATE;
789
+
790
+ EXCEPTION
791
+
792
+ WHEN no_data_found THEN
793
+ RAISE
794
+ 'Resource % not found, not current, or deleted',
795
+ _resname
796
+ USING ERRCODE = 'no_data_found';
797
+
798
+ WHEN too_many_rows THEN
799
+ RAISE
800
+ 'Too many current stsresource records for resource %',
801
+ _resname
802
+ USING ERRCODE = 'cardinality_violation';
803
+
804
+ END;
805
+
806
+
807
+ -- Close the previous active resource version.
808
+ UPDATE public.stsresource
809
+ SET validto = __now
810
+ WHERE oid = __resoid;
811
+
812
+
813
+ -- Create the next active resource version.
814
+ INSERT INTO public.stsresource(
815
+ resname,
816
+ resdesc,
817
+ resdesc_jsonb,
818
+ vnum,
819
+ dbaction,
820
+ dbactionuser,
821
+ validfrom
822
+ )
823
+ VALUES (
824
+ _resname,
825
+ _resdesc,
826
+ __resdesc_jsonb,
827
+ __resvnum + 1,
828
+ 2,
829
+ _dbactionuser,
830
+ __now
831
+ )
832
+ RETURNING oid
833
+ INTO __newresoid;
834
+
835
+
836
+ -- Preserve resource-concept ownership for all entity history.
837
+ UPDATE public.stsentity
838
+ SET resourceoid = __newresoid
839
+ WHERE resourceoid = __resoid;
840
+
841
+
842
+ -- Return the new resource version.
843
+ RETURN QUERY
844
+ SELECT *
845
+ FROM public.stsresource
846
+ WHERE oid = __newresoid;
847
+
848
+ END;
849
+ $BODY$;
850
+
851
+ ALTER FUNCTION public.update_stsresource_latest(
852
+ character varying,
853
+ character varying,
854
+ character varying
855
+ )
856
+ OWNER TO postgres;
857
+
858
+
859
+ -- =============================================================================
860
+ -- delete_stsresource
861
+ -- =============================================================================
862
+ --
863
+ -- Logically deletes a caller-specified current resource version.
864
+ --
865
+ -- Delete is versioned: the active row is closed and a new dbaction=3 tombstone
866
+ -- becomes the current resource version.
867
+ --
868
+ -- Entity rows are NOT individually deleted. Their complete history is moved to
869
+ -- the new resource tombstone OID. If the resource is later recreated, those
870
+ -- same rows are moved again to the reinstated active resource OID.
871
+ CREATE OR REPLACE FUNCTION public.delete_stsresource(
872
+ _dbactionuser character varying,
873
+ _resname character varying,
874
+ _resvnum bigint
875
+ )
876
+ RETURNS SETOF public.stsresource
877
+ LANGUAGE plpgsql
878
+ COST 100
879
+ VOLATILE
880
+ ROWS 1
881
+
882
+ AS $BODY$
883
+ DECLARE
884
+ __now timestamptz := now();
885
+ __resoid bigint;
886
+ __newresoid bigint;
887
+ BEGIN
888
+
889
+ -- Resolve and lock the requested current active resource version.
890
+ BEGIN
891
+ SELECT
892
+ r.oid
893
+ INTO STRICT
894
+ __resoid
895
+ FROM public.stsresource r
896
+ WHERE r.resname = _resname
897
+ AND r.vnum = _resvnum
898
+ AND r.validto IS NULL
899
+ AND r.dbaction != 3
900
+ FOR UPDATE;
901
+
902
+ EXCEPTION
903
+
904
+ WHEN no_data_found THEN
905
+ RAISE
906
+ 'Resource % version % not found, not current, or deleted',
907
+ _resname,
908
+ _resvnum
909
+ USING ERRCODE = 'no_data_found';
910
+
911
+ WHEN too_many_rows THEN
912
+ RAISE
913
+ 'Too many current stsresource records for resource % version %',
914
+ _resname,
915
+ _resvnum
916
+ USING ERRCODE = 'cardinality_violation';
917
+
918
+ END;
919
+
920
+
921
+ -- Close the current active resource version.
922
+ UPDATE public.stsresource
923
+ SET validto = __now
924
+ WHERE oid = __resoid;
925
+
926
+
927
+ -- Create the delete tombstone.
928
+ --
929
+ -- Copy resdesc_jsonb directly rather than reparsing resdesc text.
930
+ INSERT INTO public.stsresource(
931
+ resname,
932
+ resdesc,
933
+ resdesc_jsonb,
934
+ vnum,
935
+ dbaction,
936
+ dbactionuser,
937
+ validfrom
938
+ )
939
+ SELECT
940
+ r.resname,
941
+ r.resdesc,
942
+ r.resdesc_jsonb,
943
+ _resvnum + 1,
944
+ 3,
945
+ _dbactionuser,
946
+ __now
947
+ FROM public.stsresource r
948
+ WHERE r.oid = __resoid
949
+ RETURNING oid
950
+ INTO __newresoid;
951
+
952
+
953
+ -- Preserve the entity history under the current resource tombstone OID.
954
+ UPDATE public.stsentity
955
+ SET resourceoid = __newresoid
956
+ WHERE resourceoid = __resoid;
957
+
958
+
959
+ -- Return the delete tombstone.
960
+ RETURN QUERY
961
+ SELECT *
962
+ FROM public.stsresource
963
+ WHERE oid = __newresoid;
964
+
965
+ END;
966
+ $BODY$;
967
+
968
+ ALTER FUNCTION public.delete_stsresource(
969
+ character varying,
970
+ character varying,
971
+ bigint
972
+ )
973
+ OWNER TO postgres;
974
+
975
+
976
+ -- =============================================================================
977
+ -- delete_stsresource_latest
978
+ -- =============================================================================
979
+ --
980
+ -- Logically deletes the current active resource without requiring the caller to
981
+ -- provide its version number.
982
+ --
983
+ -- The current OID and version are resolved together and locked in one query.
984
+ CREATE OR REPLACE FUNCTION public.delete_stsresource_latest(
985
+ _dbactionuser character varying,
986
+ _resname character varying
987
+ )
988
+ RETURNS SETOF public.stsresource
989
+ LANGUAGE plpgsql
990
+ COST 100
991
+ VOLATILE
992
+ ROWS 1
993
+
994
+ AS $BODY$
995
+ DECLARE
996
+ __now timestamptz := now();
997
+ __resoid bigint;
998
+ __resvnum bigint;
999
+ __newresoid bigint;
1000
+ BEGIN
1001
+
1002
+ -- Resolve and lock the current active resource.
1003
+ BEGIN
1004
+ SELECT
1005
+ r.oid,
1006
+ r.vnum
1007
+ INTO STRICT
1008
+ __resoid,
1009
+ __resvnum
1010
+ FROM public.stsresource r
1011
+ WHERE r.resname = _resname
1012
+ AND r.validto IS NULL
1013
+ AND r.dbaction != 3
1014
+ FOR UPDATE;
1015
+
1016
+ EXCEPTION
1017
+
1018
+ WHEN no_data_found THEN
1019
+ RAISE
1020
+ 'Resource % not found, not current, or deleted',
1021
+ _resname
1022
+ USING ERRCODE = 'no_data_found';
1023
+
1024
+ WHEN too_many_rows THEN
1025
+ RAISE
1026
+ 'Too many current stsresource records for resource %',
1027
+ _resname
1028
+ USING ERRCODE = 'cardinality_violation';
1029
+
1030
+ END;
1031
+
1032
+
1033
+ -- Close the current active resource version.
1034
+ UPDATE public.stsresource
1035
+ SET validto = __now
1036
+ WHERE oid = __resoid;
1037
+
1038
+
1039
+ -- Create the delete tombstone.
1040
+ --
1041
+ -- The original implementation accidentally omitted resdesc_jsonb from the
1042
+ -- SELECT even though it was present in the INSERT column list. This version
1043
+ -- copies the already parsed JSONB value correctly.
1044
+ INSERT INTO public.stsresource(
1045
+ resname,
1046
+ resdesc,
1047
+ resdesc_jsonb,
1048
+ vnum,
1049
+ dbaction,
1050
+ dbactionuser,
1051
+ validfrom
1052
+ )
1053
+ SELECT
1054
+ r.resname,
1055
+ r.resdesc,
1056
+ r.resdesc_jsonb,
1057
+ __resvnum + 1,
1058
+ 3,
1059
+ _dbactionuser,
1060
+ __now
1061
+ FROM public.stsresource r
1062
+ WHERE r.oid = __resoid
1063
+ RETURNING oid
1064
+ INTO __newresoid;
1065
+
1066
+
1067
+ -- Preserve the entity history under the current resource tombstone OID.
1068
+ UPDATE public.stsentity
1069
+ SET resourceoid = __newresoid
1070
+ WHERE resourceoid = __resoid;
1071
+
1072
+
1073
+ -- Return the delete tombstone.
1074
+ RETURN QUERY
1075
+ SELECT *
1076
+ FROM public.stsresource
1077
+ WHERE oid = __newresoid;
1078
+
1079
+ END;
1080
+ $BODY$;
1081
+
1082
+ ALTER FUNCTION public.delete_stsresource_latest(
1083
+ character varying,
1084
+ character varying
1085
+ )
1086
+ OWNER TO postgres;
1087
+
1088
+
1089
+ -- =============================================================================
1090
+ -- Entity functions
1091
+ -- =============================================================================
1092
+
1093
+
1094
+ -- =============================================================================
1095
+ -- create_stsentity
1096
+ -- =============================================================================
1097
+ --
1098
+ -- Creates an entity against a caller-specified current resource version.
1099
+ --
1100
+ -- A deleted entity is still the current entity tombstone. CREATE against a
1101
+ -- deleted entity closes that tombstone and continues the entity version chain.
1102
+ --
1103
+ -- The current resource row is locked first. This prevents the resource from
1104
+ -- changing OID while the entity operation is in progress.
1105
+ CREATE OR REPLACE FUNCTION public.create_stsentity(
1106
+ _dbactionuser character varying,
1107
+ _resname character varying,
1108
+ _resvnum bigint,
1109
+ _entname character varying,
1110
+ _entvalue character varying
1111
+ )
1112
+ RETURNS SETOF public.stsentity
1113
+ LANGUAGE plpgsql
1114
+ COST 100
1115
+ VOLATILE
1116
+ ROWS 1
1117
+
1118
+ AS $BODY$
1119
+ DECLARE
1120
+ __now timestamptz := now();
1121
+
1122
+ -- Current resource.
1123
+ __resoid bigint;
1124
+
1125
+ -- Current entity, if one exists.
1126
+ __entoid bigint;
1127
+ __entvnum bigint;
1128
+ __entdbaction smallint;
1129
+
1130
+ -- Version/OID for the new active entity.
1131
+ __newvnum bigint;
1132
+ __inserted_oid bigint;
1133
+
1134
+ -- Parsed JSON.
1135
+ __entvalue_jsonb jsonb;
1136
+ BEGIN
1137
+
1138
+ -- -------------------------------------------------------------------------
1139
+ -- Validate / parse entity JSON.
1140
+ -- -------------------------------------------------------------------------
1141
+
1142
+ BEGIN
1143
+ __entvalue_jsonb := _entvalue::jsonb;
1144
+
1145
+ EXCEPTION
1146
+ WHEN invalid_text_representation THEN
1147
+ RAISE EXCEPTION
1148
+ 'Invalid JSON string: %',
1149
+ _entvalue
1150
+ USING ERRCODE = 'invalid_text_representation';
1151
+ END;
1152
+
1153
+
1154
+ -- -------------------------------------------------------------------------
1155
+ -- Resolve and lock the requested current, non-deleted resource version.
1156
+ --
1157
+ -- Locking the resource prevents a concurrent resource update/delete from
1158
+ -- changing resourceoid while this entity transaction is operating.
1159
+ -- -------------------------------------------------------------------------
1160
+
1161
+ BEGIN
1162
+ SELECT
1163
+ r.oid
1164
+ INTO STRICT
1165
+ __resoid
1166
+ FROM public.stsresource r
1167
+ WHERE r.resname = _resname
1168
+ AND r.vnum = _resvnum
1169
+ AND r.validto IS NULL
1170
+ AND r.dbaction != 3
1171
+ FOR UPDATE;
1172
+
1173
+ EXCEPTION
1174
+
1175
+ WHEN no_data_found THEN
1176
+ RAISE
1177
+ 'Resource % version % not found, not current, or deleted',
1178
+ _resname,
1179
+ _resvnum
1180
+ USING ERRCODE = 'no_data_found';
1181
+
1182
+ WHEN too_many_rows THEN
1183
+ RAISE
1184
+ 'Too many current stsresource records for resource % version %',
1185
+ _resname,
1186
+ _resvnum
1187
+ USING ERRCODE = 'cardinality_violation';
1188
+
1189
+ END;
1190
+
1191
+
1192
+ -- -------------------------------------------------------------------------
1193
+ -- Find and lock the current entity.
1194
+ --
1195
+ -- IMPORTANT:
1196
+ -- Do NOT exclude dbaction = 3.
1197
+ --
1198
+ -- A logically deleted entity remains the current entity version and must be
1199
+ -- closed before it can be recreated.
1200
+ -- -------------------------------------------------------------------------
1201
+
1202
+ BEGIN
1203
+ SELECT
1204
+ e.oid,
1205
+ e.vnum,
1206
+ e.dbaction
1207
+ INTO STRICT
1208
+ __entoid,
1209
+ __entvnum,
1210
+ __entdbaction
1211
+ FROM public.stsentity e
1212
+ WHERE e.resourceoid = __resoid
1213
+ AND e.entname = _entname
1214
+ AND e.validto IS NULL
1215
+ FOR UPDATE;
1216
+
1217
+ EXCEPTION
1218
+
1219
+ WHEN no_data_found THEN
1220
+ __entoid := NULL;
1221
+ __entvnum := NULL;
1222
+ __entdbaction := NULL;
1223
+
1224
+ WHEN too_many_rows THEN
1225
+ RAISE
1226
+ 'Too many current stsentity records for entity % within resource %',
1227
+ _entname,
1228
+ _resname
1229
+ USING ERRCODE = 'cardinality_violation';
1230
+
1231
+ END;
1232
+
1233
+
1234
+ -- -------------------------------------------------------------------------
1235
+ -- A current entity exists.
1236
+ -- -------------------------------------------------------------------------
1237
+
1238
+ IF __entoid IS NOT NULL THEN
1239
+
1240
+ -- Active current entity: CREATE is a duplicate.
1241
+ IF __entdbaction != 3 THEN
1242
+ RAISE
1243
+ 'Duplicate entname % within stsresource %',
1244
+ _entname,
1245
+ _resname
1246
+ USING ERRCODE = 'unique_violation';
1247
+ END IF;
1248
+
1249
+
1250
+ -- Deleted current entity: close the tombstone and continue versioning.
1251
+ UPDATE public.stsentity
1252
+ SET validto = __now
1253
+ WHERE oid = __entoid;
1254
+
1255
+ __newvnum := __entvnum + 1;
1256
+
1257
+ ELSE
1258
+
1259
+ -- ---------------------------------------------------------------------
1260
+ -- No current entity exists.
1261
+ --
1262
+ -- Normally this is the first creation, producing version 1.
1263
+ --
1264
+ -- MAX(vnum) also protects against an abnormal history in which previous
1265
+ -- versions exist but no entity row currently has validto IS NULL.
1266
+ -- ---------------------------------------------------------------------
1267
+
1268
+ SELECT
1269
+ COALESCE(MAX(e.vnum), 0) + 1
1270
+ INTO
1271
+ __newvnum
1272
+ FROM public.stsentity e
1273
+ WHERE e.resourceoid = __resoid
1274
+ AND e.entname = _entname;
1275
+
1276
+ END IF;
1277
+
1278
+
1279
+ -- Create the new active entity version.
1280
+ INSERT INTO public.stsentity(
1281
+ resourceoid,
1282
+ entname,
1283
+ entvalue,
1284
+ entvalue_jsonb,
1285
+ vnum,
1286
+ dbaction,
1287
+ dbactionuser,
1288
+ validfrom
1289
+ )
1290
+ VALUES (
1291
+ __resoid,
1292
+ _entname,
1293
+ _entvalue,
1294
+ __entvalue_jsonb,
1295
+ __newvnum,
1296
+ 1,
1297
+ _dbactionuser,
1298
+ __now
1299
+ )
1300
+ RETURNING oid
1301
+ INTO __inserted_oid;
1302
+
1303
+
1304
+ -- Return the newly created/recreated entity.
1305
+ RETURN QUERY
1306
+ SELECT *
1307
+ FROM public.stsentity
1308
+ WHERE oid = __inserted_oid;
1309
+
1310
+ END;
1311
+ $BODY$;
1312
+
1313
+ ALTER FUNCTION public.create_stsentity(
1314
+ character varying,
1315
+ character varying,
1316
+ bigint,
1317
+ character varying,
1318
+ character varying
1319
+ )
1320
+ OWNER TO postgres;
1321
+
1322
+
1323
+ -- =============================================================================
1324
+ -- create_stsentity_latest
1325
+ -- =============================================================================
1326
+ --
1327
+ -- Creates an entity against the latest/current active resource version.
1328
+ --
1329
+ -- This is identical in entity semantics to create_stsentity, except the caller
1330
+ -- does not supply the resource version.
1331
+ CREATE OR REPLACE FUNCTION public.create_stsentity_latest(
1332
+ _dbactionuser character varying,
1333
+ _resname character varying,
1334
+ _entname character varying,
1335
+ _entvalue character varying
1336
+ )
1337
+ RETURNS SETOF public.stsentity
1338
+ LANGUAGE plpgsql
1339
+ COST 100
1340
+ VOLATILE
1341
+ ROWS 1
1342
+
1343
+ AS $BODY$
1344
+ DECLARE
1345
+ __now timestamptz := now();
1346
+
1347
+ __resoid bigint;
1348
+
1349
+ __entoid bigint;
1350
+ __entvnum bigint;
1351
+ __entdbaction smallint;
1352
+
1353
+ __newvnum bigint;
1354
+ __inserted_oid bigint;
1355
+
1356
+ __entvalue_jsonb jsonb;
1357
+ BEGIN
1358
+
1359
+ -- Validate / parse entity JSON.
1360
+ BEGIN
1361
+ __entvalue_jsonb := _entvalue::jsonb;
1362
+
1363
+ EXCEPTION
1364
+ WHEN invalid_text_representation THEN
1365
+ RAISE EXCEPTION
1366
+ 'Invalid JSON string: %',
1367
+ _entvalue
1368
+ USING ERRCODE = 'invalid_text_representation';
1369
+ END;
1370
+
1371
+
1372
+ -- Resolve and lock the current active resource.
1373
+ BEGIN
1374
+ SELECT
1375
+ r.oid
1376
+ INTO STRICT
1377
+ __resoid
1378
+ FROM public.stsresource r
1379
+ WHERE r.resname = _resname
1380
+ AND r.validto IS NULL
1381
+ AND r.dbaction != 3
1382
+ FOR UPDATE;
1383
+
1384
+ EXCEPTION
1385
+
1386
+ WHEN no_data_found THEN
1387
+ RAISE
1388
+ 'Resource % not found, not current, or deleted',
1389
+ _resname
1390
+ USING ERRCODE = 'no_data_found';
1391
+
1392
+ WHEN too_many_rows THEN
1393
+ RAISE
1394
+ 'Too many current stsresource records for resource %',
1395
+ _resname
1396
+ USING ERRCODE = 'cardinality_violation';
1397
+
1398
+ END;
1399
+
1400
+
1401
+ -- Resolve and lock the current entity, including a deleted tombstone.
1402
+ BEGIN
1403
+ SELECT
1404
+ e.oid,
1405
+ e.vnum,
1406
+ e.dbaction
1407
+ INTO STRICT
1408
+ __entoid,
1409
+ __entvnum,
1410
+ __entdbaction
1411
+ FROM public.stsentity e
1412
+ WHERE e.resourceoid = __resoid
1413
+ AND e.entname = _entname
1414
+ AND e.validto IS NULL
1415
+ FOR UPDATE;
1416
+
1417
+ EXCEPTION
1418
+
1419
+ WHEN no_data_found THEN
1420
+ __entoid := NULL;
1421
+ __entvnum := NULL;
1422
+ __entdbaction := NULL;
1423
+
1424
+ WHEN too_many_rows THEN
1425
+ RAISE
1426
+ 'Too many current stsentity records for entity % within resource %',
1427
+ _entname,
1428
+ _resname
1429
+ USING ERRCODE = 'cardinality_violation';
1430
+
1431
+ END;
1432
+
1433
+
1434
+ -- Current entity exists.
1435
+ IF __entoid IS NOT NULL THEN
1436
+
1437
+ -- Active entity already exists.
1438
+ IF __entdbaction != 3 THEN
1439
+ RAISE
1440
+ 'Duplicate entname % within stsresource %',
1441
+ _entname,
1442
+ _resname
1443
+ USING ERRCODE = 'unique_violation';
1444
+ END IF;
1445
+
1446
+ -- Recreate the deleted entity by closing its tombstone.
1447
+ UPDATE public.stsentity
1448
+ SET validto = __now
1449
+ WHERE oid = __entoid;
1450
+
1451
+ __newvnum := __entvnum + 1;
1452
+
1453
+ ELSE
1454
+
1455
+ -- First creation, or recovery from history with no current row.
1456
+ SELECT
1457
+ COALESCE(MAX(e.vnum), 0) + 1
1458
+ INTO
1459
+ __newvnum
1460
+ FROM public.stsentity e
1461
+ WHERE e.resourceoid = __resoid
1462
+ AND e.entname = _entname;
1463
+
1464
+ END IF;
1465
+
1466
+
1467
+ -- Create the new active entity version.
1468
+ INSERT INTO public.stsentity(
1469
+ resourceoid,
1470
+ entname,
1471
+ entvalue,
1472
+ entvalue_jsonb,
1473
+ vnum,
1474
+ dbaction,
1475
+ dbactionuser,
1476
+ validfrom
1477
+ )
1478
+ VALUES (
1479
+ __resoid,
1480
+ _entname,
1481
+ _entvalue,
1482
+ __entvalue_jsonb,
1483
+ __newvnum,
1484
+ 1,
1485
+ _dbactionuser,
1486
+ __now
1487
+ )
1488
+ RETURNING oid
1489
+ INTO __inserted_oid;
1490
+
1491
+
1492
+ -- Return the new entity version.
1493
+ RETURN QUERY
1494
+ SELECT *
1495
+ FROM public.stsentity
1496
+ WHERE oid = __inserted_oid;
1497
+
1498
+ END;
1499
+ $BODY$;
1500
+
1501
+ ALTER FUNCTION public.create_stsentity_latest(
1502
+ character varying,
1503
+ character varying,
1504
+ character varying,
1505
+ character varying
1506
+ )
1507
+ OWNER TO postgres;
1508
+
1509
+
1510
+ -- =============================================================================
1511
+ -- update_stsentity
1512
+ -- =============================================================================
1513
+ --
1514
+ -- Updates a caller-specified current entity version under a caller-specified
1515
+ -- current resource version.
1516
+ --
1517
+ -- Both version arguments therefore act as optimistic-concurrency requirements.
1518
+ CREATE OR REPLACE FUNCTION public.update_stsentity(
1519
+ _dbactionuser character varying,
1520
+ _resname character varying,
1521
+ _resvnum bigint,
1522
+ _entname character varying,
1523
+ _entvnum bigint,
1524
+ _entvalue character varying
1525
+ )
1526
+ RETURNS SETOF public.stsentity
1527
+ LANGUAGE plpgsql
1528
+ COST 100
1529
+ VOLATILE
1530
+ ROWS 1
1531
+
1532
+ AS $BODY$
1533
+ DECLARE
1534
+ __now timestamptz := now();
1535
+ __resoid bigint;
1536
+ __entoid bigint;
1537
+ __inserted_oid bigint;
1538
+ __entvalue_jsonb jsonb;
1539
+ BEGIN
1540
+
1541
+ -- Validate / parse entity JSON.
1542
+ BEGIN
1543
+ __entvalue_jsonb := _entvalue::jsonb;
1544
+
1545
+ EXCEPTION
1546
+ WHEN invalid_text_representation THEN
1547
+ RAISE EXCEPTION
1548
+ 'Invalid JSON string: %',
1549
+ _entvalue
1550
+ USING ERRCODE = 'invalid_text_representation';
1551
+ END;
1552
+
1553
+
1554
+ -- Resolve and lock the requested current active resource version.
1555
+ BEGIN
1556
+ SELECT
1557
+ r.oid
1558
+ INTO STRICT
1559
+ __resoid
1560
+ FROM public.stsresource r
1561
+ WHERE r.resname = _resname
1562
+ AND r.vnum = _resvnum
1563
+ AND r.validto IS NULL
1564
+ AND r.dbaction != 3
1565
+ FOR UPDATE;
1566
+
1567
+ EXCEPTION
1568
+
1569
+ WHEN no_data_found THEN
1570
+ RAISE
1571
+ 'Resource % version % not found, not current, or deleted',
1572
+ _resname,
1573
+ _resvnum
1574
+ USING ERRCODE = 'no_data_found';
1575
+
1576
+ WHEN too_many_rows THEN
1577
+ RAISE
1578
+ 'Too many current stsresource records for resource % version %',
1579
+ _resname,
1580
+ _resvnum
1581
+ USING ERRCODE = 'cardinality_violation';
1582
+
1583
+ END;
1584
+
1585
+
1586
+ -- Resolve and lock the requested current active entity version.
1587
+ BEGIN
1588
+ SELECT
1589
+ e.oid
1590
+ INTO STRICT
1591
+ __entoid
1592
+ FROM public.stsentity e
1593
+ WHERE e.resourceoid = __resoid
1594
+ AND e.entname = _entname
1595
+ AND e.vnum = _entvnum
1596
+ AND e.validto IS NULL
1597
+ AND e.dbaction != 3
1598
+ FOR UPDATE;
1599
+
1600
+ EXCEPTION
1601
+
1602
+ WHEN no_data_found THEN
1603
+ RAISE
1604
+ 'Entity % version % not found, not current, or deleted within resource %',
1605
+ _entname,
1606
+ _entvnum,
1607
+ _resname
1608
+ USING ERRCODE = 'no_data_found';
1609
+
1610
+ WHEN too_many_rows THEN
1611
+ RAISE
1612
+ 'Too many current stsentity records for entity % version % within resource %',
1613
+ _entname,
1614
+ _entvnum,
1615
+ _resname
1616
+ USING ERRCODE = 'cardinality_violation';
1617
+
1618
+ END;
1619
+
1620
+
1621
+ -- Close the previous entity version.
1622
+ UPDATE public.stsentity
1623
+ SET validto = __now
1624
+ WHERE oid = __entoid;
1625
+
1626
+
1627
+ -- Create the next active entity version.
1628
+ INSERT INTO public.stsentity(
1629
+ resourceoid,
1630
+ entname,
1631
+ entvalue,
1632
+ entvalue_jsonb,
1633
+ vnum,
1634
+ dbaction,
1635
+ dbactionuser,
1636
+ validfrom
1637
+ )
1638
+ VALUES (
1639
+ __resoid,
1640
+ _entname,
1641
+ _entvalue,
1642
+ __entvalue_jsonb,
1643
+ _entvnum + 1,
1644
+ 2,
1645
+ _dbactionuser,
1646
+ __now
1647
+ )
1648
+ RETURNING oid
1649
+ INTO __inserted_oid;
1650
+
1651
+
1652
+ -- Return the new entity version.
1653
+ RETURN QUERY
1654
+ SELECT *
1655
+ FROM public.stsentity
1656
+ WHERE oid = __inserted_oid;
1657
+
1658
+ END;
1659
+ $BODY$;
1660
+
1661
+ ALTER FUNCTION public.update_stsentity(
1662
+ character varying,
1663
+ character varying,
1664
+ bigint,
1665
+ character varying,
1666
+ bigint,
1667
+ character varying
1668
+ )
1669
+ OWNER TO postgres;
1670
+
1671
+
1672
+ -- =============================================================================
1673
+ -- update_stsentity_latest
1674
+ -- =============================================================================
1675
+ --
1676
+ -- Updates the current active entity under the current active resource.
1677
+ --
1678
+ -- The current entity OID and version are resolved together in one locked query.
1679
+ CREATE OR REPLACE FUNCTION public.update_stsentity_latest(
1680
+ _dbactionuser character varying,
1681
+ _resname character varying,
1682
+ _entname character varying,
1683
+ _entvalue character varying
1684
+ )
1685
+ RETURNS SETOF public.stsentity
1686
+ LANGUAGE plpgsql
1687
+ COST 100
1688
+ VOLATILE
1689
+ ROWS 1
1690
+
1691
+ AS $BODY$
1692
+ DECLARE
1693
+ __now timestamptz := now();
1694
+ __resoid bigint;
1695
+ __entoid bigint;
1696
+ __entvnum bigint;
1697
+ __inserted_oid bigint;
1698
+ __entvalue_jsonb jsonb;
1699
+ BEGIN
1700
+
1701
+ -- Validate / parse entity JSON.
1702
+ BEGIN
1703
+ __entvalue_jsonb := _entvalue::jsonb;
1704
+
1705
+ EXCEPTION
1706
+ WHEN invalid_text_representation THEN
1707
+ RAISE EXCEPTION
1708
+ 'Invalid JSON string: %',
1709
+ _entvalue
1710
+ USING ERRCODE = 'invalid_text_representation';
1711
+ END;
1712
+
1713
+
1714
+ -- Resolve and lock the current active resource.
1715
+ BEGIN
1716
+ SELECT
1717
+ r.oid
1718
+ INTO STRICT
1719
+ __resoid
1720
+ FROM public.stsresource r
1721
+ WHERE r.resname = _resname
1722
+ AND r.validto IS NULL
1723
+ AND r.dbaction != 3
1724
+ FOR UPDATE;
1725
+
1726
+ EXCEPTION
1727
+
1728
+ WHEN no_data_found THEN
1729
+ RAISE
1730
+ 'Resource % not found, not current, or deleted',
1731
+ _resname
1732
+ USING ERRCODE = 'no_data_found';
1733
+
1734
+ WHEN too_many_rows THEN
1735
+ RAISE
1736
+ 'Too many current stsresource records for resource %',
1737
+ _resname
1738
+ USING ERRCODE = 'cardinality_violation';
1739
+
1740
+ END;
1741
+
1742
+
1743
+ -- Resolve and lock the current active entity and its version.
1744
+ BEGIN
1745
+ SELECT
1746
+ e.oid,
1747
+ e.vnum
1748
+ INTO STRICT
1749
+ __entoid,
1750
+ __entvnum
1751
+ FROM public.stsentity e
1752
+ WHERE e.resourceoid = __resoid
1753
+ AND e.entname = _entname
1754
+ AND e.validto IS NULL
1755
+ AND e.dbaction != 3
1756
+ FOR UPDATE;
1757
+
1758
+ EXCEPTION
1759
+
1760
+ WHEN no_data_found THEN
1761
+ RAISE
1762
+ 'Entity % not found, not current, or deleted within resource %',
1763
+ _entname,
1764
+ _resname
1765
+ USING ERRCODE = 'no_data_found';
1766
+
1767
+ WHEN too_many_rows THEN
1768
+ RAISE
1769
+ 'Too many current stsentity records for entity % within resource %',
1770
+ _entname,
1771
+ _resname
1772
+ USING ERRCODE = 'cardinality_violation';
1773
+
1774
+ END;
1775
+
1776
+
1777
+ -- Close the previous entity version.
1778
+ UPDATE public.stsentity
1779
+ SET validto = __now
1780
+ WHERE oid = __entoid;
1781
+
1782
+
1783
+ -- Create the next active entity version.
1784
+ INSERT INTO public.stsentity(
1785
+ resourceoid,
1786
+ entname,
1787
+ entvalue,
1788
+ entvalue_jsonb,
1789
+ vnum,
1790
+ dbaction,
1791
+ dbactionuser,
1792
+ validfrom
1793
+ )
1794
+ VALUES (
1795
+ __resoid,
1796
+ _entname,
1797
+ _entvalue,
1798
+ __entvalue_jsonb,
1799
+ __entvnum + 1,
1800
+ 2,
1801
+ _dbactionuser,
1802
+ __now
1803
+ )
1804
+ RETURNING oid
1805
+ INTO __inserted_oid;
1806
+
1807
+
1808
+ -- Return the new entity version.
1809
+ RETURN QUERY
1810
+ SELECT *
1811
+ FROM public.stsentity
1812
+ WHERE oid = __inserted_oid;
1813
+
1814
+ END;
1815
+ $BODY$;
1816
+
1817
+ ALTER FUNCTION public.update_stsentity_latest(
1818
+ character varying,
1819
+ character varying,
1820
+ character varying,
1821
+ character varying
1822
+ )
1823
+ OWNER TO postgres;
1824
+
1825
+
1826
+ -- =============================================================================
1827
+ -- delete_stsentity
1828
+ -- =============================================================================
1829
+ --
1830
+ -- Logically deletes a caller-specified current active entity version.
1831
+ --
1832
+ -- Delete closes the active entity version and creates a dbaction=3 tombstone
1833
+ -- as the next/current version.
1834
+ CREATE OR REPLACE FUNCTION public.delete_stsentity(
1835
+ _dbactionuser character varying,
1836
+ _resname character varying,
1837
+ _resvnum bigint,
1838
+ _entname character varying,
1839
+ _entvnum bigint
1840
+ )
1841
+ RETURNS SETOF public.stsentity
1842
+ LANGUAGE plpgsql
1843
+ COST 100
1844
+ VOLATILE
1845
+ ROWS 1
1846
+
1847
+ AS $BODY$
1848
+ DECLARE
1849
+ __now timestamptz := now();
1850
+ __resoid bigint;
1851
+ __entoid bigint;
1852
+ __inserted_oid bigint;
1853
+ BEGIN
1854
+
1855
+ -- Resolve and lock the requested current active resource version.
1856
+ BEGIN
1857
+ SELECT
1858
+ r.oid
1859
+ INTO STRICT
1860
+ __resoid
1861
+ FROM public.stsresource r
1862
+ WHERE r.resname = _resname
1863
+ AND r.vnum = _resvnum
1864
+ AND r.validto IS NULL
1865
+ AND r.dbaction != 3
1866
+ FOR UPDATE;
1867
+
1868
+ EXCEPTION
1869
+
1870
+ WHEN no_data_found THEN
1871
+ RAISE
1872
+ 'Resource % version % not found, not current, or deleted',
1873
+ _resname,
1874
+ _resvnum
1875
+ USING ERRCODE = 'no_data_found';
1876
+
1877
+ WHEN too_many_rows THEN
1878
+ RAISE
1879
+ 'Too many current stsresource records for resource % version %',
1880
+ _resname,
1881
+ _resvnum
1882
+ USING ERRCODE = 'cardinality_violation';
1883
+
1884
+ END;
1885
+
1886
+
1887
+ -- Resolve and lock the requested current active entity version.
1888
+ BEGIN
1889
+ SELECT
1890
+ e.oid
1891
+ INTO STRICT
1892
+ __entoid
1893
+ FROM public.stsentity e
1894
+ WHERE e.resourceoid = __resoid
1895
+ AND e.entname = _entname
1896
+ AND e.vnum = _entvnum
1897
+ AND e.validto IS NULL
1898
+ AND e.dbaction != 3
1899
+ FOR UPDATE;
1900
+
1901
+ EXCEPTION
1902
+
1903
+ WHEN no_data_found THEN
1904
+ RAISE
1905
+ 'Entity % version % not found, not current, or deleted within resource %',
1906
+ _entname,
1907
+ _entvnum,
1908
+ _resname
1909
+ USING ERRCODE = 'no_data_found';
1910
+
1911
+ WHEN too_many_rows THEN
1912
+ RAISE
1913
+ 'Too many current stsentity records for entity % version % within resource %',
1914
+ _entname,
1915
+ _entvnum,
1916
+ _resname
1917
+ USING ERRCODE = 'cardinality_violation';
1918
+
1919
+ END;
1920
+
1921
+
1922
+ -- Close the previous active entity version.
1923
+ UPDATE public.stsentity
1924
+ SET validto = __now
1925
+ WHERE oid = __entoid;
1926
+
1927
+
1928
+ -- Create the delete tombstone.
1929
+ --
1930
+ -- Copy entvalue_jsonb directly rather than reparsing entvalue text.
1931
+ INSERT INTO public.stsentity(
1932
+ resourceoid,
1933
+ entname,
1934
+ entvalue,
1935
+ entvalue_jsonb,
1936
+ vnum,
1937
+ dbaction,
1938
+ dbactionuser,
1939
+ validfrom
1940
+ )
1941
+ SELECT
1942
+ __resoid,
1943
+ e.entname,
1944
+ e.entvalue,
1945
+ e.entvalue_jsonb,
1946
+ _entvnum + 1,
1947
+ 3,
1948
+ _dbactionuser,
1949
+ __now
1950
+ FROM public.stsentity e
1951
+ WHERE e.oid = __entoid
1952
+ RETURNING oid
1953
+ INTO __inserted_oid;
1954
+
1955
+
1956
+ -- Return the delete tombstone.
1957
+ RETURN QUERY
1958
+ SELECT *
1959
+ FROM public.stsentity
1960
+ WHERE oid = __inserted_oid;
1961
+
1962
+ END;
1963
+ $BODY$;
1964
+
1965
+ ALTER FUNCTION public.delete_stsentity(
1966
+ character varying,
1967
+ character varying,
1968
+ bigint,
1969
+ character varying,
1970
+ bigint
1971
+ )
1972
+ OWNER TO postgres;
1973
+
1974
+
1975
+ -- =============================================================================
1976
+ -- delete_stsentity_latest
1977
+ -- =============================================================================
1978
+ --
1979
+ -- Logically deletes the current active entity under the current active resource
1980
+ -- without requiring the caller to provide either version number.
1981
+ CREATE OR REPLACE FUNCTION public.delete_stsentity_latest(
1982
+ _dbactionuser character varying,
1983
+ _resname character varying,
1984
+ _entname character varying
1985
+ )
1986
+ RETURNS SETOF public.stsentity
1987
+ LANGUAGE plpgsql
1988
+ COST 100
1989
+ VOLATILE
1990
+ ROWS 1
1991
+
1992
+ AS $BODY$
1993
+ DECLARE
1994
+ __now timestamptz := now();
1995
+ __resoid bigint;
1996
+ __entoid bigint;
1997
+ __entvnum bigint;
1998
+ __inserted_oid bigint;
1999
+ BEGIN
2000
+
2001
+ -- Resolve and lock the current active resource.
2002
+ BEGIN
2003
+ SELECT
2004
+ r.oid
2005
+ INTO STRICT
2006
+ __resoid
2007
+ FROM public.stsresource r
2008
+ WHERE r.resname = _resname
2009
+ AND r.validto IS NULL
2010
+ AND r.dbaction != 3
2011
+ FOR UPDATE;
2012
+
2013
+ EXCEPTION
2014
+
2015
+ WHEN no_data_found THEN
2016
+ RAISE
2017
+ 'Resource % not found, not current, or deleted',
2018
+ _resname
2019
+ USING ERRCODE = 'no_data_found';
2020
+
2021
+ WHEN too_many_rows THEN
2022
+ RAISE
2023
+ 'Too many current stsresource records for resource %',
2024
+ _resname
2025
+ USING ERRCODE = 'cardinality_violation';
2026
+
2027
+ END;
2028
+
2029
+
2030
+ -- Resolve and lock the current active entity and its version.
2031
+ BEGIN
2032
+ SELECT
2033
+ e.oid,
2034
+ e.vnum
2035
+ INTO STRICT
2036
+ __entoid,
2037
+ __entvnum
2038
+ FROM public.stsentity e
2039
+ WHERE e.resourceoid = __resoid
2040
+ AND e.entname = _entname
2041
+ AND e.validto IS NULL
2042
+ AND e.dbaction != 3
2043
+ FOR UPDATE;
2044
+
2045
+ EXCEPTION
2046
+
2047
+ WHEN no_data_found THEN
2048
+ RAISE
2049
+ 'Entity % not found, not current, or deleted within resource %',
2050
+ _entname,
2051
+ _resname
2052
+ USING ERRCODE = 'no_data_found';
2053
+
2054
+ WHEN too_many_rows THEN
2055
+ RAISE
2056
+ 'Too many current stsentity records for entity % within resource %',
2057
+ _entname,
2058
+ _resname
2059
+ USING ERRCODE = 'cardinality_violation';
2060
+
2061
+ END;
2062
+
2063
+
2064
+ -- Close the previous active entity version.
2065
+ UPDATE public.stsentity
2066
+ SET validto = __now
2067
+ WHERE oid = __entoid;
2068
+
2069
+
2070
+ -- Create the delete tombstone.
2071
+ INSERT INTO public.stsentity(
2072
+ resourceoid,
2073
+ entname,
2074
+ entvalue,
2075
+ entvalue_jsonb,
2076
+ vnum,
2077
+ dbaction,
2078
+ dbactionuser,
2079
+ validfrom
2080
+ )
2081
+ SELECT
2082
+ __resoid,
2083
+ e.entname,
2084
+ e.entvalue,
2085
+ e.entvalue_jsonb,
2086
+ __entvnum + 1,
2087
+ 3,
2088
+ _dbactionuser,
2089
+ __now
2090
+ FROM public.stsentity e
2091
+ WHERE e.oid = __entoid
2092
+ RETURNING oid
2093
+ INTO __inserted_oid;
2094
+
2095
+
2096
+ -- Return the delete tombstone.
2097
+ RETURN QUERY
2098
+ SELECT *
2099
+ FROM public.stsentity
2100
+ WHERE oid = __inserted_oid;
2101
+
2102
+ END;
2103
+ $BODY$;
2104
+
2105
+ ALTER FUNCTION public.delete_stsentity_latest(
2106
+ character varying,
2107
+ character varying,
2108
+ character varying
2109
+ )
2110
+ OWNER TO postgres;
2111
+
2112
+
2113
+ -- =============================================================================
2114
+ -- Count functions
2115
+ -- =============================================================================
2116
+
2117
+
2118
+ -- =============================================================================
2119
+ -- count_stsresource
2120
+ -- =============================================================================
2121
+ --
2122
+ -- Counts stsresource rows.
2123
+ --
2124
+ -- _valid_records = false
2125
+ -- Count every physical stsresource row, including all historical versions
2126
+ -- and delete tombstones.
2127
+ --
2128
+ -- _valid_records = true
2129
+ -- Count only current ACTIVE resource concepts.
2130
+ --
2131
+ -- A delete tombstone has validto IS NULL but dbaction = 3, so both
2132
+ -- predicates are required. Testing validto alone would incorrectly count
2133
+ -- logically deleted resources as valid.
2134
+ --
2135
+ -- This function performs no writes and is therefore STABLE rather than
2136
+ -- VOLATILE.
2137
+ CREATE OR REPLACE FUNCTION public.count_stsresource(
2138
+ _valid_records boolean DEFAULT false
2139
+ )
2140
+ RETURNS TABLE (count bigint)
2141
+ LANGUAGE plpgsql
2142
+ COST 100
2143
+ STABLE
2144
+ ROWS 1
2145
+
2146
+ AS $BODY$
2147
+ BEGIN
2148
+
2149
+ IF _valid_records THEN
2150
+
2151
+ RETURN QUERY
2152
+ SELECT
2153
+ COUNT(*)::bigint
2154
+ FROM public.stsresource r
2155
+ WHERE r.validto IS NULL
2156
+ AND r.dbaction != 3;
2157
+
2158
+ ELSE
2159
+
2160
+ RETURN QUERY
2161
+ SELECT
2162
+ COUNT(*)::bigint
2163
+ FROM public.stsresource r;
2164
+
2165
+ END IF;
2166
+
2167
+ END;
2168
+ $BODY$;
2169
+
2170
+ ALTER FUNCTION public.count_stsresource(boolean)
2171
+ OWNER TO postgres;
2172
+
2173
+
2174
+ -- =============================================================================
2175
+ -- End of stsrest01 generic resource/entity schema
2176
+ -- =============================================================================