diff --git a/tests/viyaonly/mv_castabload.test.sas b/tests/viyaonly/mv_castabload.test.sas new file mode 100644 index 0000000..d8dd200 --- /dev/null +++ b/tests/viyaonly/mv_castabload.test.sas @@ -0,0 +1,148 @@ +/** + @file + @brief Testing mv_castabload macro + +

SAS Macros

+ @li mf_uid.sas + @li mp_assert.sas + @li mp_assertscope.sas + @li mv_castabload.sas + @li mv_createfile.sas + +**/ + +options mprint; + +/* ------------------------------------------------------------------------ */ +/* Setup: start a CAS session and stage a source 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(); +%let tab2=T%mf_uid(); +%let tab3=T%mf_uid(); + +/* Create a SASHDAT source file in the Public caslib from SASHELP.BASEBALL + so that subsequent LOAD operations have something real to pick up. */ +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; + +/* And a second hdat, with a name different from the table name, so that we + can exercise the explicit srcfile= path. */ +proc casutil; + load data=sashelp.cars outcaslib="&testcaslib" casout="&tab2" replace; + save casdata="&tab2" incaslib="&testcaslib" + casout="src_&tab2..sashdat" outcaslib="&testcaslib" replace; + droptable casdata="&tab2" incaslib="&testcaslib" quiet; +quit; + + +/* ------------------------------------------------------------------------ */ +%put TEST 1 - missing required parameters returns without setting RC to 0/1; +/* ------------------------------------------------------------------------ */ +%let MV_CASTABLOAD_RC=; +%mv_castabload(caslib=,table=,srcfile=) + +%mp_assert( + iftrue=(&MV_CASTABLOAD_RC=3), + desc=Check RC=3 (initial/failure value) when required params are missing +) + + +/* ------------------------------------------------------------------------ */ +%put TEST 2 - load a table that does not yet exist (default srcfile=table.hdat); +/* ------------------------------------------------------------------------ */ +%mv_castabload(caslib=&testcaslib,table=&tab1,mdebug=1) + +%mp_assert( + iftrue=(&MV_CASTABLOAD_RC=1), + desc=Check RC=1 when table is loaded and promoted for the first time +) + + +/* ------------------------------------------------------------------------ */ +%put TEST 3 - calling again for the same table should be a no-op (RC=0); +/* also verify no scope leakage of macro variables */ +/* ------------------------------------------------------------------------ */ +%mp_assertscope(SNAPSHOT) + +%mv_castabload(caslib=&testcaslib,table=&tab1,mdebug=1) + +%mp_assertscope(COMPARE, + desc=Check mv_castabload does not leak macro variables into GLOBAL scope, + ignorelist=MV_CASTABLOAD_RC +) + +%mp_assert( + iftrue=(&MV_CASTABLOAD_RC=0), + desc=Check RC=0 when table is already in-memory (skip load) +) + + +/* ------------------------------------------------------------------------ */ +%put TEST 4 - explicit srcfile= where file name differs from table name; +/* ------------------------------------------------------------------------ */ +%mv_castabload( + caslib=&testcaslib, + table=&tab2, + srcfile=src_&tab2..sashdat, + mdebug=1 +) + +%mp_assert( + iftrue=(&MV_CASTABLOAD_RC=1), + desc=Check RC=1 when loading with explicit srcfile= parameter +) + + +/* ------------------------------------------------------------------------ */ +%put TEST 5 - load failure when srcfile does not exist in the caslib; +/* ------------------------------------------------------------------------ */ +%mv_castabload( + caslib=&testcaslib, + table=&tab3, + srcfile=doesnotexist_%mf_uid..sashdat, + mdebug=1 +) + +%mp_assert( + iftrue=(&MV_CASTABLOAD_RC=3), + desc=Check RC=3 when source file cannot be found / load fails +) + +/* reset so that a downstream failure RC does not break testterm */ +%let syscc=0; + + +/* ------------------------------------------------------------------------ */ +/* Teardown: drop promoted tables and remove source files */ +/* ------------------------------------------------------------------------ */ +proc casutil; + droptable casdata="&tab1" incaslib="&testcaslib" quiet; + droptable casdata="&tab2" incaslib="&testcaslib" quiet; + deletesource casdata="&tab1..sashdat" incaslib="&testcaslib" quiet; + deletesource casdata="src_&tab2..sashdat" incaslib="&testcaslib" quiet; +quit; + +cas mysess terminate; + +%let syscc=0; diff --git a/viya/mv_castabload.sas b/viya/mv_castabload.sas new file mode 100644 index 0000000..bc141fe --- /dev/null +++ b/viya/mv_castabload.sas @@ -0,0 +1,102 @@ +/** + @file mv_castabload.sas + @brief Checks if a CAS table exists in a CASLIB; if not, loads & promotes it + @details Runs in SPRE against an active CAS session. Uses + `table.tableExists` to check whether the table is already in-memory, + and PROC CASUTIL LOAD with the PROMOTE option to load it if not. + CASUTIL infers the file type from the source file extension. + + A CAS session must already be established by the caller, eg: + + cas mysess; + %mv_castabload(caslib=Public, table=BASEBALL) + + or (if not a hdat source with the same name as the table): + + %mv_castabload(caslib=Public, table=BASEBALL, + srcfile=MYBASEBALL.parquet) + + @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 + @param [in] mdebug= (0) Set to 1 to enable verbose logging: + - echoes resolved parameters + - prints tableExists result + - enables mprint/notes during PROC calls + + @returns Sets global macro variable `MV_CASTABLOAD_RC`: + 0 = table already existed (no load performed) + 1 = table was loaded & promoted successfully + 3 = action failed +**/ + +%macro mv_castabload( + caslib= + ,table= + ,srcfile=0 + ,mdebug=0 +); + +%global MV_CASTABLOAD_RC; +%let MV_CASTABLOAD_RC=3; + +%local _sysopts; +%let _sysopts=%sysfunc(getoption(mprint)) %sysfunc(getoption(notes)); + +/* ---- input validation -------------------------------------------------- */ +%if "&caslib"="" or "&table"="" or "&srcfile"="" %then %do; + %put %str(ERR)OR: caslib=, table= and srcfile= are all required; + %return; +%end; +%if "&srcfile"="0" %then %let srcfile=&table..hdat; + +%if &mdebug=1 %then %do; + %put &=caslib; + %put &=table; + %put &=srcfile; + options mprint notes; +%end; + +/* ---- existence check --------------------------------------------------- */ +proc cas; + table.tableExists result=r / + caslib="&caslib" + name="&table"; + %if &mdebug=1 %then %do; + print r; + %end; + if r.exists = 0 then rc = 9; + else rc = 0; + symputx('MV_CASTABLOAD_RC', rc, 'G'); +quit; + + +/* ---- load if absent ---------------------------------------------------- */ +%if &MV_CASTABLOAD_RC=9 %then %do; + + proc casutil; + load casdata="&srcfile" + incaslib="&caslib" + casout="&table" + outcaslib="&caslib" + promote; + quit; + + %if &syserr=0 %then %let MV_CASTABLOAD_RC=1; + %else %let MV_CASTABLOAD_RC=3; + +%end; + +%if &MV_CASTABLOAD_RC=0 %then + %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; + +/* ---- restore options --------------------------------------------------- */ +%if &mdebug=1 %then %do; + options &_sysopts; +%end; + +%mend mv_castabload;