-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path_YDBAIM.m
1621 lines (1537 loc) · 96.3 KB
/
_YDBAIM.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; Copyright (c) 2021-2024 YottaDB LLC and/or its subsidiaries. ;
; All rights reserved. ;
; ;
; This source code contains the intellectual property ;
; of its copyright holder(s), and is made available ;
; under a license. If you do not know the terms of ;
; the license, please stop and do not read further. ;
; ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Application Independent Metadata (AIM)
;
; Compute and maintain application independent metadata:
; - Cross References
; - Optionally, statistics on the count of each value and the number of
; distinct values
;
; A design point is that the triggers used to maintain Consistency between
; application global variable nodes and cross references are the most critical
; code to optimize, since that code will be executed on *every single update*
; to a cross referenced global variable. Other code should be reasonably
; efficient, but is not as critical.
;
; Cross References
;
; Consider a global variable whose first subscript is the year a US
; President assumed office and the second subscript is the year that President
; left office, with values such as:
;
; ^USPresidents(1797,1801)="John||Adams" and
; ^USPresidents(1835,1839)="John|Quincy|Adams"
;
; - A call such as $$XREFDATA^%YDBAIM("^USPresidents",2,"|",3) would cross
; reference the third piece of node values (last names) and with the cross
; reference global having values such as
; ^%ydbAIMDf1x7fSMuGT4HtAEXAx0g65(3,"Adams",1797,1801)="",
; ^%ydbAIMDf1x7fSMuGT4HtAEXAx0g65(3,"Adams",1835,1839)="", and many others
; including ^%ydbAIMDf1x7fSMuGT4HtAEXAx0g65(3,"Obama",2009,2017)="".
;
; - Suppose only Presidents who left office in the 19th century should be
; cross referenced. A local variable node such as cent19(2)="1801:1899" can
; be created, and passed by reference, and
; $$XREFDATA^%YDBAIM("^USPresidents",.cent19,"|",3) would produce the two
; cross references as the above, but in a different global variable name
; since the trigger signatures (see below for an explanation) would be
; different. Unlike the first cross reference, this would not cross
; reference Barack Obama who assumed office in 2009 and left in 2017.
;
; - Suppose only Presidents who assumed office in the 19th century should be
; cross referenced, a local variable cent19 would instead have the node
; cent19(1)="1801|1899" to indicate that only first subscripts should be
; cross referenced if they are in the 19th century, but the local variable
; root node cent19=2 should be set to indicate that two subscripts should be
; cross referenced. In this case, the call
; $$XREFDATA^%YDBAIMD("^USPresidents",.cent19,"|",3) would generate a cross
; reference that includes John Quincy Adams, but not John Adams who assumed
; office in 1797, which is in the 18th century.
;
; To cross reference all three names, a call such as
; $$XREFDATA^%YDBAIM("^USPresidents",2,"|","1:3") would generate the following
; cross references for the two President Adams:
; ^%ydbAIMDf1x7fSMuGT4HtAEXAx0g65(1,"John",1797,1801)=""
; ^%ydbAIMDf1x7fSMuGT4HtAEXAx0g65(1,"John",1835,1839)=""
; ^%ydbAIMDf1x7fSMuGT4HtAEXAx0g65(2,"",1797,1801)=""
; ^%ydbAIMDf1x7fSMuGT4HtAEXAx0g65(2,"Quincy",1835,1839)=""
; ^%ydbAIMDf1x7fSMuGT4HtAEXAx0g65(3,"Adams",1797,1801)=""
; ^%ydbAIMDf1x7fSMuGT4HtAEXAx0g65(3,"Adams",1835,1839)=""
;
; Since the first President Adams record does not include a middle name, the
; corresponding record has an empty string ("") subscript. Any region to which
; ^%ydbAIMD* global variables are mapped should have NULL_SUBSCRIPTS set to
; ALWAYS. Since the subscripts will include pieces of global nodes, or even
; entire global nodes, it would be prudent to set YottaDB's maximum key size
; (1019 bytes) for that region.
;
; Note: subscript specifications which are not canonical numbers should be
; quoted. So to cross reference the first piece with "|" as the separator of
; ^%ydbocto("tables","pg_catalog","pg_attribute",*):
; YDB>set sub=4,sub(1)="""tables""",sub(2)="""pg_catalog""",sub(3)="""pg_attribute"""
;
; YDB>set xref=$$XREFDATA^%YDBAIM("^%ydbocto",.sub,"|",1)
; to create the cross reference:
; YDB>write xref
; ^%ydbAIMDvjlGbD84bQ5u5hXGOtIe37
; YDB>
; Setting a value now creates the cross reference:
; YDB>set ^%ydbocto("tables","pg_catalog","pg_attribute",100)="ABC|DEF"
;
; YDB>write $query(@xref@(1,"ABC",""))
; ^%ydbAIMDvjlGbD84bQ5u5hXGOtIe37(1,"ABC",100)
; YDB>YDB
;
; Statistics
;
; The optional parameter stat can be used to instruct AIM that the application
; wishes to compute and maintain statistics. There are two types of statistcs
; (the default, stat=0, is cross references only and no statistics):
;
; - stat=1: statistics on the count of each value. Thus the call
; $$XREFDATA^%YDBAIM("^USPresidents",2,"|","1:3",,,,1) would compute and
; maintain nodes such as ^%ydbAIMDf1x7fSMuGT4HtAEXAx0g65(-1,"John")=4 to
; indicate that "John" appears as the first piece four times (the first)
; subscript is the negative of the piece number.
; - stat=2: in addition to the count of each value, also counts the number
; of different values, and also a total count of the number of values
; maintained. Thus, the call
; $$XREFDATA^%YDBAIM("^USPresidents",2,"|","1:3",,,,2) would compute and
; maintain nodes such as ^%ydbAIMDf1x7fSMuGT4HtAEXAx0g65(-3)=39 to
; indicate that there are 39 distinct last names and
; ^%ydbAIMDf1x7fSMuGT4HtAEXAx0g65(11)=135 to indicate that there are 135
; nodes maintained (as of 2021, the 45 former US Presidents times 3 names
; for each ex-President).
;
; Application Schema Type
;
; The above description describes the most straightforward type of application
; schema, where all application nodes with metadata managed by AIM have the
; number of subscripts. However, the freedom that global variables provide to
; application designers means that different applications design their schemas
; in different ways.
;
; The default type parameter in the call to XREFDATA() creates metadata for
; straightforward case above. Adding schemas consists of:
;
; - Creating new trigger templates as needed, and creating triggers from new
; and existing trigger templates.
; - Adding logic in xrefdata() to create the initial metadata.
;
; With values of 1 or 3 for type, AIM creates and manages metadata for a schema
; used by the VistA Fileman software
; (https://www.va.gov/vdl/application.asp?appid=5):
;
; - the last subscript specification specifies a constant;
; - a node with that constant subscript does not exist; and
; - other nodes exist at the level of that constant subscript, i.e., there is
; at least one other node whose subscripts are identical except for that
; constant last subscript;
;
; AIM creates and maintains metadata nodes for the requested pieces using the
; empty string ("") as the last subscript instead of the specified constant.
; If omitfix=1 (the default), the metadata node omits that last empty string
; subscript. The difference between a type of 1 and a type of 3 is that the latter
; allows the application to provide a transformation function (see below).
;
; Metadata for nodes with that constant subscript that do exist have the same
; schema as metadata for the default type ("").
;
; Metadata Transformations
;
; There are cases where it is required for the cross reference to have a
; different value than the application data:
;
; - Data ordering: M default collation collates the empty string first, then
; canonic numbers and then other strings. This can lead to unintuitive
; ordering where an Octo SQL VARCHAR column of zip codes would expect 02067
; to be sorted before 14534 whereas M would sort 14534 first as it is a
; canonic number, unlike 02067.
;
; - Multiple data representations: an application may store a timestamp as
; "2024-02-21T13:31:48.05098021+07:00" in one node and as "2024-02-21
; 13:31:48.05098021+07:00" in another. Since both represent the same
; timestamp, they should ideally have the same metadata.
;
; The former has a specific solution; the latter a general solution. As the
; latter requirement came after the former requirement, the parameters are
; overloaded, and it would not be unreasonable to consider the solution hacky.
;
; - With a type parameter of 0 or 1, and a nonzero length force parameter, AIM
; prefixes each data item with a hash ("#"), forcing it to be a string. An
; application using $ORDER() to step through a cross reference built with a
; nonzero length force parameter will need to strip that hash prefix, e.g.,
; if x is the variable used for the $ORDER() calls, the actual subscripts are
; $ZEXTRACT(x,2,$ZLENGTH(x)).
;
; - With a type parameter of 2 or 3, a nonzero length force parameter is treated
; as a transformation function e.g., "$$ABC^DEF()". Metadata uses the value
; returned by the transformation function as the value to cross
; reference. When the function is called at runtime by the trigger, its first
; parameter is the actual node or piece value, e.g.,
; $$ABC^DEF("2024-02-21T13:31:48.05098021+07:00") would be the actual value
; using the example of a timestamp at
; https://gitlab.com/YottaDB/DBMS/YDBOcto/-/issues/1050. If the function
; requires additional parameters, they can be specified as comma separated
; values for the second and subsequent parameters, e.g.,
; "$$ABC^DEF(,1,""two"")". Additional parameters can only be constants or
; global variable references, as local variables cannot be passed to
; triggers. Application code that needs to pass local variable values to the
; transformation function should use $ZTWORMHOLE.
;
; Note that $DATA(@name@(SUB))#10 constructs where SUB is an integer occur in
; the code because these root nodes are metadata about the metadata and
; subtrees then contain the stored metadata of application globals. Also
; processes updating application globals using triggers and concurrent
; executions of metadata have prompted these $DATA() calls. It is likely that
; analysis of the code will find that some of them are not needed and can be
; removed without affecting correctness.
;
; The number in the comment after the %YDBAIM label is a version number of the
; metadata format.
%YDBAIM;1
; Top level entry not supported
new $etrap,io do etrap
set $ecode=",U255," ; top level entry not supported
quit ; should get here only in direct mode
; Set error trap. The first action the error trap does is set a failsafe error
; trap (e.g., if $zroutines is not correct). Thereafter, it jumps to the actual
; error trap to print an error message and terminate, with a return code.
;
; Note that all external entry points:
; - NEW $ETRAP and DO etrap if $ETRAP is the default error handler. This allows
; them to report the error messages for %YDBAIM errors, instead of just
; reporting that $ECODE was assigned a non-empty value, which the default
; error trap will report. If an application sets an error trap ($ETRAP or
; $ETRAP) that will be used.
; - Capture existing locks in currlck so that the error trap can release
; locks acquired by %YDBAIM. In non-error code paths, acquired locks are
; released without the need to refer to captured locks.
etrap
set $etrap="set io=$io,$etrap=""open """"/proc/self/fd/2"""" use """"/proc/self/fd/2"""" write $zstatus,! zshow """"*"""" zhalt $piece($zstatus,"""","""",1)"" goto err^"_$text(+0)
quit
err ; Primary Error Handler
; -----------------------------------------------------------------
; This is where control reaches when any error is encountered inside AIM.
; We do AIM-specific cleanup here and then switch $etrap to a non-AIM default handler that rethrows
; the error one caller frame at a time until we unwind to a non-AIM caller frame that has $etrap set
; at which point it can handle the error accordingly.
; -----------------------------------------------------------------
new errcode,errtxt
set errcode=$zpiece($ecode,",",2),errtxt=$text(@errcode)
; Check for AIM-specific errors (in that case "errtxt" will be non-empty).
if $zlength(errtxt) do
. new xstr
. ; This is an AIM specific error. Extract error text with potential unfilled parameter values.
. ; Run "xecute" on that string to fill it with actual values.
. set xstr="set errtxt="_$zpiece(errtxt,";",2,$zlength(errtxt,";")) xecute xstr
. set $zstatus=$zpiece($zstatus,",",1,2)_","_$text(+0)_errtxt
; Rollback the transaction to $TLEVEL at entry into the first caller AIM frame.
trollback:$tlevel&$data(tlevel)&(tlevel<$tlevel) tlevel
; Undo ztrigger_output changes if any done
view:$data(ztout) "ztrigger_output":ztout
; Release locks obtained inside AIM
do unsnaplck(.currlck)
; Terminate any JOB'd xrefdata() processes
if $data(xrefproc(1)),'$zsigproc(xrefproc(1),"term") zkill xrefproc(1)
if $data(xrefproc(-1)),'$zsigproc(xrefproc(-1),"term") zkill xrefproc(-1)
; Now that primary error handling is done, switch to different handler to rethrow error in caller AIM frames.
; The rethrow will cause a different $etrap to be invoked in the first non-AIM caller frame (because AIM
; did a "new $etrap" at entry).
set $etrap="quit:$quit """" quit"
use io
quit:$quit "" quit
; List metadata for a cross reference, all cross references for a global
; variable, or all cross references
;
; Usage: DO LSXREFDATA^%YDBAIM(lvn[,gbl])
; Parameters
; - lvn is a local variable passed by reference. In that local variable, the
; function describes all cross references as follows:
; - The first subscript is the cross refence global variable name, e.g.,
; "^%ydbAIMDgBPWsnL76HLyVnlvsrvE19". The value of that node (i.e., with a
; first subscript and no second subscript) is the application global
; variable name, e.g., "^xyz".
; - Nodes with positive integer second subscripts have metadata about the
; metadata. These are described below with XREFDATA().
; Nodes of lvn other than those corresponding to reported cross references
; remain unaltered.
; - gbl is a global variable name. There are three cases:
; - It is an application global variable name, e.g., "^USPresidents". In lvn,
; as described above, the function returns all cross references for that
; global variable.
; - It starts with "^%ydbAIMD". In lvn, the function returns information about
; the specified cross reference.
; - It is omitted or the empty string (""). In lvn, the function returns
; information about all cross references.
LSXREFDATA(lvn,gbl)
new $etrap,io do etrap
new currlck,tlevel,xrefvar
set tlevel=$tlevel
do snaplck(.currlck)
if '$zlength($get(gbl)) do
. set gbl="" for set gbl=$order(^%ydbAIMDxref(gbl)) quit:'$zlength(gbl) do
. . set xrefvar="" for set xrefvar=$order(^%ydbAIMDxref(gbl,xrefvar)) quit:'$zlength(xrefvar) do lsxrefdata(.lvn,xrefvar)
else if gbl'?1"^%ydbAIMD".E do
. set xrefvar="" for set xrefvar=$order(^%ydbAIMDxref(gbl,xrefvar)) quit:'$zlength(xrefvar) do lsxrefdata(.lvn,xrefvar)
else do lsxrefdata(.lvn,gbl)
quit
; Remove triggers and cross references for a specified global, or all globals.
; The parameters for UNXREFDATA() mirror those of XREFDATA() to simplify calling
; it to remove triggers and cross references, even though some parameters are
; not required and are therefore ignored.
;
; Usage: DO UNXREFDATA^%YDBAIM(gbl,xsub,sep,pnum,nmonly,zpiece,omitfix,stat,type,force)
; Quick summary:
; - UNXREFDATA() deletes all metadata
; - UNXREFDATA(gbl) where gbl is an application global name deletes all AIM
; metadata for that application global.
; - UNXREFDATA(aimgbl) where aimgbl is an AIM metadata global variable remove
; that metadata
; Triggers associated with maintaining any requested metadata are removed when
; the metadata is removed.
;
; Parameters:
; - gbl is the global variable name, e.g., "^ABC" for which the specified
; metadata and triggers are to be removed. If omitted, all metadata and
; associated triggers for xrefs are removed. If gbl is an AIM global, then
; that metadata and its triggers are removed.
; - xsub is a specification of the subscripts in the cross reference to be
; removed. There are four cases:
; - xsub is unspecified or its root node is zero and there is no subtree. In
; this case, all cross references for the specified global variable are
; removed. In all three cases following, as the subscript specification is
; part of the "signature" of a cross reference, the subscript specification
; of xsub must match that of the trigger being removed.
; - xsub has a positive integer value at the root, and no subtrees ($DATA() is
; 1): The value specifies the level (number of subscripts) of the global
; variable for whch the cross reference is to be removed, with all
; subscripts at each level in the signature of the cross reference. In this
; case, the actual parameter can be a literal or a variable passed by
; value. In both the following cases it must be passed by reference.
; - xsub has no value at the root, but positive integer subscripts (1), (2),
; (3), etc. ($DATA() is 10): The subscripts of the local variable specify
; the values of the global variable subscript in the signature cross
; referenced, using the same syntax as trigvn field of trigger definitions.
; The last subscript defines the level of the global variable to be cross
; referenced. Any omitted intervening subscript (e.g., if the local variable
; has nodes (1) and (3) but not (2)), means that all subscripts at that level
; are in the cross reference signature.
; - xsub has both a value at the root, as well as positive integer subscripts
; (($DATA() is 11): This is similar to the previous case, except that
; should the number at the root exceed the last subscript, the value at the
; root is the level of the cross reference signature, with all global
; variables to be included at levels beyond those of the last local
; variable subscript. A value at the root smaller than the last subscript
; is ignored.
; Other cases (e.g., non integer subscripts of xsub) raise errors.
; - sep is the piece separator for values at that node; if unspecified or the
; empty string, the cross reference signature is entire node values.
; - pnum exists to allow the parameters of UNXREFDATA() to match those of
; XREFDATA() and is ignored. Note that it is not possible to remove the cross
; reference of one piece of a node.
; - nmonly exists to allow the parameters of UNXREFDATA() to match those of
; XREFDATA() and is ignored.
; - zpiece, if 1 means that $ZPIECE() was used as the piece separator instead
; of $PIECE(); this is part of the trigger signature.
; - omitfix and stat exist only to allow the parameters of UNXREFDATA()
; to match those of XREFDATA() and are ignored.
; - if type is used in the XREFDATA() call, it should be passed here.
; - if force is used in the XREFDATA() call, it should be passed here.
UNXREFDATA(gbl,xsub,sep,pnum,nmonly,zpiece,omitfix,stat,type,force)
new $etrap,io do etrap
new currlck,i,nsubs,tlevel,xrefvar
set tlevel=$tlevel
; Ensure type & force have values and convert 0 to "" for backward compatibility
if '$data(type)!(0=type) set type=""
if '$data(force)!(0=force) set force=""
do snaplck(.currlck)
set gbl=$get(gbl)
if gbl?1"^%ydbAIMD".22AN do
. tstart (gbl,xrefvar):transactionid="batch"
. if $data(@gbl) set xrefvar=gbl,gbl=@xrefvar do unxrefdata(xrefvar)
. tcommit
else if '$zlength(gbl) do ; remove all xrefs
. lock +^%ydbAIMD
. set gbl="" for set gbl=$order(^%ydbAIMDxref(gbl)) quit:'$zlength(gbl) do
. . set xrefvar="" for set xrefvar=$order(^%ydbAIMDxref(gbl,xrefvar)) quit:'$zlength(xrefvar) do unxrefdata(xrefvar)
. lock -^%ydbAIMD
else do
. ; Xrefs are only supported for global variables
. set:(gbl'?1"^"1(1"%",1AN).AN)!(32<$zlength(gbl)) $ecode=",U252,"
. set nsubs=$get(xsub,0)
. ; If constraints specified for subscripts, ensure all refer to
. ; subscripts that are integers in the range 1 through 31
. do:$data(xsub)\10
. . set i=$order(xsub(""))
. . set:1>i!(i\1'=i)!(31<i) $ecode=",U247,"
. . for set i=$order(xsub(i)) quit:'$zlength(i) set:i\1'=i!(31<i) $ecode=",U247,"
. . set i=$order(xsub(""),-1)
. . set:i>nsubs nsubs=i
. set:31<nsubs $ecode=",U247,"
. if 'nsubs do ; remove all xrefs for gbl
. . lock +^%ydbAIMD(gbl)
. . set xrefvar="" for set xrefvar=$order(^%ydbAIMDxref(gbl,xrefvar)) quit:'$zlength(xrefvar) do unxrefdata(xrefvar)
. . lock -^%ydbAIMD(gbl)
. else do unxrefdata($$XREFDATA(gbl,.xsub,$get(sep),,1,$get(zpiece),$get(omitfix,1),$get(stat,0),type,force))
quit:$quit "" quit
; Create triggers to maintain cross references and compute cross references
; for a global variable at a specified subscript level. Concurrent execution OK.
;
; Usage: $$XREFDATA^%YDBAIM(gbl,xsub,sep,pnum,nmonly,zpiece,omitfix,stat,type,force)
; Parameters:
; - gbl is the global variable name, e.g., "^ABC"
; - xsub is a specification of the subscripts to be cross referenced. There are
; three cases:
; - xsub has a positive integer value at the root, and no subtrees (i.e.,
; $DATA() is 1): The value specifies the level (number of subscripts) of
; the global variable for whch the cross reference is to be created, with
; all subscripts at each level to be included in the cross reference. In
; this case, the actual parameter can be a literal or a variable passed by
; value. In other cases it must be passed by reference.
; - xsub has no value at the root, but positive integer subscripts (1), (2),
; (3), etc. (i.e., $DATA() is 10): The subscripts of the local variable
; specify the values of the global variable subscript to be cross
; referenced, using the same syntax as trigvn field of trigger
; definitions. The last subscript defines the level of the global variable
; to be cross referenced. Any omitted intervening subscript (e.g., if the
; local variable has nodes (1) and (3) but not (2)), means that all
; subscripts at that level should be included in the cross reference.
; - xsub has both a value at the root, as well as positive integer subscripts
; (i.e., ($DATA() is 11): This is similar to the previous case, except that
; if the value at the root exceeds the last subscript, that is the level of
; the global variable to be cross referenced. For example, if the local
; variable has nodes (1) and (3) but the value at the root is 5, five
; subscripts of the global variable will be cross referenced. A value at
; the root smaller than the last subscript is ignored, so with the
; subscripts above and a value of 2 at the root, three subscripts will be
; cross referenced.
; Other cases (e.g., non integer subscripts of xsub) raise errors.
; - sep is the piece separator for values at that node; if unspecified or the
; empty string, the cross reference is for entire node values.
; - pnum is a semi-colon separated list of integer piece numbers for which cross
; references should exist; ignored for xrefs of entire node values effectively
; a no-op if pieces specified are already cross-referenced
; - nmonly, if 1, means just return the xref global variable name but don't set
; triggers or compute xrefs
; - zpiece, if 1 means that $ZPIECE() should be used as the piece separator
; instead of $PIECE(); AIM can have cross references for the same nodes with
; both options; the cross references are in different global variables
; - omitfix, if 1 instructs XREFDATA() to omit from the subscripts of the cross
; reference any subscripts of the application global that are fixed constants
; because the code to traverse the application global using the cross
; reference will include those known fixed subscripts when making the access.
; If not specified, omitfix defaults to 1.
; - stat if 1 or 2 says the metadata should include statistics, as described
; above under "Statistics".
; - type and force, both defaulting to the empty string, together specify the
; application schema and metadata type, as follows:
;
; type
; | 0 | 1 | 2 | 3 |
; --+------------------+------------------+------------------+------------------+
; 0 | normal schema; | Fileman schema; | |
; | normal data | normal data | |
; | ordering | ordering | |
; --+------------------+------------------+ Error |
; | normal schema; | Fileman schema; | |
; force 1 | forced string | forced string | |
; | ordering | ordering | |
; --+------------------+------------------+------------------+------------------+
; | | normal schema; | Fileman schema; |
; string | Error | transform func. | transform func. |
; | | ordering | ordering |
; --+------------------+------------------+------------------+------------------+
;
; Return value: name of global variable with cross reference e.g.,
; "^%ydbAIMDZzUmfwxt80MHPiWLZNtq4". The subscripts of cross reference variables
; are:
; - (pnum,value,sub[,sub]) where pnum is a positive integer for cross references
; of pieces of nodes; and
; - (0,value,sub[,sub]) for cross references of entire nodes.
;
; The function is coded so that the function tries as efficiently as possible to
; determine whether the cross reference already exists. This means that it is
; reasonable to just call the function to ensure that the cross reference
; exists, without the caller first checking whether it already exists. Since
; the function uses $PIECE(), sep is interpreted according to whether the
; process is in M mode or UTF-8 mode. Cross references are in global variables
; such as ^%ydbAIMDZzUmfwxt80MHPiWLZNtq4, which are derived from a "trigger
; signature" derived from:
; - the name of the global variable being cross referenced
; - the specification to match subscripts at each level of the global variable
; - if a piece separator is specified, then also:
; - the piece separator
; - Whether a piece separator is ASCII or not (digit 0 if yes)
; - if zpiece is specified, then the digit 1
; - if the separator is not ASCII and zpiece is not specified, $zchset
; - the type of the application schema
; - whether string collation should be forced for subscripts
;
; The global variable name of the cross reference is derived by prefixing
; "^%ydbAIMD" to a 128-bit MurMurHash (a non-cryptographic hash with excellent
; statistical properties) rendered into a 22-character alphanumeric string.
; The approach is to first create triggers to maintain cross references, and
; then scan the global variable to generate cross references. This way, any
; global variable changes will have cross references maintained by the triggers,
; and when returning to caller, the cross reference is complete, and will remain
; Consistent thereafter. ^%ydbAIMD* global variables should be mapped to a
; region with NULL_SUBSCRIPTS set to EXISTING or ALWAYS.
; Just as cross references are metadata for application global variables,
; metadata for the cross reference global variables are stored in the cross
; reference global variables as described below.
; - Since the first subscript of any cross reference node is a non-negative
; integer, 0 for a cross reference of an entire node, or a piece number.
; - Nodes where the first subscript is negative integer are used for
; statistics.
; - Nodes where the first subscript is a non-numeric string are reserved
; for use by %YDBAIM. For example, they are used by parallel JOBs that scan
; globals for initial metadata computation.
; - The root node is the application global variable name; subscripted nodes are:
; - (0) space separated $zut, $job, $zyrelease, metadata format version number
; - (1) number of subscripts of the application global variable xref'd
; - (2) piece separator, "" for an xref of entire nodes
; - (3) & (4) piece numbers, in the form of a bit-map like string,
; prefixed with "#" to prevent numeric conversion, e.g., the value for
; pieces 2, 4 and 5 would be "#01011", "" for an xref of the entire node.
; (3) identifies the piece numbers for which cross referencing is complete
; whereas (4) identifies those for which triggers exist. If they are not
; equal, it means that a process created triggers, but is still working on
; cross referencing existing global nodes. It is also possible that the
; process terminated, or was terminated, before completing its work.
; - (5) 1 means $ZPIECE() was used for pieces; the default of "" is $PIECE()
; - (6) SET trigger for this cross reference
; - (7) KILL trigger for this cross reference; see also comment for (12) below
; - (8) ZKILL trigger for this cross reference
; - (9) 1 means that omitting fixed subscripts was requested, whether or not
; any subscripts were actually omitted
; - (10) if 1 or 2 means that statistics are maintained, as specified by the
; stat parameter
; - (11) if stat is 2, this contains the total number of nodes being tracked
; - (12) & up - triggers for KILLs of higher level nodes. Until YDB is enhanced
; to invoke at lower levels of the tree when a higher level node is KILLed
; (YDB#659) YDBAIM creates triggers documented in nodes (12) and up for levels
; of the tree above the one for which YDBAIM maintaine metadata. The trigger
; described in (7) also does a recursive traversal of the tree below. Once
; the YDB#659 enhancement is implemented, the triggers in nodes (12) and up
; are not required, and the KILL trigger in (7) can be simplified, and
; potentially merged with the ZKILL trigger in (8).
; - subscripts above those record type-specific triggers
;
; Nodes of ^%ydbAIMDxref(gbl,aimgbl) are metadata on metadata, where gbl is an
; application global and aimgbl is the AIM global.
;
; Note that for historical reasons, variables whose names suggest they are
; relevant just to type=1, e.g., type1last, are also relevant to type=3,
; i.e., to Fileman globals.
XREFDATA(gbl,xsub,sep,pnum,nmonly,zpiece,omitfix,stat,type,force)
new $etrap,io do etrap
new altlastsub,altsub,asciisep,constlist,currlck,fullsub,fullsubprnt
new fulltrigsub,gblind,gblindtype1,i,j,killtrg,lastfullsub
new lastsub,lastsubind,lastvarsub,locxsub,modflag,name,nameind,newpnum
new newpstr,pieces,nsubs,nullsub,omitflag,oldpstr,stacklvl1,sub,subary
new suffix,tlevel,tmp,trigdel,trigdelx,type1last,trigprefix
new trigset,trigsub,ttprfx,valcntind,xfnp1,xfnp2,xfntest,xrefind
new xrefindtype1,z,zintrptsav,zlsep,ztout,zyintrsig
set stacklvl1=$stack ; required by premature termination
set tlevel=$tlevel ; required by error trap to rollback/unwind
set zintrptsav=$zinterrupt
do snaplck(.currlck)
set:'$data(gbl) $ecode=",U252,"
; Extended references are not supported
set:""'=$qsubscript(gbl,-1) $ecode=",U254,"
; Xrefs are only supported for global variables other than AIM global variables
set:gbl'?1"^"1(1"%",1AN).AN!(32<$zlength(gbl)) $ecode=",U252,"
set:gbl?1"^%ydbAIMD".AN $ecode=",U243,"
set nsubs=$get(xsub,0) ; Ensure Number of subcripts has a value
; Ensure type & force have values and convert 0 to "" for backward compatibility
if '$data(type)!(0=type) set type=""
if '$data(force)!(0=force) set force=""
; While the caller is expected to pass both type and force if specified as 1
; this raises the possibility of ambiguity in the AIM variable name if one of
; them defaults to and the other does not (both add 1 to the suffix). Therefore
; for calculating the suffix, force is negated, so that type_force has the
; following values:
; - "" both default
; - "1" type specified, force defaults
; - "-1" type defaults; force specified
; - "1-1" both specified
; If type>1, there is no ambiguity
if 1<type do ; Superficial syntax check of transformation function
. set xfnp1=$zpiece(force,"(",1)_"(",xfnp2=$zpiece(force,"(",2,$zlength(force,"("))
. set:(xfnp1'?1(1"$$"0.1"%".an1"^"0.1"%",1"$").an1"(")!(xfnp2'?1(1")",1","1.e1")")) $ecode=",U230,"
. set xfntest=$$^%ZMVALID("set zyx="_xfnp1_"zyx"_xfnp2),$ecode="" ; compile & discard errors
. set:$zlength(xfntest) $ecode=",U230,"
else do
. set:($zlength(type)&(1'=type))!($zlength(force)&(1'=force)) $ecode=",U237,"
. set:$zlength(force) force=-force
; If constraints specified for subscripts, ensure all refer to
; subscripts that are integers in the range 1 through 31
do:$data(xsub)\10
. set i="" for set i=$order(xsub(i)) quit:'$zlength(i) do:$data(xsub(i))#10
. . set:1>i!(i\1'=i)!(31<i) $ecode=",U247,"
. . set tmp=xsub(i),locxsub(i)=$select(":"=tmp:"*",1:tmp)
. . set constlist(i)=$$chktrgspec(.locxsub,i)
. set i=$order(locxsub(""),-1)
. set:i>nsubs nsubs=i
set:$select(type#2:2,1:1)>nsubs $ecode=",U253," ; Fileman schema requires at least 2 subscripts
set:nsubs\1'=nsubs!(31<nsubs) $ecode=",U247,"
for i=1:1:nsubs set:'$data(locxsub(i)) locxsub(i)="*",constlist(i)=0
; Ensure subscript specifications for Fileman global variables end in
; a constant. The actual trigger subscript specification for the last
; subscript matches all subscripts, as metadata varies depending on
; whether or not there are other nodes at that last level, if no node
; exists with the constant subscript.
set:(type#2)&'constlist(nsubs) $ecode=",U236,"
; Derive subscript specification for trigger definitions from parameters
; and build string from which to derive xref variable name
set omitfix=+$get(omitfix,1),omitflag=0
set (altsub,fullsub,fulltrigsub,name,sub,trigsub)=""
for i=1:1:nsubs do
. set name=name_locxsub(i)
. set lastfullsub="sub"_i,trigsub=trigsub_lastfullsub_"=",fulltrigsub=fulltrigsub_lastfullsub_","
. if constlist(i)&(type#2) do
. . set fullsub=fullsub_locxsub(i)_","
. . set trigsub=trigsub_$select(i=nsubs:"*",1:locxsub(i))_","
. else do
. . set fullsub=fullsub_lastfullsub_","
. . set trigsub=trigsub_locxsub(i)_","
. if omitfix&constlist(i) set omitflag=1
. else set lastsub=lastfullsub,sub=sub_lastfullsub_","
; Ensure at least one subscript selected for cross reference global
; Remove trailing commas from building subscript lists
set $zextract(sub,$zlength(sub))=""
set $zextract(fullsub,$zlength(fullsub))=""
set $zextract(fulltrigsub,$zlength(fulltrigsub))=""
set $zextract(trigsub,$zlength(trigsub))=""
set sep=$get(sep),zlsep=$zlength(sep),zpiece=+$get(zpiece),z=$select(zpiece:"z",1:"")
set asciisep=1
for i=1:1:zlsep set:$zascii($zextract(sep,i))>127 asciisep=0 quit:'asciisep
set suffix=$zysuffix(gbl_name_$select(zlsep:sep_$select(asciisep:0,zpiece:1,1:$zchset),1:"")_$select(omitflag:1,1:"")_type_force)
set name="^%ydbAIMD"_suffix
; Flagging this error needs to be deferred as the error handler may
; need name to be set.
set:'$data(lastsub) $ecode=",U244,"
; Quit if caller only wants the variable name. Note that asking for a
; name requires that XREFDATA() be called as a function that returns
; a value, as calling it as a routine asking for a name is a
; meaningless operation that is likely an application program bug.
quit:+$get(nmonly) name
; For Fileman application globals need to search all subtrees at bottom
; level when other subscripts match. Note that these application globals
; have at least two subscripts.
do:type#2
. if omitfix set altsub=sub,altlastsub=$zpiece(sub,",",$zlength(sub,","))
. else set tmp=$zlength(sub,",")-1,altsub=$zpiece(sub,",",1,tmp)_$select(tmp:",""""",1:""""""),altlastsub=""
. set type1last=locxsub(nsubs),locxsub(nsubs)="*",constlist(nsubs)=0
. set fullsubprnt=$zpiece(fullsub,",",1,$zlength(fullsub,",")-1)
do mkindxrefdata ; Create indirection strings to be used
set ztout=$view("ztrigger_output")
; Common prefix for all triggers
set trigprefix="+"_gbl_"("_trigsub_") -command="
; As cross referencing does not require exclusivity, multiple processes
; can invoke XREFDATA() with the same parameters and all will run
; correctly to completion. The process acquires locks to ensure that
; XREFDATA() and UNXREFDATA() do not run concurrently. So XREFDATA()
; uses $JOB as a subscript to the lock resource, whereas UNXREFDATA()
; does not.
lock +(^%ydbAIMD($job),^%ydbAIMD(gbl,$job),@name@($job))
set stat=+$get(stat)
; Determine whether application global permits null subscripts. For a
; global variable that spans multiple regions, all regions must be
; consistent in allowing or not allowing null subscripts. Two cascading
; unary operators ('') are used to force non-zero values to 1.
set tmp=$view("region",gbl),nullsub=''$$^%PEEKBYNAME("sgmnt_data.null_subs",$zpiece(tmp,",",1))
for i=2:1:$zlength(tmp,",") set:nullsub'=''$$^%PEEKBYNAME("sgmnt_data.null_subs",$zpiece(tmp,",",i)) $ecode=",U251,"
set:nullsub&(type#2) $ecode=",U235," ; null subscripts not permitted for Fileman globals
set ttprfx="tt"_type ; prefix for trigger template
set killtrg="rk"_nullsub ; template for KILL trigger
; Determine whether to xref pieces or entire node, and act accordingly
if $zlength(sep) do ; xref pieces
. set:'$zlength($get(pnum)) $ecode=",U250,"
. ; Make any trigger updates needed
. view "ztrigger_output":0
. tstart ():transactionid="batch"
. do:$data(@name@(10))#10
. . set tmp=^(10)
. . set:stat>tmp $ecode=",U240,"
. . set:stat<tmp stat=tmp
. set oldpstr=$get(@name@(4),"#")
. set newpstr=$$unravel(pnum)
. do:oldpstr'=newpstr
. . for i=2:1:$zlength(newpstr) set:+$zextract(oldpstr,i)'=+$zextract(newpstr,i) $zextract(newpstr,i)=1
. . set:$zlength(oldpstr)>$zlength(newpstr) $zextract(newpstr,i+1,$zlength(oldpstr))=$zextract(oldpstr,i+1,$zlength(oldpstr))
. . set pieces=$$ravel(newpstr)
. . ; remove existing triggers
. . if $ztrigger("item","-%ydb"_$zextract(name,10,$zlength(name))_"*")
. . if 'stat do
. . . set trigset=trigprefix_"set -name=%ydb"_suffix_"S -xecute="_$$exptempl(ttprfx_"Sp0")
. . . set trigdel=trigprefix_"kill -name=%ydb"_suffix_"K -xecute="_$$exptempl(killtrg)
. . . set trigdelx=trigprefix_"zkill -name=%ydb"_suffix_"Z -xecute="_$$exptempl(ttprfx_"ZKp0")
. . else if 1=stat do
. . . set trigset=trigprefix_"set -name=%ydb"_suffix_"S -xecute="_$$exptempl(ttprfx_"Sp1")
. . . set trigdel=trigprefix_"kill -name=%ydb"_suffix_"K -xecute="_$$exptempl(killtrg)
. . . set trigdelx=trigprefix_"zkill -name=%ydb"_suffix_"Z -xecute="_$$exptempl(ttprfx_"ZKp1")
. . else if 2=stat do
. . . set trigset=trigprefix_"set -name=%ydb"_suffix_"S -xecute="_$$exptempl(ttprfx_"Sp2")
. . . set trigdel=trigprefix_"kill -name=%ydb"_suffix_"K -xecute="_$$exptempl(killtrg)
. . . set trigdelx=trigprefix_"zkill -name=%ydb"_suffix_"Z -xecute="_$$exptempl(ttprfx_"ZKp2")
. . else set $ecode=",U241,"
. . set @name=gbl,@name@(4)=newpstr,^(5)=z,^(6)=trigset,^(7)=trigdel,^(8)=trigdelx,^(9)=omitfix,^(10)=stat
. . set:2=stat ^(11)=0
. . set:'($ztrigger("item",trigset)&$ztrigger("item",trigdel)&$ztrigger("item",trigdelx)) $ecode=",U239,"
. . do xtratrig ; set additional triggers for higher levels in the tree
. . set ^%ydbAIMDxref(gbl,name)=""
. tcommit
. view "ztrigger_output":ztout
. ; Cross reference existing nodes, if needed. Note that even if this
. ; process set triggers, another concurrent process might have
. ; cross referenced the pieces this process wants xref'd.
. set oldpstr=$get(@name@(3),"#"),modflag=0
. for i=2:1:$zlength(newpstr) if +$zextract(newpstr,i)&'+$zextract(oldpstr,i) set modflag=1 quit
. do:modflag
. . set:type#2 tmp=$order(constlist(""),-1),constlist(tmp)=1,locxsub(tmp)=$zwrite(type1last,1)
. . do xrefdatajobs(nsubs)
. . ; Update metadata to indicate completion
. . tstart ():transactionid="batch"
. . set @name=gbl,@name@(0)=$zut_" "_$job_" "_$zyrelease_" "_$zpiece($text(%YDBAIM),";",2),^(1)=nsubs,^(2)=sep
. . set oldpstr=$get(^(3),"#")
. . for i=2:1:$zlength(newpstr) set $zextract(oldpstr,i)=$select(+$zextract(newpstr,i):1,1:+$zextract(oldpstr,i))
. . set ^(3)=oldpstr
. . tcommit
else do ; No piece sep; xref entire node
. set:$zlength($get(pnum)) $ecode=",U238,"
. view "ztrigger_output":0
. tstart ():transactionid="batch"
. do:$data(@name@(10))#10
. . set tmp=^(10)
. . set:stat>tmp $ecode=",U240,"
. . set:stat<tmp stat=tmp
. do:'($data(@name@(0))#10)
. . if $ztrigger("item","-%ydb"_$zextract(name,10,$zlength(name))_"*")
. . if 'stat do
. . . set trigset=trigprefix_"set -name=%ydb"_suffix_"S -xecute="_$$exptempl(ttprfx_"Se0")
. . . set trigdel=trigprefix_"kill -name=%ydb"_suffix_"K -xecute="_$$exptempl(killtrg)
. . . set trigdelx=trigprefix_"zkill -name=%ydb"_suffix_"Z -xecute="_$$exptempl(ttprfx_"ZKe0")
. . else if 1=stat do
. . . set trigset=trigprefix_"set -name=%ydb"_suffix_"S -xecute="_$$exptempl(ttprfx_"Se1")
. . . set trigdel=trigprefix_"kill -name=%ydb"_suffix_"K -xecute="_$$exptempl(killtrg)
. . . set trigdelx=trigprefix_"zkill -name=%ydb"_suffix_"Z -xecute="_$$exptempl(ttprfx_"ZKe1")
. . else if 2=stat do
. . . set trigset=trigprefix_"set -name=%ydb"_suffix_"S -xecute="_$$exptempl(ttprfx_"Se2")
. . . set trigdel=trigprefix_"kill -name=%ydb"_suffix_"K -xecute="_$$exptempl(killtrg)
. . . set trigdelx=trigprefix_"zkill -name=%ydb"_suffix_"Z -xecute="_$$exptempl(ttprfx_"ZKe2")
. . else set $ecode=",U241,"
. . set @name=gbl,@name@(6)=trigset,^(7)=trigdel,^(8)=trigdelx,^(9)=omitfix,^(10)=stat
. . set:2=stat ^(11)=0
. . set:'($ztrigger("item",trigset)&$ztrigger("item",trigdel)&$ztrigger("item",trigdelx)) $ecode=",U239,"
. . do xtratrig ; set additional triggers for higher level nodes
. . set ^%ydbAIMDxref(gbl,name)=""
. tcommit
. view "ztrigger_output":ztout
. set newpstr=""
. set:type#2 tmp=$order(constlist(""),-1),constlist(tmp)=1,locxsub(tmp)=$zwrite(type1last,1)
. do xrefdatajobs(nsubs)
. ; Add metadata to indicate completion
. tstart ():transactionid="batch"
. set @name@(0)=$zut_" "_$job_" "_$zyrelease_" "_$zpiece($text(%YDBAIM),";",2),^(1)=nsubs,(^(2),^(3),^(4),^(5))=""
. tcommit
; label to which premature termination of xrefdatajobs() does ZGOTO
XREFDATAQUIT
; Release locks that block UNXREFDATA()
lock:$data(name) -@name@($job)
lock:$data(gbl) -^%ydbAIMD(gbl,$job)
lock -^%ydbAIMD($job)
set $zinterrupt=zintrptsav
quit:$quit name quit
; The functions below are intended only to be called internally. Therefore,
; they assume that parameters have been validated by the caller. Also, as
; some of them are helper functions, analogous to macros in some other
; languages, they use variables from caller code, as documented.
; Checks for whether the single parameter meets one of the following trigger
; specifications supported by Application Independent Metadata:
; - A specific value, e.g., 100.1 or "PATIENT" with * (the default if not
; specified) to indicate that all subscripts at that level should be matched.
; - An inclusive range of specific values separated by a colon, e.g.,
; "1700:1799" or "A":"Z" where an omitted value is either the first possible
; subscript or the last possible subscript in the collation sequence for that
; global variable. If both subscripts are omitted, all subscripts are matched
; (i.e., ":" is equivalent to "*").
; - A semicolon (;) separated list of values or inclusive values of either of
; the two forms above.
; Returns 1 if the trigger specification is a constant, 0 otherwise. Invalid
; specifications set $ecode and raise an error.
chktrgspec:(locxsub,n)
new i,lower,okflag,piecelen,subpiece,upper
quit:"*"=locxsub(n) 0
set okflag=1
for i=1:1:$length(locxsub(n),";") do quit:'okflag
. set subpiece=$piece(locxsub(n),";",i),piecelen=$length(subpiece,":")
. ; Nothing to check if single value; only ranges need to be checked
. if 1=piecelen
. else if 2=piecelen do
. . if (":"=$zextract(subpiece,1))!(":"=$zextract(subpiece,$zlength(subpiece)))
. . else do
. . . set lower=$piece(subpiece,":",1),upper=$piece(subpiece,":",2)
. . . ; if upper=lower, this piece is really a constant value
. . . if upper=lower set piecelen=1,$piece(locxsub(n),";",i)=lower
. . . else set:lower]]upper $ecode=",U246,"
. else set okflag=0,$ecode=",U245,"
quit (1=i)&(1=piecelen)
; Expand trigger template into -xecute string for trigger Note that this relies
; on the (per standard) behavior of $text() to report empty source code lines
; as a single space.
; Notes:
; - exptempl() uses the line just before the label to get list of variables
; to replace. A variable name ending in $ means $ZWRITE() its value when
; expanding.
; - fullsubprnt needs to precede fullsub in the list of substitutions otherwise
; the @fullsub part of @fullsubprnt will be replaced leaving "prnt" in place,
; which creates incorrect triggers.
; - code between slashes (///) is replaced, with the first part used if force
; is non-zero, and the second if it is zero
; Uses local variables from XREFDATA() but does not substitute them: force
; Uses local variables from XREFDATA(): altlastsub,altsub,fullsubprnt,fullsub,fulltrigsub,gbl,lastfullsub,lastsub,name,pieces,sep$,sub,type1last,xfnp1,xfnp2,z
exptempl:(lab)
new i,j,len,line,multiline,outstr,npieces,rep,str,tmp,var,vars,zflag
set tmp=$text(@lab),len=$zlength(tmp,";"),line=$zpiece(tmp,";",2,len)
set str=line
for i=1:1 set tmp=$text(@lab+i) quit:" "=tmp set len=$zlength(tmp,";"),line=$zpiece(tmp,";",2,len),str=str_$char(10)_line
set multiline=i-1
; Set template options to force string collation if specified
set npieces=$zlength(str,"/")-1
set:npieces#3 $ecode=",U231,"
do:npieces
. for i=npieces:-3:2 set $zpiece(str,"/",$select(force:i,1:i-1))=""
. set str=$ztranslate(str,"/") ; Remove / option separators
set vars=$zpiece($text(exptempl+-1),": ",2)
set outstr=str ; handle case where there are no substitutions
for i=1:1:$zlength(vars,",") set tmp=$zpiece(vars,",",i),var=$zpiece(tmp,"$",1),zflag=$zlength(tmp,"$")-1 do:$data(@var)
. set rep="@"_var,len=$zlength(str,rep),outstr=$zpiece(str,rep,1)
. for j=2:1:len do
. . set outstr=outstr_$select(zflag:$zwrite(@var),1:@var)_$zpiece(str,rep,j)
. set str=outstr
quit $select(multiline:"<<"_$char(10)_outstr_$c(10),1:$zwrite(outstr))
; Output metadata for a specific xref variable.
lsxrefdata:(lvn,xref)
tstart ():transactionid="batch"
do:$data(@xref)
. new s
. set lvn(xref)=@xref
. set s="" for set:$data(@xref@(s))#10 lvn(xref,s)=^(s) set s=$order(^(s)) quit:'$zlength(s)
tcommit
quit
; Create indirection strings to be used by xrefdata()
; Uses or references local variables passed to or defined in XREFDATA():
; constlist, gbl, gblind, gblindtype1, lastvarsub, name, nameind, nsubs,
; omitflags, sep, subary, type, valcntind, xrefind, xrefindtype1
mkindxrefdata:
new i,tmp,valtype
set gblind(1)=gbl_"("_$select(constlist(1):locxsub(1),1:"subary(1)")
for i=2:1:nsubs set gblind(i)=gblind(i-1)_","_$select(constlist(i):locxsub(i),1:"subary("_i_")")
for i=1:1:nsubs set gblind(i)=gblind(i)_")"
set xrefind=name_"("_$select($zlength(sep):"k,"_$select(force:"""#""_",1:"")_"pieceval",1:"0,"_$select(force:"""#""_",1:"")_"nodeval")
for i=1:1:nsubs do
. if constlist(i)!((type#2)&(i=nsubs)) do
. . if 'omitfix set lastvarsub=i,lastsubind=locxsub(i),xrefind=xrefind_","_lastsubind
. else set lastvarsub=i,lastsubind="subary("_i_")",xrefind=xrefind_","_lastsubind
set xrefind=xrefind_")"
if $zlength(sep) set nameind=name_"(-k,"_$select(force:"""#""_",1:"")_"pieceval)",valcntind=name_"(-k)"
else set nameind=name_"("""","_$select(force:"""#""_",1:"")_"nodeval)",valcntind=name_"("""")"
do:type#2 ; Fileman global schema
. set xrefindtype1=$select(omitfix:xrefind,1:$zpiece(xrefind,",",1,$zlength(xrefind,",")-1)_","""")")
. set gblindtype1=gblind(nsubs)
. set $zpiece(gblindtype1,",",$zlength(gblindtype1,","))=type1last_")"
do:1<type ; Transformation function used for metadata
. set valtype=$select($zfind(xrefind,"nodeval"):"nodeval",1:"pieceval")
. set xrefind=$zpiece(xrefind,valtype,1)_xfnp1_valtype_xfnp2_$zpiece(xrefind,valtype,2)
. set nameind=$zpiece(nameind,valtype,1)_xfnp1_valtype_xfnp2_$zpiece(nameind,valtype,2)
quit
; ravel() takes a bit-map like piece number string, e.g., "#0010100111", and
; composes from it the form suitable for the index values of a for loop,
; e.g., 3,5,8,9,10
ravel:(pstr)
new i,newpspec
set newpspec=""
for i=2:1:$zlength(pstr) if $zextract(pstr,i) set newpspec=newpspec_(i-1)_","
quit $zextract(newpspec,1,$zlength(newpspec)-1)
; Snapshots state of locks. Parameter must be passed by reference.
snaplck:(snap)
new i,lcks,tmp
zshow "l":lcks
for i=1:1 quit:'$data(lcks("L",i)) do
. set tmp=lcks("L",i)
. set snap($piece($piece(tmp,"LOCK ",2)," LEVEL=",1))=$piece(tmp," LEVEL=",2)
quit
; This label takes a piece number specification, e.g., "3;5;8:10" and returns
; the bit-map like piece specification, preceded by "#", e.g., "#0010100111" for
; the example above. and determines if there are new pieces to xref. Pieces are
; separated by semi-colons. Each piece can be a number, or two colon separated
; numbers, with the first number smaller than the second.
unravel:(pnum)
new k,newpstr,nextp,nextp1,nextpl,nextplen,nextpu
set newpstr="#"
for k=1:1:$zlength(pnum,";") do
. set nextp=$zpiece(pnum,";",k),nextplen=$zlength(nextp,":")
. if 1=nextplen do
. . set:nextp\1'=nextp!(nextp<=0) $ecode=",U248,"
. . set nextp1=nextp+1
. . set:nextp1>$zlength(newpstr) newpstr=newpstr_$ztranslate($justify("",nextp1-$zlength(newpstr))," ",0)
. . set $zextract(newpstr,nextp1)=1
. else if 2=nextplen do
. . set nextpl=$zpiece(nextp,":",1),nextpu=$zpiece(nextp,":",2)
. . set:(nextpl\1'=nextpl)!(0>=nextpl)!(nextpu\1'=nextpu)!(0>=nextpu)!(nextpu<nextpl) $ecode=",U248,"
. . set nextp1=nextpu+1
. . set:nextp1>$zlength(newpstr) newpstr=newpstr_$ztranslate($justify("",nextp1-$zlength(newpstr))," ",0)
. . for j=nextpl+1:1:nextp1 set $zextract(newpstr,j)=1
. else set $ecode=",U249,"
quit newpstr
; Releases locks so that locks owned by the process match those in the
; parameter, which should be created by snaplck(). Parameter should be
; passed by reference.
unsnaplck:(oldlck)
new currlck,i,lck,relstr
do snaplck(.currlck)
set lck=""
for set lck=$order(currlck(lck)) quit:'$zlength(lck) do
. if $increment(currlck(lck),-$get(oldlck(lck),0))
. for i=1:1:currlck(lck) lock -@lck
quit
; Given the name of a cross reference global, this function removes the triggers
; used to maintain the xref, and then deletes the cross reference global
; variable. Variable(s) used from caller: gbl
unxrefdata:(xrefgbl)
do:$data(@xrefgbl)
. new i,trig,ztout
. ; The M lock protects against concurrent XREFDATA() and UNXREFDATA()
. ; and the transaction ensures Consistency of the metadata about the
. ; metadata.
. lock +@xrefgbl
. set ztout=$view("ztrigger_output")
. view "ztrigger_output":0
. tstart ():transactionid="batch"
. ; YDBAIM metadata variables start with ^%ydbAIMD, but the trigger
. ; names start with %ydb. Both have the same $ZYHASH(), but trigger
. ; names additionally have a single character suffix.
. if $ztrigger("item","-%ydb"_$zextract(xrefgbl,10,$zlength(xrefgbl))_"*")
. kill @xrefgbl
. zkill ^%ydbAIMDxref(gbl,xrefgbl)
. tcommit
. view "ztrigger_output":ztout
. lock -@xrefgbl
quit
; Generate cross references for nodes at the specified subscript level. It goes
; through the global at each level with fewer subscripts than the cross
; reference calls for, and where a subtree is found, recursively calls itself on
; that subtree till it reaches the specified level, whereon it generates the
; cross references. Since triggers are set before calling this routine, they
; will take care of cross references for any node deletions. This label only
; needs to be concerned with setting cross references for nodes that don't
; already have cross references, which is done in a transaction to ensure
; Consistency.
; The following code has some built-in assumptions about the frequency of
; occurrence of different options, and actual application data might imply
; a different ordering of tests for the various code paths. Fortunately, this
; code is executed only for the initial creation of a cross reference, and not
; in the triggers that maintain cross references as globals are updated.
; References variables defined in parent or passed through from XREFDATA():
; constlist, force, gbl, gblind, gblindtype1, lastsubind, lastvarsub, locxsub,
; name, nameind, newpstr, nullsub, omitfix, stacklvl1, stacklvl2, stat, subary,
; tick, type, type1last, valcntind, xfnp1, xfnp2, xrefind, xrefindtype1, zpiece
xrefdata(nsubsxref,dir,ppid)
new flag,i,j,k,nodelen1,nodeval,nranges,piece2,piece2,pieceval,quitflag
new rangebegin,rangeend,rangefirst,rangeflag,rangelast,sublvl,thisrange,tmp1,tmp2
; If nsubsxref>1 it means call the function recursively for the next
; subscript level.
set flag=nullsub
set sublvl=$order(locxsub(""),-1)-nsubsxref+1
set subary(sublvl)=""
if nsubsxref>1 do
. if "*"=locxsub(sublvl) for do:flag set flag=1,subary(sublvl)=$order(@gblind(sublvl),dir) quit:'$zlength(subary(sublvl))
. . do:$data(@gblind(sublvl))\10 xrefdata(nsubsxref-1,dir,ppid)
. else if $get(constlist(sublvl),0) do:$data(@gblind(sublvl))\10 xrefdata(nsubsxref-1,dir,ppid) quit ; quit is performance optimization
. else do quit ; quit is performance optimization
. . set nranges=$select(zpiece:$zlength(locxsub(sublvl),";"),1:$length(locxsub(sublvl),";"))
. . if 1=dir set rangefirst=1,rangelast=nranges
. . else set rangefirst=nranges,rangelast=1
. . for i=rangefirst:dir:rangelast do
. . . if zpiece do
. . . . set thisrange=$zpiece(locxsub(sublvl),";",i)
. . . . set piece1=$zpiece(thisrange,":",1),piece2=$zpiece(thisrange,":",$zlength(thisrange,":"))
. . . else do
. . . . set thisrange=$piece(locxsub(sublvl),";",i)
. . . . set piece1=$piece(thisrange,":",1),piece2=$piece(thisrange,":",$length(thisrange,":"))
. . . set rangeflag=0
. . . if $zlength(piece1) set rangebegin=$zwrite(piece1,1),rangeflag=1
. . . else set rangebegin=""
. . . if $zlength(piece2) set rangeend=$zwrite(piece2,1),rangeflag=1
. . . else set rangeend=""
. . . set flag=$select(1=dir:$select($zlength(piece1):1,1:nullsub),1:$select($zlength(piece2):1,1:0))
. . . set subary(sublvl)=$select(1=dir:rangebegin,1:rangeend)
. . . for do:flag set flag=1,(subary(sublvl),tmp2)=$order(@gblind(sublvl),dir) quit:'$zlength(tmp2)!(rangeflag&$select(1=dir:$select($zlength(piece2):tmp2]]rangeend,1:0),1:rangebegin]]tmp2))
. . . . if (type#2)&(2=nsubsxref) do
. . . . . tstart ():transactionid="batch"
. . . . . set tmp1=$data(@gblind(sublvl))