1
0
mirror of https://github.com/sasjs/core.git synced 2025-12-11 06:24:35 +00:00

chore: merge main

This commit is contained in:
Allan Bowe
2021-11-26 15:37:08 +00:00
8 changed files with 950 additions and 44 deletions

430
all.sas
View File

@@ -3070,20 +3070,13 @@ run;
@brief Returns all files and subdirectories within a specified parent
@details When used with getattrs=NO, is not OS specific (uses dopen / dread).
If getattrs=YES then the doptname / foptname functions are used to scan all
properties - any characters that are not valid in a SAS name (v7) are simply
stripped, and the table is transposed so theat each property is a column
and there is one file per row. An attempt is made to get all properties
whether a file or folder, but some files/folders cannot be accessed, and so
not all properties can / will be populated.
Credit for the rename approach:
https://communities.sas.com/t5/SAS-Programming/SAS-Function-to-convert-string-to-Legal-SAS-Name/m-p/27375/highlight/true#M5003
usage:
%mp_dirlist(path=/some/location,outds=myTable)
%mp_dirlist(path=/some/location, outds=myTable, maxdepth=MAX)
%mp_dirlist(outds=cwdfileprops, getattrs=YES)
@@ -3097,11 +3090,19 @@ run;
X CMD) do please raise an issue!
@param path= for which to return contents
@param fref= Provide a DISK engine fileref as an alternative to PATH
@param outds= the output dataset to create
@param getattrs= YES/NO (default=NO). Uses doptname and foptname to return
all attributes for each file / folder.
@param [in] path= for which to return contents
@param [in] fref= Provide a DISK engine fileref as an alternative to PATH
@param [in] maxdepth= (0) Set to a positive integer to indicate the level of
subdirectory scan recursion - eg 3, to go `./3/levels/deep`. For unlimited
recursion, set to MAX.
@param [out] outds= the output dataset to create
@param [out] getattrs= (NO) If getattrs=YES then the doptname / foptname
functions are used to scan all properties - any characters that are not
valid in a SAS name (v7) are simply stripped, and the table is transposed
so theat each property is a column and there is one file per row. An
attempt is made to get all properties whether a file or folder, but some
files/folders cannot be accessed, and so not all properties can / will be
populated.
@returns outds contains the following variables:
@@ -3111,8 +3112,12 @@ run;
- filename (just the file name)
- ext (.extension)
- msg (system message if any issues)
- level (depth of folder)
- OS SPECIFIC variables, if <code>getattrs=</code> is used.
<h4> SAS Macros </h4>
@li mp_dropmembers.sas
@version 9.2
@author Allan Bowe
**/
@@ -3121,14 +3126,27 @@ run;
, fref=0
, outds=work.mp_dirlist
, getattrs=NO
, maxdepth=0
, level=0 /* The level of recursion to perform. For internal use only. */
)/*/STORE SOURCE*/;
%let getattrs=%upcase(&getattrs)XX;
data &outds(compress=no
keep=file_or_folder filepath filename ext msg directory
/* temp table */
%local out_ds;
data;run;
%let out_ds=%str(&syslast);
/* drop main (top) table if it exists */
%if &level=0 %then %do;
%mp_dropmembers(&outds, libref=WORK)
%end;
data &out_ds(compress=no
keep=file_or_folder filepath filename ext msg directory level
);
length directory filepath $500 fref fref2 $8 file_or_folder $6 filename $80
ext $20 msg $200;
retain level &level;
%if &fref=0 %then %do;
rc = filename(fref, "&path");
%end;
@@ -3186,8 +3204,8 @@ data &outds(compress=no
run;
%if %substr(&getattrs,1,1)=Y %then %do;
data &outds;
set &outds;
data &out_ds;
set &out_ds;
length infoname infoval $60 fref $8;
rc=filename(fref,filepath);
drop rc infoname fid i close fref;
@@ -3228,12 +3246,37 @@ run;
run;
proc sort;
by filepath sasname;
proc transpose data=&outds out=&outds(drop=_:);
proc transpose data=&out_ds out=&out_ds(drop=_:);
id sasname;
var infoval;
by filepath file_or_folder filename ext ;
run;
%end;
/* update main table */
proc append base=&outds data=&out_ds;
run;
data &outds;
set &outds(where=(filepath ne ''));
run;
/* recursive call */
%if &maxdepth>&level or &maxdepth=MAX %then %do;
data _null_;
set &out_ds;
where file_or_folder='folder';
code=cats('%nrstr(%mp_dirlist(path=',filepath,",outds=&outds"
,",getattrs=&getattrs,level=%eval(&level+1),maxdepth=&maxdepth))");
put code=;
call execute(code);
run;
%end;
/* tidy up */
proc sql;
drop table &out_ds;
%mend mp_dirlist;/**
@file
@brief Creates a dataset containing distinct _formatted_ values
@@ -6204,6 +6247,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
<h4> SAS Macros </h4>
@li mp_abort.sas
@li mp_lockfilecheck.sas
@li mf_getuser.sas
<h4> Related Macros </h4>
@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
<h4> SAS Macros </h4>
@li mp_abort.sas
@li mf_getattrc.sas
<h4> Related Macros </h4>
@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

View File

@@ -3,20 +3,13 @@
@brief Returns all files and subdirectories within a specified parent
@details When used with getattrs=NO, is not OS specific (uses dopen / dread).
If getattrs=YES then the doptname / foptname functions are used to scan all
properties - any characters that are not valid in a SAS name (v7) are simply
stripped, and the table is transposed so theat each property is a column
and there is one file per row. An attempt is made to get all properties
whether a file or folder, but some files/folders cannot be accessed, and so
not all properties can / will be populated.
Credit for the rename approach:
https://communities.sas.com/t5/SAS-Programming/SAS-Function-to-convert-string-to-Legal-SAS-Name/m-p/27375/highlight/true#M5003
usage:
%mp_dirlist(path=/some/location,outds=myTable)
%mp_dirlist(path=/some/location, outds=myTable, maxdepth=MAX)
%mp_dirlist(outds=cwdfileprops, getattrs=YES)
@@ -30,11 +23,19 @@
X CMD) do please raise an issue!
@param path= for which to return contents
@param fref= Provide a DISK engine fileref as an alternative to PATH
@param outds= the output dataset to create
@param getattrs= YES/NO (default=NO). Uses doptname and foptname to return
all attributes for each file / folder.
@param [in] path= for which to return contents
@param [in] fref= Provide a DISK engine fileref as an alternative to PATH
@param [in] maxdepth= (0) Set to a positive integer to indicate the level of
subdirectory scan recursion - eg 3, to go `./3/levels/deep`. For unlimited
recursion, set to MAX.
@param [out] outds= the output dataset to create
@param [out] getattrs= (NO) If getattrs=YES then the doptname / foptname
functions are used to scan all properties - any characters that are not
valid in a SAS name (v7) are simply stripped, and the table is transposed
so theat each property is a column and there is one file per row. An
attempt is made to get all properties whether a file or folder, but some
files/folders cannot be accessed, and so not all properties can / will be
populated.
@returns outds contains the following variables:
@@ -58,6 +59,7 @@
, fref=0
, outds=work.mp_dirlist
, getattrs=NO
, maxdepth=0
, level=0 /* The level of recursion to perform. For internal use only. */
)/*/STORE SOURCE*/;
%let getattrs=%upcase(&getattrs)XX;
@@ -184,23 +186,25 @@ run;
run;
%end;
data &out_ds;
set &out_ds(where=(filepath ne ''));
run;
/* update main table */
proc append base=&outds data=&out_ds;
run;
data &outds;
set &outds(where=(filepath ne ''));
run;
/* recursive call */
data _null_;
set &out_ds;
where file_or_folder='folder';
code=cats('%nrstr(%mp_dirlist(path=',filepath,",outds=&outds"
,",getattrs=&getattrs,level=%eval(&level+1)))");
put code=;
call execute(code);
run;
%if &maxdepth>&level or &maxdepth=MAX %then %do;
data _null_;
set &out_ds;
where file_or_folder='folder';
code=cats('%nrstr(%mp_dirlist(path=',filepath,",outds=&outds"
,",getattrs=&getattrs,level=%eval(&level+1),maxdepth=&maxdepth))");
put code=;
call execute(code);
run;
%end;
/* tidy up */
proc sql;

255
base/mp_lockanytable.sas Normal file
View File

@@ -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
<h4> SAS Macros </h4>
@li mp_abort.sas
@li mp_lockfilecheck.sas
@li mf_getuser.sas
<h4> Related Macros </h4>
@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;

97
base/mp_lockfilecheck.sas Normal file
View File

@@ -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
<h4> SAS Macros </h4>
@li mp_abort.sas
@li mf_getattrc.sas
<h4> Related Macros </h4>
@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;

View File

@@ -0,0 +1,50 @@
/**
@file
@brief Testing mp_ds2cards.sas macro
<h4> SAS Macros </h4>
@li mf_nobs.sas
@li mf_mkdir.sas
@li mp_dirlist.sas
@li mp_assert.sas
**/
/**
* make a directory structure
*/
%let root=%sysfunc(pathname(work))/top;
%mf_mkdir(&root)
%mf_mkdir(&root/a)
%mf_mkdir(&root/b)
%mf_mkdir(&root/a/d)
%mf_mkdir(&root/a/e)
%mf_mkdir(&root/a/e/f)
data "&root/a/e/f/ds1.sas7bdat";
x=1;
run;
%mp_dirlist(path=&root, outds=myTable, maxdepth=MAX)
%mp_assert(
iftrue=(%mf_nobs(work.mytable)=6),
desc=All levels returned,
outds=work.test_results
)
%mp_dirlist(path=&root, outds=myTable2, maxdepth=2)
%mp_assert(
iftrue=(%mf_nobs(work.mytable2)=5),
desc=Top two levels returned,
outds=work.test_results
)
%mp_dirlist(path=&root, outds=myTable3, maxdepth=0)
%mp_assert(
iftrue=(%mf_nobs(work.mytable3)=2),
desc=Top level returned,
outds=work.test_results
)

View File

@@ -0,0 +1,62 @@
/**
@file
@brief Testing mp_lockfilecheck macro
<h4> SAS Macros </h4>
@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
)

View File

@@ -0,0 +1,36 @@
/**
@file
@brief Testing mp_lockfilecheck macro
<h4> SAS Macros </h4>
@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
)

View File

@@ -5,4 +5,12 @@
**/
/* location in metadata or SAS Drive for temporary files */
%let mcTestAppLoc=/Public/temp/macrocore;
%let mcTestAppLoc=/Public/temp/macrocore;
%macro loglevel();
%if &_debug=2477 %then %do;
options mprint;
%end;
%mend loglevel;
%loglevel()