1
0
mirror of https://github.com/sasjs/core.git synced 2025-12-13 23:44:35 +00:00

Compare commits

...

17 Commits

Author SHA1 Message Date
Allan Bowe
42a46b32e9 Merge pull request #96 from sasjs/add_deletefolder
feat: Adding Delete Folder Macro
2021-11-26 16:34:03 +00:00
Allan Bowe
3b395b3ae5 chore: all.sas update 2021-11-26 16:20:33 +00:00
Allan Bowe
9e84e47563 feat: mp_deletefolder tests, closes #90 2021-11-26 16:20:13 +00:00
Allan Bowe
aff29427e2 chore: merge main 2021-11-26 15:37:08 +00:00
Ivor Townsend
474f1b3cc6 feat: Adding Delete Folder Macro 2021-11-26 15:06:38 +00:00
Allan Bowe
22bf8065bb Merge pull request #95 from sasjs/dirlist_enhancement
feat: Recursive Directory Listing
2021-11-26 13:00:15 +00:00
Allan Bowe
7bb089e269 fix: moving cleanup to temp table for efficiency 2021-11-26 11:55:55 +00:00
Allan Bowe
a6e9158814 chore: adding recursive option to the mp_dirlist macro, and updating all.sas 2021-11-26 11:52:48 +00:00
Ivor Townsend
19a1336720 feat: Recursive Directory Listing 2021-11-26 09:29:42 +00:00
Allan Bowe
79d5d42e6b Merge pull request #94 from sasjs/issue93
Macros for locking / unlocking tables
2021-11-25 17:44:37 +00:00
Allan Bowe
2d81de5841 chore: generating all.sas and updating doc links 2021-11-25 16:46:56 +00:00
Allan Bowe
107ab836d6 feat: mp_lockXXX macros f
These macros provide a centralised approach for locking tables where updates can happen in multiple passes.  The mp_lockfilecheck macro will also verify the ability to create a SAS lock (if a v9 library engine, and not a view)
2021-11-25 16:43:39 +00:00
Allan Bowe
5225e74465 chore: adding @vznesh for issue #88 2021-11-18 18:40:32 +00:00
Allan Bowe
39253d2828 chore: merge conflicts 2021-11-18 18:36:32 +00:00
Allan Bowe
171c169537 chore: updating all.sas 2021-11-18 18:36:08 +00:00
Allan Bowe
76af9fa33c Merge pull request #89 from sasjs/issue88
fix: removing notes when running mp_zip, closes #88. .
2021-11-18 18:35:33 +00:00
Allan Bowe
37ccc8a2aa fix: removing notes when running mp_zip, closes #88. .
Also adding 3 tests
2021-11-18 18:21:43 +00:00
14 changed files with 1373 additions and 60 deletions

View File

@@ -108,6 +108,15 @@
"test",
"review"
]
},
{
"login": "vznesh",
"name": "Vignesh T.",
"avatar_url": "https://avatars.githubusercontent.com/u/28916792?v=4",
"profile": "https://github.com/vznesh",
"contributions": [
"bug"
]
}
],
"contributorsPerLine": 7,

View File

@@ -189,7 +189,7 @@ If you find this library useful, please leave a [star](https://github.com/sasjs/
## Contributors ✨
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-9-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-10-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
@@ -209,6 +209,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<tr>
<td align="center"><a href="https://github.com/kkchandok"><img src="https://avatars.githubusercontent.com/u/46090627?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kkchandok</b></sub></a><br /><a href="#ideas-kkchandok" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/VladislavParhomchik"><img src="https://avatars.githubusercontent.com/u/83717836?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vladislav Parhomchik</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=VladislavParhomchik" title="Tests">⚠️</a> <a href="https://github.com/sasjs/core/pulls?q=is%3Apr+reviewed-by%3AVladislavParhomchik" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/vznesh"><img src="https://avatars.githubusercontent.com/u/28916792?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vignesh T.</b></sub></a><br /><a href="https://github.com/sasjs/core/issues?q=author%3Avznesh" title="Bug reports">🐛</a></td>
</tr>
</table>

573
all.sas
View File

@@ -3066,24 +3066,85 @@ data &outds;
run;
%mend mp_deleteconstraints;/**
@file
@brief A macro to delete a directory
@details Will delete all folder content (including subfolder content) and
finally, the folder itself.
Usage:
%let rootdir=%sysfunc(pathname(work))/demo;
%mf_mkdir(&rootdir)
%mf_mkdir(&rootdir/subdir)
%mf_mkdir(&rootdir/subdir/subsubdir)
data "&rootdir/subdir/example.sas7bdat";
run;
%mp_deletefolder(&rootdir)
@param path Unquoted path to the folder to delete.
<h4> SAS Macros </h4>
@li mf_getuniquename.sas
@li mf_isdir.sas
@li mp_dirlist.sas
<h4> Related Macros </h4>
@li mp_deletefolder.test.sas
**/
%macro mp_deletefolder(folder);
/* proceed if valid directory */
%if %mf_isdir(&folder)=1 %then %do;
/* prep temp table */
%local tempds;
%let tempds=%mf_getuniquename();
/* recursive directory listing */
%mp_dirlist(path=&folder,outds=work.&tempds, maxdepth=MAX)
/* sort descending level so can delete folder contents before folders */
proc sort data=work.&tempds;
by descending level;
run;
/* ensure top level folder is removed at the end */
proc sql;
insert into work.&tempds set filepath="&folder";
/* delete everything */
data _null_;
set work.&tempds end=last;
length fref $8;
rc=filename(fref,filepath);
rc=fdelete(fref);
if rc then do;
msg=sysmsg();
put "&sysmacroname:" / rc= / msg= / filepath=;
end;
rc=filename(fref);
run;
/* tidy up */
proc sql;
drop table work.&tempds;
%end;
%else %put &sysmacroname: &folder: is not a valid / accessible folder. ;
%mend mp_deletefolder;/**
@file
@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 +3158,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 +3180,15 @@ 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
<h4> Related Macros </h4>
@li mp_dirlist.test.sas
@version 9.2
@author Allan Bowe
**/
@@ -3121,14 +3197,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(%scan(&outds,-1,.), 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 +3275,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 +3317,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;
data &out_ds;
set &out_ds(where=(filepath ne ''));
run;
/* update main table */
proc append base=&outds data=&out_ds;
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 +6318,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
@@ -7567,10 +8032,10 @@ run;
@file mp_unzip.sas
@brief Unzips a zip file
@details Opens the zip file and copies all the contents to another directory.
It is not possible to retain permissions / timestamps, also the BOF marker
is lost so it cannot extract binary files.
It is not possible to retain permissions / timestamps, also the BOF marker
is lost so it cannot extract binary files.
Usage:
Usage:
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc;
@@ -7581,8 +8046,9 @@ run;
@li mf_mkdir.sas
@li mf_getuniquefileref.sas
@param ziploc= fileref or quoted full path to zip file ("/path/to/file.zip")
@param outdir= directory in which to write the outputs (created if non existant)
@param ziploc= Fileref or quoted full path to zip file ("/path/to/file.zip")
@param outdir= (%sysfunc(pathname(work))) Directory in which to write the
outputs (created if non existant)
@version 9.4
@author Allan Bowe
@@ -7600,7 +8066,8 @@ run;
%let fname2=%mf_getuniquefileref();
%let fname3=%mf_getuniquefileref();
filename &fname1 ZIP &ziploc; * Macro variable &datazip would be read from the file*;
/* Macro variable &datazip would be read from the file */
filename &fname1 ZIP &ziploc;
/* Read the "members" (files) from the ZIP file */
data _data_(keep=memname isFolder);
@@ -7788,6 +8255,42 @@ alter table &libds modify &var char(&len);
%mend mp_validatecol;
/**
@file
@brief Wait until a file arrives before continuing execution
@details Loops with a `sleep()` command until a file arrives or the max wait
period expires.
@example
Wait 3 minutes OR for /tmp/flag.txt to appear
%mp_wait4file(/tmp/flag.txt , maxwait=60*3)
@param [in] file The file to wait for. Must be provided.
@param [in] maxwait= (0) Number of seconds to wait. If set to zero, will
loop indefinitely (to a maximum of 46 days, per SAS [documentation](
https://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a001418809.htm
)). Otherwise, execution will proceed upon sleep expiry.
@param [in] interval= (1) The wait period between sleeps, in seconds
**/
%macro mp_wait4file(file, maxwait=0, interval=1);
%if %str(&file)=%str() %then %do;
%put %str(ERR)OR: file not provided;
%end;
data _null_;
maxwait=&maxwait;
if maxwait=0 then maxwait=60*60*24*46;
do until (fileexist("&file") or slept>maxwait );
slept=sum(slept,sleep(&interval,1));
end;
run;
%mend mp_wait4file;/**
@file
@brief Fix the `_WEBIN` variables provided to SAS web services
@details When uploading files to SAS Stored Processes or Viya Jobs a number
@@ -7863,11 +8366,18 @@ alter table &libds modify &var char(&len);
@li mp_dirlist.sas
@param in= unquoted filepath, dataset of files or directory to zip
@param type= FILE, DATASET, DIRECTORY. (FILE / DATASET not ready yet)
@param outname= output file to create, without .zip extension
@param outpath= location for output zip file
@param type= (FILE) Valid values:
@li FILE - /full/path/and/filename.extension to a particular file
@li DATASET - a dataset containing a list of files to zip (see `incol`)
@li DIRECTORY - a directory to zip
@param outname= (FILE) Output file to create, _without_ .zip extension
@param outpath= (%sysfunc(pathname(WORK))) Parent folder for output zip file
@param incol= if DATASET input, say which column contains the filepath
<h4> Related Macros </h4>
@li mp_unzip.sas
@li mp_zip.test.sas
@version 9.2
@author Allan Bowe
@source https://github.com/sasjs/core
@@ -7898,9 +8408,9 @@ ods package open nopf;
set &ds;
length __command $4000;
if file_or_folder='file';
command=cats('ods package add file="',filepath
__command=cats('ods package add file="',filepath
,'" mimetype="application/x-compress";');
call execute(command);
call execute(__command);
run;
/* tidy up */
%if &debug=NO %then %do;
@@ -7911,11 +8421,10 @@ ods package open nopf;
data _null_;
set &in;
length __command $4000;
command=cats('ods package add file="',&incol
__command=cats('ods package add file="',&incol
,'" mimetype="application/x-compress";');
call execute(command);
call execute(__command);
run;
ods package add file="&in" mimetype="application/x-compress";
%end;

69
base/mp_deletefolder.sas Normal file
View File

@@ -0,0 +1,69 @@
/**
@file
@brief A macro to delete a directory
@details Will delete all folder content (including subfolder content) and
finally, the folder itself.
Usage:
%let rootdir=%sysfunc(pathname(work))/demo;
%mf_mkdir(&rootdir)
%mf_mkdir(&rootdir/subdir)
%mf_mkdir(&rootdir/subdir/subsubdir)
data "&rootdir/subdir/example.sas7bdat";
run;
%mp_deletefolder(&rootdir)
@param path Unquoted path to the folder to delete.
<h4> SAS Macros </h4>
@li mf_getuniquename.sas
@li mf_isdir.sas
@li mp_dirlist.sas
<h4> Related Macros </h4>
@li mp_deletefolder.test.sas
**/
%macro mp_deletefolder(folder);
/* proceed if valid directory */
%if %mf_isdir(&folder)=1 %then %do;
/* prep temp table */
%local tempds;
%let tempds=%mf_getuniquename();
/* recursive directory listing */
%mp_dirlist(path=&folder,outds=work.&tempds, maxdepth=MAX)
/* sort descending level so can delete folder contents before folders */
proc sort data=work.&tempds;
by descending level;
run;
/* ensure top level folder is removed at the end */
proc sql;
insert into work.&tempds set filepath="&folder";
/* delete everything */
data _null_;
set work.&tempds end=last;
length fref $8;
rc=filename(fref,filepath);
rc=fdelete(fref);
if rc then do;
msg=sysmsg();
put "&sysmacroname:" / rc= / msg= / filepath=;
end;
rc=filename(fref);
run;
/* tidy up */
proc sql;
drop table work.&tempds;
%end;
%else %put &sysmacroname: &folder: is not a valid / accessible folder. ;
%mend mp_deletefolder;

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:
@@ -44,8 +45,15 @@
- 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
<h4> Related Macros </h4>
@li mp_dirlist.test.sas
@version 9.2
@author Allan Bowe
**/
@@ -54,14 +62,27 @@
, 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(%scan(&outds,-1,.), 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;
@@ -119,8 +140,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;
@@ -161,10 +182,35 @@ 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;
data &out_ds;
set &out_ds(where=(filepath ne ''));
run;
/* update main table */
proc append base=&outds data=&out_ds;
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;

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

@@ -16,11 +16,18 @@
@li mp_dirlist.sas
@param in= unquoted filepath, dataset of files or directory to zip
@param type= FILE, DATASET, DIRECTORY. (FILE / DATASET not ready yet)
@param outname= output file to create, without .zip extension
@param outpath= location for output zip file
@param type= (FILE) Valid values:
@li FILE - /full/path/and/filename.extension to a particular file
@li DATASET - a dataset containing a list of files to zip (see `incol`)
@li DIRECTORY - a directory to zip
@param outname= (FILE) Output file to create, _without_ .zip extension
@param outpath= (%sysfunc(pathname(WORK))) Parent folder for output zip file
@param incol= if DATASET input, say which column contains the filepath
<h4> Related Macros </h4>
@li mp_unzip.sas
@li mp_zip.test.sas
@version 9.2
@author Allan Bowe
@source https://github.com/sasjs/core
@@ -51,9 +58,9 @@ ods package open nopf;
set &ds;
length __command $4000;
if file_or_folder='file';
command=cats('ods package add file="',filepath
__command=cats('ods package add file="',filepath
,'" mimetype="application/x-compress";');
call execute(command);
call execute(__command);
run;
/* tidy up */
%if &debug=NO %then %do;
@@ -64,11 +71,10 @@ ods package open nopf;
data _null_;
set &in;
length __command $4000;
command=cats('ods package add file="',&incol
__command=cats('ods package add file="',&incol
,'" mimetype="application/x-compress";');
call execute(command);
call execute(__command);
run;
ods package add file="&in" mimetype="application/x-compress";
%end;

View File

@@ -0,0 +1,50 @@
/**
@file
@brief Testing mp_deletefolder.sas macro
<h4> SAS Macros </h4>
@li mp_deletefolder.sas
@li mf_mkdir.sas
@li mf_nobs.sas
@li mp_assert.sas
@li mp_dirlist.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=Temp data successfully created,
outds=work.test_results
)
%mp_deletefolder(&root/a)
%mp_dirlist(path=&root, outds=work.myTable2, maxdepth=MAX)
data _null_;
set work.mytable2;
putlog (_all_)(=);
run;
%mp_assert(
iftrue=(%mf_nobs(work.mytable2)=1),
desc=Subfolder and contents successfully deleted,
outds=work.test_results
)

View File

@@ -0,0 +1,50 @@
/**
@file
@brief Testing mp_dirlist.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=work.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

@@ -0,0 +1,115 @@
/**
@file
@brief Testing mp_zip macro
<h4> SAS Macros </h4>
@li mf_mkdir.sas
@li mp_assert.sas
@li mp_zip.sas
@li mp_unzip.sas
**/
%let work=%sysfunc(pathname(work));
%let root=&work/zipme;
/* TEST 1 - zip a file */
%mf_mkdir(&root)
data _null_;
file "&root/test.txt";
put "houston, this is a test";
run;
%mp_zip(in=&root/test.txt
,type=FILE
,outpath=&work
,outname=myFile
)
%mp_unzip(ziploc="&work/myFile.zip",outdir=&work)
data _null_;
infile "&work/test.txt";
input;
call symputx('content1',_infile_);
putlog _infile_;
run;
%mp_assert(
iftrue=(
%str(&content1)=%str(houston, this is a test)
),
desc=Checking if file zip / unzip works,
outds=work.test_results
)
/* TEST 2 - zip a dataset of files */
data _null_;
file "&root/test2.txt";
put "houston, this is test2";
run;
libname tmp "&root";
data tmp.test;
filepath="&root/test2.txt";
run;
%mp_zip(in=tmp.test
,incol=filepath
,type=DATASET
,outpath=&work
,outname=myFile2
)
%mp_unzip(ziploc="&work/myFile2.zip",outdir=&work)
data _null_;
infile "&work/test2.txt";
input;
call symputx('content2',_infile_);
putlog _infile_;
run;
%mp_assert(
iftrue=(
%str(&content2)=%str(houston, this is test2)
),
desc=Checking if file zip / unzip from a dataset works,
outds=work.test_results
)
/* TEST 3 - zip a dataset of files */
%mf_mkdir(&work/out3)
%mp_zip(in=&root
,type=DIRECTORY
,outpath=&work
,outname=myFile3
)
%mp_unzip(ziploc="&work/myFile3.zip",outdir=&work/out3)
data _null_;
infile "&work/out3/test.txt";
input;
call symputx('content3a',_infile_);
putlog _infile_;
run;
data _null_;
infile "&work/out3/test2.txt";
input;
call symputx('content3b',_infile_);
putlog _infile_;
run;
%mp_assert(
iftrue=(
%str(&content3a)=%str(houston, this is a test)
and
%str(&content3b)=%str(houston, this is test2)
),
desc=Checking if file zip / unzip from a directory works,
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()