From d0a5780cd18eb5d33d40478790a484099d7e71a0 Mon Sep 17 00:00:00 2001 From: 4gl <@> Date: Mon, 27 Apr 2026 17:29:07 +0100 Subject: [PATCH] feat: adding tests, adding param to mfv_existsashdat, updating README --- README.md | 2 +- base/mp_assert.sas | 1 + tests/viyaonly/mfv_existsashdat.test.sas | 99 ++++++++++++++++++++++++ tests/viyaonly/mv_castabload.test.sas | 2 +- viya/mfv_existsashdat.sas | 15 ++-- viya/mv_castabload.sas | 21 +++-- 6 files changed, 127 insertions(+), 13 deletions(-) create mode 100644 tests/viyaonly/mfv_existsashdat.test.sas diff --git a/README.md b/README.md index ef1b40b..898c450 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ When contributing to this library, it is therefore important to ensure that all - All dataset references must be 2 level (eg `work.blah`, not `blah`). This is to avoid contention when options [DATASTMTCHK](https://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000279064.htm)=ALLKEYWORDS is in effect, or the [USER](https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/lrcon/n18m1vkqmeo4esn1moikt23zhp8s.htm) library is active. - Avoid naming collisions! All macro variables should be local scope. Use system generated work tables where possible - eg `data ; set sashelp.class; run; data &output; set &syslast; run;` - Where global macro variables are absolutely necessary, they should make use of `&sasjs_prefix` - see mp_init.sas -- The use of `quit;` for `proc sql` is optional unless you are looking to benefit from the timing statistics. +- The use of `quit;` for `proc sql` is essential, to avoid `WARNING: You cannot disconnect or terminate session XXXX until the procedure completes.` when terminating CAS sessions in Viya. - Use [sasjs lint](https://github.com/sasjs/lint)! ## General Notes diff --git a/base/mp_assert.sas b/base/mp_assert.sas index 86fdf56..e30e387 100644 --- a/base/mp_assert.sas +++ b/base/mp_assert.sas @@ -52,5 +52,6 @@ run; proc sql; drop table &ds; + quit; %mend mp_assert; \ No newline at end of file diff --git a/tests/viyaonly/mfv_existsashdat.test.sas b/tests/viyaonly/mfv_existsashdat.test.sas new file mode 100644 index 0000000..1dd2426 --- /dev/null +++ b/tests/viyaonly/mfv_existsashdat.test.sas @@ -0,0 +1,99 @@ +/** + @file + @brief Testing mfv_existsashdat macro function + +

SAS Macros

+ @li mf_uid.sas + @li mfv_existsashdat.sas + @li mp_assert.sas + @li mp_assertscope.sas + +**/ + +options mprint; + +/* ------------------------------------------------------------------------ */ +/* Setup: start a CAS session and stage a sashdat file in the Public caslib */ +/* ------------------------------------------------------------------------ */ +cas mysess; +caslib _all_ assign; + +%let testcaslib = Public; /* change this if Public isn't available */ +proc cas; + table.caslibInfo result=r / ; + found = 0; + do row over r.CASLibInfo; + if upcase(row.Name) = upcase("&testcaslib") then found = 1; + end; + if found = 0 then do; + print "ERROR: caslib &testcaslib not available"; + exit; + end; +quit; +%put NOTE: Using testcaslib=&testcaslib; + +%let tab1=T%mf_uid(); + +proc casutil; + load data=sashelp.baseball outcaslib="&testcaslib" casout="&tab1" replace; + save casdata="&tab1" incaslib="&testcaslib" + casout="&tab1..sashdat" outcaslib="&testcaslib" replace; + droptable casdata="&tab1" incaslib="&testcaslib" quiet; +quit; + + +/* ------------------------------------------------------------------------ */ +%put TEST 1 - returns 1 when the sashdat file exists in the caslib; +/* ------------------------------------------------------------------------ */ +%mp_assert( + iftrue=(%mfv_existsashdat(&testcaslib..&tab1)=1), + desc=Test 1 - Check returns 1 for a sashdat that exists +) + +/* ------------------------------------------------------------------------ */ +%put TEST 2 - returns 0 when the file does not exist in the caslib; +/* ------------------------------------------------------------------------ */ +%mp_assertscope(SNAPSHOT) +%mp_assert( + iftrue=(%mfv_existsashdat(&testcaslib..DOESNOTEXIST_%mf_uid())=0), + desc=Check returns 0 for a sashdat that does not exist +) +%mp_assertscope(COMPARE, + desc=Check mfv_existsashdat does not leak macro variables into GLOBAL scope +) + +/* ------------------------------------------------------------------------ */ +%put TEST 3 - usecache= controls whether the cached dataset is reused; +/* ------------------------------------------------------------------------ */ + +/* First call: populates the cache dataset work.testcache_&testcaslib */ +%let _rc=%mfv_existsashdat(&testcaslib..&tab1,outprefix=work.testcache); + +/* Delete the sashdat from the caslib so a fresh scan would return 0 */ +proc casutil; + deletesource casdata="&tab1..sashdat" incaslib="&testcaslib" quiet; +quit; + +/* usecache=1 (default): must return 1 from the cached dataset */ +%mp_assert( + iftrue=(%mfv_existsashdat(&testcaslib..&tab1,outprefix=work.testcache)=1), + desc=Check returns 1 from cache even after source file is deleted +) + +/* usecache=0: forces rescan and reflects the deletion */ +%mp_assert( + iftrue=( + %mfv_existsashdat(&testcaslib..&tab1,usecache=0,outprefix=work.testcache)=0 + ), + desc=Check returns 0 when usecache=0 forces a rescan after source file deleted +) + +%let syscc=0; + + +/* ------------------------------------------------------------------------ */ +/* Teardown: terminate CAS session (sashdat already removed in TEST 4) */ +/* ------------------------------------------------------------------------ */ +cas mysess terminate; + +%let syscc=0; diff --git a/tests/viyaonly/mv_castabload.test.sas b/tests/viyaonly/mv_castabload.test.sas index d8dd200..8f5ca17 100644 --- a/tests/viyaonly/mv_castabload.test.sas +++ b/tests/viyaonly/mv_castabload.test.sas @@ -69,7 +69,7 @@ quit; /* ------------------------------------------------------------------------ */ -%put TEST 2 - load a table that does not yet exist (default srcfile=table.hdat); +%put TEST 2 - load a table that does not yet exist (default srcfile=table.sashdat); /* ------------------------------------------------------------------------ */ %mv_castabload(caslib=&testcaslib,table=&tab1,mdebug=1) diff --git a/viya/mfv_existsashdat.sas b/viya/mfv_existsashdat.sas index 5a256c9..84d47cd 100644 --- a/viya/mfv_existsashdat.sas +++ b/viya/mfv_existsashdat.sas @@ -6,14 +6,17 @@ %if %mfv_existsashdat(libds=casuser.sometable) %then %put yes it does!; The function uses `dosubl()` to run the `table.fileinfo` action, for the - specified library, filtering for `*.sashdat` tables. The results are stored - in a WORK table (&outprefix._&lib). If that table already exists, it is - queried instead, to avoid the dosubl() performance hit. + specified library, filtering for `*.sashdat` tables. + + IMPORTANT NOTE - The results are cached in a WORK table (&outprefix._&lib). + If that table already exists, it is queried instead, to avoid the + dosubl() performance hit. To force a rescan, just use a new `&outprefix` value, or delete the table(s) before running the function. @param [in] libds library.dataset + @param [in] usecache= (1) Set to 0 to rebuild the cache @param [out] outprefix= (work.mfv_existsashdat) Used to store current HDATA tables to improve subsequent query performance. This reference is a prefix and is converted to `&prefix._{libref}` @@ -24,14 +27,14 @@ @author Mathieu Blauw **/ -%macro mfv_existsashdat(libds,outprefix=work.mfv_existsashdat +%macro mfv_existsashdat(libds,usecache=1,outprefix=work.mfv_existsashdat ); %local rc dsid name lib ds; %let lib=%upcase(%scan(&libds,1,'.')); %let ds=%upcase(%scan(&libds,-1,'.')); /* if table does not exist, create it */ -%if %sysfunc(exist(&outprefix._&lib)) ne 1 %then %do; +%if &usecache ne 1 or %sysfunc(exist(&outprefix._&lib)) ne 1 %then %do; %let rc=%sysfunc(dosubl(%nrstr( /* Read in table list (once per &lib per session) */ proc cas; @@ -41,7 +44,7 @@ quit; /* Only keep name, without file extension */ data &outprefix._&lib; - set &outprefix._&lib(where=(Name like '%.sashdat') keep=Name); + set &outprefix._&lib(where=(upcase(Name) like '%.SASHDAT') keep=Name); Name=upcase(scan(Name,1,'.')); run; ))); diff --git a/viya/mv_castabload.sas b/viya/mv_castabload.sas index bc141fe..c7f4fd2 100644 --- a/viya/mv_castabload.sas +++ b/viya/mv_castabload.sas @@ -19,7 +19,7 @@ @param [in] caslib= CASLIB containing the source file @param [in] table= Name to give the in-memory CAS table @param [in] srcfile= (0) Source file name.ext in the caslib. If not provided, - the code assumes that srcfile=&table..hdat + the code assumes that srcfile=&table..sashdat @param [in] mdebug= (0) Set to 1 to enable verbose logging: - echoes resolved parameters - prints tableExists result @@ -28,7 +28,11 @@ @returns Sets global macro variable `MV_CASTABLOAD_RC`: 0 = table already existed (no load performed) 1 = table was loaded & promoted successfully - 3 = action failed + 3 = action failed (including source file missing) + +

SAS Macros

+ @li mfv_existsashdat.sas + **/ %macro mv_castabload( @@ -49,7 +53,7 @@ %put %str(ERR)OR: caslib=, table= and srcfile= are all required; %return; %end; -%if "&srcfile"="0" %then %let srcfile=&table..hdat; +%if "&srcfile"="0" %then %let srcfile=&table..sashdat; %if &mdebug=1 %then %do; %put &=caslib; @@ -58,6 +62,13 @@ options mprint notes; %end; +/* ---- check source file exists ------------------------------------------ */ +%if not %mfv_existsashdat(&caslib..%scan(&srcfile,1,.)) %then %do; + %put %str(ERR)OR: Source file "&srcfile" not found in caslib "&caslib"; + %let MV_CASTABLOAD_RC=3; + %return; +%end; + /* ---- existence check --------------------------------------------------- */ proc cas; table.tableExists result=r / @@ -92,11 +103,11 @@ quit; %put NOTE: Table &caslib..&table already loaded - skipping; %else %if &MV_CASTABLOAD_RC=1 %then %put NOTE: Table &caslib..&table loaded and promoted; -%else %put ERROR: load failed for &caslib..&table; +%else %put %str(ERR)OR: load failed for &caslib..&table; /* ---- restore options --------------------------------------------------- */ %if &mdebug=1 %then %do; options &_sysopts; %end; -%mend mv_castabload; +%mend mv_castabload; \ No newline at end of file