diff --git a/all.sas b/all.sas index fef4067..543fbcf 100644 --- a/all.sas +++ b/all.sas @@ -6204,6 +6204,357 @@ select distinct lowcase(memname) %end; %mend mp_lib2inserts;/** + @file + @brief Mechanism for locking tables to prevent parallel modifications + @details Uses a control table to enable ANY table to be locked for updates. + Only useful if every update uses the macro! Used heavily within + [Data Controller for SAS](https://datacontroller.io). + + The underlying table is structured as per the MAKETABLE action. + + @param [in] action The action to be performed. Valid values: + @li LOCK - Sets the lock flag, also confirms if a SAS lock is available + @li UNLOCK - Unlocks the table + @li MAKETABLE - creates the control table (ctl_ds) + @param [in] lib= (WORK) The libref of the table to lock. Should already be + assigned. + @param [in] ds= The dataset to lock + @param [in] ref= A meaningful reference to enable the lock to be traced. Max + length is 200 characters. + @param [out] ctl_ds= (0) The control table which controls the actual locking. + Should already be assigned and available. + @param [in] loops= (25) Number of times to check for a lock. + @param [in] loop_secs= (1) Seconds to wait between each lock attempt + +

SAS Macros

+ @li mp_abort.sas + @li mp_lockfilecheck.sas + @li mf_getuser.sas + +

Related Macros

+ @li mp_lockanytable.test.sas + + @version 9.2 + +**/ + +%macro mp_lockanytable( + action + ,lib= WORK + ,ds=0 + ,ref= + ,ctl_ds=0 + ,loops=25 + ,loop_secs=1 + ); +data _null_; + if _n_=1 then putlog "&sysmacroname entry vars:"; + set sashelp.vmacro; + where scope="&sysmacroname"; + put name '=' value; +run; + +%mp_abort(iftrue= (&ds=0 and &action ne MAKETABLE) + ,mac=&sysmacroname + ,msg=%str(dataset was not provided) +) +%mp_abort(iftrue= (&ctl_ds=0) + ,mac=&sysmacroname + ,msg=%str(Control dataset was not provided) +) + +/* set up lib & mac vars */ +%let lib=%upcase(&lib); +%let ds=%upcase(&ds); +%let action=%upcase(&action); +%local user x trans msg abortme; +%let user=%mf_getuser(); +%let abortme=0; + +%mp_abort(iftrue= (&action ne LOCK & &action ne UNLOCK & &action ne MAKETABLE) + ,mac=&sysmacroname + ,msg=%str(Invalid action (&action) provided) +) + +/* if an err condition exists, exit before we even begin */ +%mp_abort(iftrue= (&syscc>0 and &action=LOCK) + ,mac=&sysmacroname + ,msg=%str(aborting due to syscc=&syscc on LOCK entry) +) + +/* do not bother locking work tables (else may affect all WORK libraries) */ +%if (%upcase(&lib)=WORK or %str(&lib)=%str()) & &action ne MAKETABLE %then %do; + %put NOTE: WORK libraries will not be registered in the locking system.; + %return; +%end; + +/* do not proceed if no observations can be processed */ +%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0) + ,mac=&sysmacroname + ,msg=%str(options obs = 0. syserrortext=&syserrortext) +) + +%if &ACTION=LOCK %then %do; + + /* abort if a SAS lock is already in place, or cannot be applied */ + %mp_lockfilecheck(&lib..&ds) + + /* next, check there is a record for this table */ + %local record_exists_check; + proc sql noprint; + select count(*) into: record_exists_check from &ctl_ds + where LOCK_LIB ="&lib" and LOCK_DS="&ds"; + quit; + %if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc; + %if &record_exists_check=0 %then %do; + data _null_; + putlog "&sysmacroname: adding record to lock table.."; + run; + + data ; + if 0 then set &ctl_ds; + LOCK_LIB ="&lib"; + LOCK_DS="&ds"; + LOCK_STATUS_CD='LOCKED'; + LOCK_START_DTTM="%sysfunc(datetime(),E8601DT26.6)"dt; + LOCK_USER_NM="&user"; + LOCK_PID="&sysjobid"; + LOCK_REF="&ref"; + output;stop; + run; + %let trans=&syslast; + proc append base=&ctl_ds data=&trans; + run; + %end; + /* if record does exist, perform lock attempts */ + %else %do x=1 %to &loops; + data _null_; + putlog "&sysmacroname: attempting lock (iteration &x) "@; + putlog "at %sysfunc(datetime(),datetime19.) .."; + run; + + proc sql; + update &ctl_ds + set LOCK_STATUS_CD='LOCKED' + , LOCK_START_DTTM="%sysfunc(datetime(),E8601DT26.6)"dt + , LOCK_USER_NM="&user" + , LOCK_PID="&sysjobid" + , LOCK_REF="&ref" + where LOCK_LIB ="&lib" and LOCK_DS="&ds"; + quit; + /** + * NOTE - occasionally SQL server will return an err code (deadlocked + * transaction). If so, ignore it, keep calm, and carry on.. + */ + %if &syscc>0 %then %do; + data _null_; + putlog 'NOTE-' / 'NOTE-'; + putlog "NOTE- &sysmacroname: Update failed. "@; + putlog "Resetting err conditions and re-attempting."; + putlog "NOTE- syscc=&syscc syserr=&syserr sqlrc=&sqlrc"; + putlog 'NOTE-' / 'NOTE-'; + run; + %let syscc=0; + %let sqlrc=0; + %end; + + /* now check if the record was successfully updated */ + %local success_check; + proc sql noprint; + select count(*) into: success_check from &ctl_ds + where LOCK_LIB ="&lib" and LOCK_DS="&ds" + and LOCK_PID="&sysjobid" and LOCK_STATUS_CD='LOCKED'; + quit; + %if &success_check=0 %then %do; + %if &x < &loops %then %do; + /* pause before next check */ + data _null_; + putlog 'NOTE-' / 'NOTE-'; + putlog "NOTE- &sysmacroname: table locked, waiting "@; + putlog "%sysfunc(sleep(&loop_inc)) seconds.. "; + putlog "NOTE- (iteration &x of &loops)"; + putlog 'NOTE-' / 'NOTE-'; + run; + %end; + %else %do; + %let msg=Unable to lock &lib..&ds via &ctl_ds after &loops attempts.\n + Please ask your administrator to investigate!; + %let abortme=1; + %end; + %end; + %else %do; + data _null_; + putlog 'NOTE-' / 'NOTE-'; + putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@ + putlog " %sysfunc(datetime(),datetime19.) (iteration &x)"@; + putlog 'NOTE-' / 'NOTE-'; + run; + %if &syscc>0 %then %do; + %put setting syscc(&syscc) back to 0; + %let syscc=0; + %end; + %let x=&loops; /* no more iterations needed */ + %end; + %end; +%end; +%else %if &ACTION=UNLOCK %then %do; + %local status; + proc sql noprint; + select LOCK_STATUS_CD into: status from &ctl_ds + where LOCK_LIB ="&lib" and LOCK_DS="&ds"; + quit; + %if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc; + %if &status=LOCKED %then %do; + data _null_; + putlog "&sysmacroname: unlocking &lib..&ds:"; + run; + proc sql; + update &ctl_ds + set LOCK_STATUS_CD='UNLOCKED' + , LOCK_END_DTTM="%sysfunc(datetime(),E8601DT26.6)"dt + , LOCK_USER_NM="&user" + , LOCK_PID="&sysjobid" + , LOCK_REF="&ref" + where LOCK_LIB ="&lib" and LOCK_DS="&ds"; + quit; + %end; + %else %if &status=UNLOCKED %then %do; + %put %str(WAR)NING: &lib..&ds is already unlocked!; + %end; + %else %do; + %put NOTE: Unrecognised STATUS_CD (&status) in &ctl_ds; + %let abortme=1; + %end; +%end; +%else %if &action=MAKETABLE %then %do; + proc sql; + create table &ctl_ds( + lock_lib char(8), + lock_ds char(32), + lock_status_cd char(10) not null, + lock_user_nm char(100) not null , + lock_ref char(200), + lock_pid char(10), + lock_start_dttm num format=E8601DT26.6, + lock_end_dttm num format=E8601DT26.6, + constraint pk_mp_lockanytable primary key(lock_lib,lock_ds)); +%end; +%else %do; + %let msg=lock_anytable given unsupported action (&action); + %let abortme=1; +%end; + +/* catch errors - mp_abort must be called outside of a logic block */ +%mp_abort(iftrue=(&abortme=1), + msg=%superq(msg), + mac=&sysmacroname +) + +%exit_macro: +data _null_; + put "&sysmacroname: Exit vars: action=&action lib=&lib ds=&ds"; + put " syscc=&syscc sqlrc=&sqlrc syserr=&syserr"; +run; +%mend mp_lockanytable; + + +/** + @file + @brief Aborts if a SAS lock file is in place, or if one cannot be applied. + @details Used in conjuction with the mp_lockanytable macro. + More info here: https://sasensei.com/flash/24 + + Usage: + + data work.test; a=1;run; + %mp_lockfilecheck(work.test) + + @param [in] libds The libref.dataset for which to check the lock status + +

SAS Macros

+ @li mp_abort.sas + @li mf_getattrc.sas + +

Related Macros

+ @li mp_lockanytable.sas + @li mp_lockfilecheck.test.sas + + @version 9.2 +**/ + +%macro mp_lockfilecheck( + libds +)/*/STORE SOURCE*/; + +data _null_; + if _n_=1 then putlog "&sysmacroname entry vars:"; + set sashelp.vmacro; + where scope="&sysmacroname"; + put name '=' value; +run; + +%mp_abort(iftrue= (&syscc>0) + ,mac=checklock.sas + ,msg=Aborting with syscc=&syscc on entry. +) +%mp_abort(iftrue= (&libds=0) + ,mac=&sysmacroname + ,msg=%str(libds not provided) +) + +%local msg lib ds; +%let lib=%upcase(%scan(&libds,1,.)); +%let ds=%upcase(%scan(&libds,2,.)); + +/* do not proceed if no observations can be processed */ +%let msg=options obs = 0. syserrortext=%superq(syserrortext); +%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0) + ,mac=checklock.sas + ,msg=%superq(msg) +) + +data _null_; + putlog "Checking engine & member type"; +run; +%local engine memtype; +%let memtype=%mf_getattrc(&libds,MTYPE); +%let engine=%mf_getattrc(&libds,ENGINE); + +%if &engine ne V9 and &engine ne BASE %then %do; + data _null_; + putlog "Lib &lib is not assigned using BASE engine - uses &engine instead"; + putlog "SAS lock check will not be performed"; + run; + %return; +%end; +%else %if &memtype ne DATA %then %do; + %put NOTE: Cannot lock a VIEW!! Memtype=&memtype; + %return; +%end; + +data _null_; + putlog "Engine = &engine, memtype=&memtype"; + putlog "Attempting lock statement"; +run; + +lock &libds; + +%local abortme; +%let abortme=0; +%if &syscc>0 or &SYSLCKRC ne 0 %then %do; + %let msg=Unable to apply lock on &libds (SYSLCKRC=&SYSLCKRC syscc=&syscc); + %put %str(ERR)OR: &sysmacroname: &msg; + %let abortme=1; +%end; + +lock &libds clear; + +%mp_abort(iftrue= (&abortme=1) + ,mac=&sysmacroname + ,msg=%superq(msg) +) + +%mend mp_lockfilecheck;/** @file @brief Create a Markdown Table from a dataset @details A markdown table is a simple table representation for use in diff --git a/base/mp_lockanytable.sas b/base/mp_lockanytable.sas new file mode 100644 index 0000000..e4dcab6 --- /dev/null +++ b/base/mp_lockanytable.sas @@ -0,0 +1,255 @@ +/** + @file + @brief Mechanism for locking tables to prevent parallel modifications + @details Uses a control table to enable ANY table to be locked for updates. + Only useful if every update uses the macro! Used heavily within + [Data Controller for SAS](https://datacontroller.io). + + The underlying table is structured as per the MAKETABLE action. + + @param [in] action The action to be performed. Valid values: + @li LOCK - Sets the lock flag, also confirms if a SAS lock is available + @li UNLOCK - Unlocks the table + @li MAKETABLE - creates the control table (ctl_ds) + @param [in] lib= (WORK) The libref of the table to lock. Should already be + assigned. + @param [in] ds= The dataset to lock + @param [in] ref= A meaningful reference to enable the lock to be traced. Max + length is 200 characters. + @param [out] ctl_ds= (0) The control table which controls the actual locking. + Should already be assigned and available. + @param [in] loops= (25) Number of times to check for a lock. + @param [in] loop_secs= (1) Seconds to wait between each lock attempt + +

SAS Macros

+ @li mp_abort.sas + @li mp_lockfilecheck.sas + @li mf_getuser.sas + +

Related Macros

+ @li mp_lockanytable.test.sas + + @version 9.2 + +**/ + +%macro mp_lockanytable( + action + ,lib= WORK + ,ds=0 + ,ref= + ,ctl_ds=0 + ,loops=25 + ,loop_secs=1 + ); +data _null_; + if _n_=1 then putlog "&sysmacroname entry vars:"; + set sashelp.vmacro; + where scope="&sysmacroname"; + put name '=' value; +run; + +%mp_abort(iftrue= (&ds=0 and &action ne MAKETABLE) + ,mac=&sysmacroname + ,msg=%str(dataset was not provided) +) +%mp_abort(iftrue= (&ctl_ds=0) + ,mac=&sysmacroname + ,msg=%str(Control dataset was not provided) +) + +/* set up lib & mac vars */ +%let lib=%upcase(&lib); +%let ds=%upcase(&ds); +%let action=%upcase(&action); +%local user x trans msg abortme; +%let user=%mf_getuser(); +%let abortme=0; + +%mp_abort(iftrue= (&action ne LOCK & &action ne UNLOCK & &action ne MAKETABLE) + ,mac=&sysmacroname + ,msg=%str(Invalid action (&action) provided) +) + +/* if an err condition exists, exit before we even begin */ +%mp_abort(iftrue= (&syscc>0 and &action=LOCK) + ,mac=&sysmacroname + ,msg=%str(aborting due to syscc=&syscc on LOCK entry) +) + +/* do not bother locking work tables (else may affect all WORK libraries) */ +%if (%upcase(&lib)=WORK or %str(&lib)=%str()) & &action ne MAKETABLE %then %do; + %put NOTE: WORK libraries will not be registered in the locking system.; + %return; +%end; + +/* do not proceed if no observations can be processed */ +%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0) + ,mac=&sysmacroname + ,msg=%str(options obs = 0. syserrortext=&syserrortext) +) + +%if &ACTION=LOCK %then %do; + + /* abort if a SAS lock is already in place, or cannot be applied */ + %mp_lockfilecheck(&lib..&ds) + + /* next, check there is a record for this table */ + %local record_exists_check; + proc sql noprint; + select count(*) into: record_exists_check from &ctl_ds + where LOCK_LIB ="&lib" and LOCK_DS="&ds"; + quit; + %if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc; + %if &record_exists_check=0 %then %do; + data _null_; + putlog "&sysmacroname: adding record to lock table.."; + run; + + data ; + if 0 then set &ctl_ds; + LOCK_LIB ="&lib"; + LOCK_DS="&ds"; + LOCK_STATUS_CD='LOCKED'; + LOCK_START_DTTM="%sysfunc(datetime(),E8601DT26.6)"dt; + LOCK_USER_NM="&user"; + LOCK_PID="&sysjobid"; + LOCK_REF="&ref"; + output;stop; + run; + %let trans=&syslast; + proc append base=&ctl_ds data=&trans; + run; + %end; + /* if record does exist, perform lock attempts */ + %else %do x=1 %to &loops; + data _null_; + putlog "&sysmacroname: attempting lock (iteration &x) "@; + putlog "at %sysfunc(datetime(),datetime19.) .."; + run; + + proc sql; + update &ctl_ds + set LOCK_STATUS_CD='LOCKED' + , LOCK_START_DTTM="%sysfunc(datetime(),E8601DT26.6)"dt + , LOCK_USER_NM="&user" + , LOCK_PID="&sysjobid" + , LOCK_REF="&ref" + where LOCK_LIB ="&lib" and LOCK_DS="&ds"; + quit; + /** + * NOTE - occasionally SQL server will return an err code (deadlocked + * transaction). If so, ignore it, keep calm, and carry on.. + */ + %if &syscc>0 %then %do; + data _null_; + putlog 'NOTE-' / 'NOTE-'; + putlog "NOTE- &sysmacroname: Update failed. "@; + putlog "Resetting err conditions and re-attempting."; + putlog "NOTE- syscc=&syscc syserr=&syserr sqlrc=&sqlrc"; + putlog 'NOTE-' / 'NOTE-'; + run; + %let syscc=0; + %let sqlrc=0; + %end; + + /* now check if the record was successfully updated */ + %local success_check; + proc sql noprint; + select count(*) into: success_check from &ctl_ds + where LOCK_LIB ="&lib" and LOCK_DS="&ds" + and LOCK_PID="&sysjobid" and LOCK_STATUS_CD='LOCKED'; + quit; + %if &success_check=0 %then %do; + %if &x < &loops %then %do; + /* pause before next check */ + data _null_; + putlog 'NOTE-' / 'NOTE-'; + putlog "NOTE- &sysmacroname: table locked, waiting "@; + putlog "%sysfunc(sleep(&loop_inc)) seconds.. "; + putlog "NOTE- (iteration &x of &loops)"; + putlog 'NOTE-' / 'NOTE-'; + run; + %end; + %else %do; + %let msg=Unable to lock &lib..&ds via &ctl_ds after &loops attempts.\n + Please ask your administrator to investigate!; + %let abortme=1; + %end; + %end; + %else %do; + data _null_; + putlog 'NOTE-' / 'NOTE-'; + putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@ + putlog " %sysfunc(datetime(),datetime19.) (iteration &x)"@; + putlog 'NOTE-' / 'NOTE-'; + run; + %if &syscc>0 %then %do; + %put setting syscc(&syscc) back to 0; + %let syscc=0; + %end; + %let x=&loops; /* no more iterations needed */ + %end; + %end; +%end; +%else %if &ACTION=UNLOCK %then %do; + %local status; + proc sql noprint; + select LOCK_STATUS_CD into: status from &ctl_ds + where LOCK_LIB ="&lib" and LOCK_DS="&ds"; + quit; + %if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc; + %if &status=LOCKED %then %do; + data _null_; + putlog "&sysmacroname: unlocking &lib..&ds:"; + run; + proc sql; + update &ctl_ds + set LOCK_STATUS_CD='UNLOCKED' + , LOCK_END_DTTM="%sysfunc(datetime(),E8601DT26.6)"dt + , LOCK_USER_NM="&user" + , LOCK_PID="&sysjobid" + , LOCK_REF="&ref" + where LOCK_LIB ="&lib" and LOCK_DS="&ds"; + quit; + %end; + %else %if &status=UNLOCKED %then %do; + %put %str(WAR)NING: &lib..&ds is already unlocked!; + %end; + %else %do; + %put NOTE: Unrecognised STATUS_CD (&status) in &ctl_ds; + %let abortme=1; + %end; +%end; +%else %if &action=MAKETABLE %then %do; + proc sql; + create table &ctl_ds( + lock_lib char(8), + lock_ds char(32), + lock_status_cd char(10) not null, + lock_user_nm char(100) not null , + lock_ref char(200), + lock_pid char(10), + lock_start_dttm num format=E8601DT26.6, + lock_end_dttm num format=E8601DT26.6, + constraint pk_mp_lockanytable primary key(lock_lib,lock_ds)); +%end; +%else %do; + %let msg=lock_anytable given unsupported action (&action); + %let abortme=1; +%end; + +/* catch errors - mp_abort must be called outside of a logic block */ +%mp_abort(iftrue=(&abortme=1), + msg=%superq(msg), + mac=&sysmacroname +) + +%exit_macro: +data _null_; + put "&sysmacroname: Exit vars: action=&action lib=&lib ds=&ds"; + put " syscc=&syscc sqlrc=&sqlrc syserr=&syserr"; +run; +%mend mp_lockanytable; + + diff --git a/base/mp_lockfilecheck.sas b/base/mp_lockfilecheck.sas new file mode 100644 index 0000000..0a2ae82 --- /dev/null +++ b/base/mp_lockfilecheck.sas @@ -0,0 +1,97 @@ +/** + @file + @brief Aborts if a SAS lock file is in place, or if one cannot be applied. + @details Used in conjuction with the mp_lockanytable macro. + More info here: https://sasensei.com/flash/24 + + Usage: + + data work.test; a=1;run; + %mp_lockfilecheck(work.test) + + @param [in] libds The libref.dataset for which to check the lock status + +

SAS Macros

+ @li mp_abort.sas + @li mf_getattrc.sas + +

Related Macros

+ @li mp_lockanytable.sas + @li mp_lockfilecheck.test.sas + + @version 9.2 +**/ + +%macro mp_lockfilecheck( + libds +)/*/STORE SOURCE*/; + +data _null_; + if _n_=1 then putlog "&sysmacroname entry vars:"; + set sashelp.vmacro; + where scope="&sysmacroname"; + put name '=' value; +run; + +%mp_abort(iftrue= (&syscc>0) + ,mac=checklock.sas + ,msg=Aborting with syscc=&syscc on entry. +) +%mp_abort(iftrue= (&libds=0) + ,mac=&sysmacroname + ,msg=%str(libds not provided) +) + +%local msg lib ds; +%let lib=%upcase(%scan(&libds,1,.)); +%let ds=%upcase(%scan(&libds,2,.)); + +/* do not proceed if no observations can be processed */ +%let msg=options obs = 0. syserrortext=%superq(syserrortext); +%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0) + ,mac=checklock.sas + ,msg=%superq(msg) +) + +data _null_; + putlog "Checking engine & member type"; +run; +%local engine memtype; +%let memtype=%mf_getattrc(&libds,MTYPE); +%let engine=%mf_getattrc(&libds,ENGINE); + +%if &engine ne V9 and &engine ne BASE %then %do; + data _null_; + putlog "Lib &lib is not assigned using BASE engine - uses &engine instead"; + putlog "SAS lock check will not be performed"; + run; + %return; +%end; +%else %if &memtype ne DATA %then %do; + %put NOTE: Cannot lock a VIEW!! Memtype=&memtype; + %return; +%end; + +data _null_; + putlog "Engine = &engine, memtype=&memtype"; + putlog "Attempting lock statement"; +run; + +lock &libds; + +%local abortme; +%let abortme=0; +%if &syscc>0 or &SYSLCKRC ne 0 %then %do; + %let msg=Unable to apply lock on &libds (SYSLCKRC=&SYSLCKRC syscc=&syscc); + %put %str(ERR)OR: &sysmacroname: &msg; + %let abortme=1; +%end; + +lock &libds clear; + +%mp_abort(iftrue= (&abortme=1) + ,mac=&sysmacroname + ,msg=%superq(msg) +) + +%mend mp_lockfilecheck; \ No newline at end of file diff --git a/tests/crossplatform/mp_lockanytable.test.sas b/tests/crossplatform/mp_lockanytable.test.sas new file mode 100644 index 0000000..0d1c158 --- /dev/null +++ b/tests/crossplatform/mp_lockanytable.test.sas @@ -0,0 +1,62 @@ +/** + @file + @brief Testing mp_lockfilecheck macro + +

SAS Macros

+ @li mp_lockanytable.sas + @li mp_assertcols.sas + @li mp_assertcolvals.sas + +**/ + +/* check create table */ + +%mp_lockanytable(MAKETABLE, ctl_ds=work.controller) + +%mp_assertcols(work.controller, + cols=lock_status_cd lock_lib lock_ds lock_user_nm lock_ref lock_pid + lock_start_dttm lock_end_dttm, + test=ALL, + desc=check all control columns exist +) + +/* check lock table */ +options dlcreatedir; +libname tmp "%sysfunc(pathname(work))/tmp"; +data tmp.sometable; + x=1; +run; + +%mp_lockanytable(LOCK,lib=tmp,ds=sometable,ref=This Ref, ctl_ds=work.controller) + +data work.checkds1; + checkval='SOMETABLE'; +run; +%mp_assertcolvals(work.controller.lock_ds, + checkvals=work.checkds1.checkval, + desc=table is captured in lock, + test=ANYVAL +) + +data work.checkds2; + checkval='LOCKED'; +run; +%mp_assertcolvals(work.controller.lock_status_cd, + checkvals=work.checkds2.checkval, + desc=code is captured in lock, + test=ANYVAL +) + + + +/* check for unsuccessful unlock */ +%mp_lockanytable(UNLOCK,lib=tmp,ds=sometable,ref=bye, ctl_ds=work.controller) + +data work.checkds3; + checkval='UNLOCKED'; +run; +%mp_assertcolvals(work.controller.lock_status_cd, + checkvals=work.checkds3.checkval, + desc=Ref is captured in unlock, + test=ANYVAL +) diff --git a/tests/crossplatform/mp_lockfilecheck.test.sas b/tests/crossplatform/mp_lockfilecheck.test.sas new file mode 100644 index 0000000..a6a612d --- /dev/null +++ b/tests/crossplatform/mp_lockfilecheck.test.sas @@ -0,0 +1,36 @@ +/** + @file + @brief Testing mp_lockfilecheck macro + +

SAS Macros

+ @li mp_lockfilecheck.sas + @li mp_assert.sas + +**/ + + +/* check for regular lock */ +data work.test; a=1;run; +%mp_lockfilecheck(work.test) + +%mp_assert( + iftrue=(&syscc=0), + desc=Checking regular table can be locked, + outds=work.test_results +) + + +/* check for unsuccessful lock */ +%global success abortme; +%let success=0; +%macro mp_abort(iftrue=,mac=,msg=); + %if &abortme=1 %then %let success=1; +%mend mp_abort; + +%mp_lockfilecheck(sashelp.class) + +%mp_assert( + iftrue=(&success=1), + desc=Checking sashelp table cannot be locked, + outds=work.test_results +) diff --git a/tests/testinit.sas b/tests/testinit.sas index 611a982..4b4633c 100644 --- a/tests/testinit.sas +++ b/tests/testinit.sas @@ -5,4 +5,12 @@ **/ /* location in metadata or SAS Drive for temporary files */ -%let mcTestAppLoc=/Public/temp/macrocore; \ No newline at end of file +%let mcTestAppLoc=/Public/temp/macrocore; + +%macro loglevel(); + %if &_debug=2477 %then %do; + options mprint; + %end; +%mend loglevel; + +%loglevel() \ No newline at end of file