Macros for SAS Application Developers
https://github.com/sasjs/core
Loading...
Searching...
No Matches
mp_lockanytable.sas
Go to the documentation of this file.
1/**
2 @file
3 @brief Mechanism for locking tables to prevent parallel modifications
4 @details Uses a control table to enable ANY table to be locked for updates
5 (not just SAS datasets).
6 Only useful if every update uses the macro! Used heavily within
7 [Data Controller for SAS](https://datacontroller.io).
8
9 @param [in] action The action to be performed. Valid values:
10 @li LOCK - Sets the lock flag, also confirms if a SAS lock is available
11 @li UNLOCK - Unlocks the table
12 @param [in] lib= (WORK) The libref of the table to lock. Should already be
13 assigned.
14 @param [in] ds= The dataset to lock
15 @param [in] ref= A meaningful reference to enable the lock to be traced. Max
16 length is 200 characters.
17 @param [out] ctl_ds= (0) The control table which controls the actual locking.
18 Should already be assigned and available. The definition is available by
19 running the mddl_dc_locktable.sas macro.
20
21 @param [in] loops= (25) Number of times to check for a lock.
22 @param [in] loop_secs= (1) Seconds to wait between each lock attempt
23
24 <h4> SAS Macros </h4>
25 @li mf_fmtdttm.sas
26 @li mp_abort.sas
27 @li mp_lockfilecheck.sas
28 @li mf_getuser.sas
29
30 <h4> Related Macros </h4>
31 @li mp_lockanytable.test.sas
32
33 @version 9.2
34
35**/
36
37%macro mp_lockanytable(
38 action
39 ,lib= WORK
40 ,ds=0
41 ,ref=
42 ,ctl_ds=0
43 ,loops=25
44 ,loop_secs=1
45 );
46data _null_;
47 if _n_=1 then putlog "&sysmacroname entry vars:";
48 set sashelp.vmacro;
49 where scope="&sysmacroname";
50 put name '=' value;
51run;
52
53%mp_abort(iftrue= ("&ds"="0" and &action ne MAKETABLE)
54 ,mac=&sysmacroname
55 ,msg=%str(dataset was not provided)
56)
57%mp_abort(iftrue= (&ctl_ds=0)
58 ,mac=&sysmacroname
59 ,msg=%str(Control dataset was not provided)
60)
61
62/* set up lib & mac vars */
63%let lib=%upcase(&lib);
64%let ds=%upcase(&ds);
65%let action=%upcase(&action);
66%local user x trans msg abortme;
67%let user=%mf_getuser();
68%let abortme=0;
69
70%mp_abort(iftrue= (&action ne LOCK & &action ne UNLOCK & &action ne MAKETABLE)
71 ,mac=&sysmacroname
72 ,msg=%str(Invalid action (&action) provided)
73)
74
75/* if an err condition exists, exit before we even begin */
76%mp_abort(iftrue= (&syscc>0 and &action=LOCK)
77 ,mac=&sysmacroname
78 ,msg=%str(aborting due to syscc=&syscc on LOCK entry)
79)
80
81/* do not bother locking work tables (else may affect all WORK libraries) */
82%if (%upcase(&lib)=WORK or %str(&lib)=%str()) & &action ne MAKETABLE %then %do;
83 %put NOTE: WORK libraries will not be registered in the locking system.;
84 %return;
85%end;
86
87/* do not proceed if no observations can be processed */
88%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)
89 ,mac=&sysmacroname
90 ,msg=%str(cannot continue when options obs = 0)
91)
92
93%if &ACTION=LOCK %then %do;
94
95 /* abort if a SAS lock is already in place, or cannot be applied */
96 %mp_lockfilecheck(&lib..&ds)
97
98 /* next, check there is a record for this table */
99 %local record_exists_check;
100 proc sql noprint;
101 select count(*) into: record_exists_check from &ctl_ds
102 where LOCK_LIB ="&lib" and LOCK_DS="&ds";
103 quit;
104 %if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;
105 %if &record_exists_check=0 %then %do;
106 data _null_;
107 putlog "&sysmacroname: adding record to lock table..";
108 run;
109
110 data ;
111 if 0 then set &ctl_ds;
112 LOCK_LIB ="&lib";
113 LOCK_DS="&ds";
114 LOCK_STATUS_CD='LOCKED';
115 LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt;
116 LOCK_USER_NM="&user";
117 LOCK_PID="&sysjobid";
118 LOCK_REF="&ref";
119 output;stop;
120 run;
121 %let trans=&syslast;
122 proc append base=&ctl_ds data=&trans;
123 run;
124 %end;
125 /* if record does exist, perform lock attempts */
126 %else %do x=1 %to &loops;
127 data _null_;
128 putlog "&sysmacroname: attempting lock (iteration &x) "@;
129 putlog "at %sysfunc(datetime(),datetime19.) ..";
130 run;
131
132 proc sql;
133 update &ctl_ds
134 set LOCK_STATUS_CD='LOCKED'
135 , LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt
136 , LOCK_USER_NM="&user"
137 , LOCK_PID="&sysjobid"
138 , LOCK_REF="&ref"
139 where LOCK_LIB ="&lib" and LOCK_DS="&ds";
140 quit;
141 /**
142 * NOTE - occasionally SQL server will return an err code (deadlocked
143 * transaction). If so, ignore it, keep calm, and carry on..
144 */
145 %if &syscc>0 %then %do;
146 data _null_;
147 putlog 'NOTE-' / 'NOTE-';
148 putlog "NOTE- &sysmacroname: Update failed. "@;
149 putlog "Resetting err conditions and re-attempting.";
150 putlog "NOTE- syscc=&syscc syserr=&syserr sqlrc=&sqlrc";
151 putlog 'NOTE-' / 'NOTE-';
152 run;
153 %let syscc=0;
154 %let sqlrc=0;
155 %end;
156
157 /* now check if the record was successfully updated */
158 %local success_check;
159 proc sql noprint;
160 select count(*) into: success_check from &ctl_ds
161 where LOCK_LIB ="&lib" and LOCK_DS="&ds"
162 and LOCK_PID="&sysjobid" and LOCK_STATUS_CD='LOCKED';
163 quit;
164 %if &success_check=0 %then %do;
165 %if &x < &loops %then %do;
166 /* pause before next check */
167 data _null_;
168 putlog 'NOTE-' / 'NOTE-';
169 putlog "NOTE- &sysmacroname: table locked, waiting "@;
170 putlog "%sysfunc(sleep(&loop_secs)) seconds.. ";
171 putlog "NOTE- (iteration &x of &loops)";
172 putlog 'NOTE-' / 'NOTE-';
173 run;
174 %end;
175 %else %do;
176 %let msg=Unable to lock &lib..&ds via &ctl_ds after &loops attempts.\n
177 Please ask your administrator to investigate!;
178 %let abortme=1;
179 %end;
180 %end;
181 %else %do;
182 data _null_;
183 putlog 'NOTE-' / 'NOTE-';
184 putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@;
185 putlog " %sysfunc(datetime(),datetime19.) (iteration &x)"@;
186 putlog 'NOTE-' / 'NOTE-';
187 run;
188 %if &syscc>0 %then %do;
189 %put setting syscc(&syscc) back to 0;
190 %let syscc=0;
191 %end;
192 %let x=&loops; /* no more iterations needed */
193 %end;
194 %end;
195%end;
196%else %if &ACTION=UNLOCK %then %do;
197 %local status cnt;
198 %let cnt=0;
199 proc sql noprint;
200 select count(*) into: cnt from &ctl_ds where LOCK_LIB ="&lib" & LOCK_DS="&ds";
201 %if &cnt=0 %then %do;
202 %put %str(WAR)NING: &lib..&ds was not previously locked in &ctl_ds!;
203 %end;
204 %else %do;
205 select LOCK_STATUS_CD into: status from &ctl_ds
206 where LOCK_LIB ="&lib" and LOCK_DS="&ds";
207 quit;
208 %if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;
209 %if &status=LOCKED %then %do;
210 data _null_;
211 putlog "&sysmacroname: unlocking &lib..&ds:";
212 run;
213 proc sql;
214 update &ctl_ds
215 set LOCK_STATUS_CD='UNLOCKED'
216 , LOCK_END_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt
217 , LOCK_USER_NM="&user"
218 , LOCK_PID="&sysjobid"
219 , LOCK_REF="&ref"
220 where LOCK_LIB ="&lib" and LOCK_DS="&ds";
221 quit;
222 %end;
223 %else %if &status=UNLOCKED %then %do;
224 %put %str(WAR)NING: &lib..&ds is already unlocked!;
225 %end;
226 %else %do;
227 %put NOTE: Unrecognised STATUS_CD (&status) in &ctl_ds;
228 %let abortme=1;
229 %end;
230 %end;
231%end;
232%else %do;
233 %let msg=lock_anytable given unsupported action (&action);
234 %let abortme=1;
235%end;
236
237/* catch errs - mp_abort must be called outside of a logic block */
238%mp_abort(iftrue=(&abortme=1),
239 msg=%superq(msg),
240 mac=&sysmacroname
241)
242
243%exit_macro:
244data _null_;
245 put "&sysmacroname: Exit vars: action=&action lib=&lib ds=&ds";
246 put " syscc=&syscc sqlrc=&sqlrc syserr=&syserr";
247run;
248%mend mp_lockanytable;
249
250