From 8b355b805646c39b2a8e5365198baf51b58b5fb5 Mon Sep 17 00:00:00 2001 From: munja Date: Sun, 5 Dec 2021 23:35:25 +0000 Subject: [PATCH] feat: new macros (mp_reseterror and mp_init), new datetime format added to mp_getcols, and stub prepared for mp_makedata --- base/mp_getcols.sas | 2 +- base/mp_init.sas | 54 +++++++++++++++++++ base/mp_makedata.sas | 45 ++++++++++++++++ base/mp_reseterror.sas | 27 ++++++++++ tests/crossplatform/mp_init.test.sas | 24 +++++++++ tests/crossplatform/mp_lockfilecheck.test.sas | 3 ++ tests/crossplatform/mp_reseterror.test.sas | 23 ++++++++ tests/testinit.sas | 6 +++ 8 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 base/mp_init.sas create mode 100644 base/mp_makedata.sas create mode 100644 base/mp_reseterror.sas create mode 100644 tests/crossplatform/mp_init.test.sas create mode 100644 tests/crossplatform/mp_reseterror.test.sas diff --git a/base/mp_getcols.sas b/base/mp_getcols.sas index 64f9c4d..d529513 100644 --- a/base/mp_getcols.sas +++ b/base/mp_getcols.sas @@ -51,7 +51,7 @@ data &outds(keep=name type length varnum format label ddtype); else if formatd=0 then format=cats(format2,formatl,'.'); else format=cats(format2,formatl,'.',formatd); type='N'; - if format=:'DATETIME' then ddtype='DATETIME'; + if format=:'DATETIME' or format=:'E8601DT' then ddtype='DATETIME'; else if format=:'DATE' or format=:'DDMMYY' or format=:'MMDDYY' or format=:'YYMMDD' or format=:'E8601DA' or format=:'B8601DA' or format=:'MONYY' diff --git a/base/mp_init.sas b/base/mp_init.sas new file mode 100644 index 0000000..ca056a2 --- /dev/null +++ b/base/mp_init.sas @@ -0,0 +1,54 @@ +/** + @file + @brief Initialise session with useful settings and variables + @details Implements a set of recommended options for general SAS use. This + macro is NOT used elsewhere within the core library (other than in tests), + but it is used by the SASjs team when building web services for + SAS-Powered applications elsewhere. + + If you have a good idea for an option, setting, or useful global variable - + feel free to [raise an issue](https://github.com/sasjs/core/issues/new)! + + All global variables are prefixed with "SASJS_" (unless modfied with the + prefix parameter). + + @param [in] prefix= (SASJS) The prefix to apply to the global macro variables + + + @version 9.2 + @author Allan Bowe + +**/ + +%macro mp_init(prefix= +)/*/STORE SOURCE*/; + + %global + &prefix._INIT_NUM /* initialisation time as numeric */ + &prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */ + ; + %if %eval(&&&prefix._INIT_NUM>0) %then %return; /* only run once */ + + data _null_; + dttm=datetime(); + call symputx("&prefix._init_num",dttm); + call symputx("&prefix._init_dttm",put(dttm,E8601DT26.6)); + run; + + options + autocorrect /* disallow mis-spelled procedure names */ + compress=CHAR /* default is none so ensure we have something! */ + datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */ + errorcheck=STRICT /* catch errors in libname/filename statements */ + fmterr /* ensure error when a format cannot be found */ + mergenoby=ERROR /* + missing=. /* some sites change this which causes hard to detect errors */ + noquotelenmax /* avoid warnings for long strings */ + noreplace /* avoid overwriting permanent datasets */ + ps=max /* reduce log size slightly */ + validmemname=COMPATIBLE /* avoid special characters etc in table names */ + validvarname=V7 /* avoid special characters etc in variable names */ + varlenchk=ERROR /* fail hard if truncation (data loss) can result */ + ; + +%mend mp_init; \ No newline at end of file diff --git a/base/mp_makedata.sas b/base/mp_makedata.sas new file mode 100644 index 0000000..cb53c30 --- /dev/null +++ b/base/mp_makedata.sas @@ -0,0 +1,45 @@ +/** + @file + @brief Create sample data based on the structure of an empty table + @details Many SAS projects involve sensitive datasets. One way to _ensure_ + the data is anonymised, is never to receive it in the first place! Often + consultants are provided with empty tables, and expected to create complex + ETL flows. + + This macro can help by taking an empty table, and populating it with data + according to the variable types and formats. + + The primary key is respected, as well as any NOT NULL constraints. + + Usage: + + proc sql; + create table work.example( + TX_FROM float format=datetime19., + DD_TYPE char(16), + DD_SOURCE char(2048), + DD_SHORTDESC char(256), + constraint pk primary key(tx_from, dd_type,dd_source), + constraint nnn not null(DD_SHORTDESC) + ); + %mp_makedata(work.example) + + @param [in] libds The empty table in which to create data + @param [out] obs= (500) The number of records to create. + +

SAS Macros

+ @li mf_nobs.sas + @li mp_getcols.sas + @li mp_getpk.sas + + @version 9.2 + @author Allan Bowe + +**/ + +%macro mp_makedata(libds + ,obs=500 +)/*/STORE SOURCE*/; + + +%mend mp_makedata; \ No newline at end of file diff --git a/base/mp_reseterror.sas b/base/mp_reseterror.sas new file mode 100644 index 0000000..7bd6066 --- /dev/null +++ b/base/mp_reseterror.sas @@ -0,0 +1,27 @@ +/** + @file + @brief Reset when an err condition occurs + @details When building apps, sometimes an operation must be attempted that + can cause an err condition. There is no try catch in SAS! So the err state + must be caught and reset. + + This macro attempts to do that reset. + + @version 9.2 + @author Allan Bowe + +**/ + +%macro mp_reseterror( +)/*/STORE SOURCE*/; + +options obs=max replace nosyntaxcheck; +%let syscc=0; + +%if "&sysprocessmode " = "SAS Stored Process Server " %then %do; + data _null_; + rc=stpsrvset('program error', 0); + run; +%end; + +%mend mp_reseterror; \ No newline at end of file diff --git a/tests/crossplatform/mp_init.test.sas b/tests/crossplatform/mp_init.test.sas new file mode 100644 index 0000000..cbe8168 --- /dev/null +++ b/tests/crossplatform/mp_init.test.sas @@ -0,0 +1,24 @@ +/** + @file + @brief Testing mp_gsubfile.sas macro + +

SAS Macros

+ @li mp_init.sas + @li mp_assert.sas + +**/ + +/** + * Test 1 - mp_init.sas actually already ran as part of testinit + * So lets test to make sure it will not run again + */ + +%let initial_value=&sasjs_init_num; + +%mp_init(); + +%mp_assert( + iftrue=("&initial_value"="&sasjs_init_num"), + desc=Check that mp_init() did not run twice, + outds=work.test_results +) \ No newline at end of file diff --git a/tests/crossplatform/mp_lockfilecheck.test.sas b/tests/crossplatform/mp_lockfilecheck.test.sas index a6a612d..55ff284 100644 --- a/tests/crossplatform/mp_lockfilecheck.test.sas +++ b/tests/crossplatform/mp_lockfilecheck.test.sas @@ -5,6 +5,7 @@

SAS Macros

@li mp_lockfilecheck.sas @li mp_assert.sas + @li mp_reseterror.sas **/ @@ -29,6 +30,8 @@ data work.test; a=1;run; %mp_lockfilecheck(sashelp.class) +%mp_reseterror() + %mp_assert( iftrue=(&success=1), desc=Checking sashelp table cannot be locked, diff --git a/tests/crossplatform/mp_reseterror.test.sas b/tests/crossplatform/mp_reseterror.test.sas new file mode 100644 index 0000000..ffee380 --- /dev/null +++ b/tests/crossplatform/mp_reseterror.test.sas @@ -0,0 +1,23 @@ +/** + @file + @brief Testing mp_reseterror macro + +

SAS Macros

+ @li mp_assert.sas + @li mp_reseterror.sas + +**/ + + +/* cause an error */ + +lock sashelp.class; + +/* recover ? */ +%mp_reseterror() + +%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 4b4633c..5019170 100644 --- a/tests/testinit.sas +++ b/tests/testinit.sas @@ -2,11 +2,17 @@ @file @brief init file for tests +

SAS Macros

+ @li mp_init.sas + **/ /* location in metadata or SAS Drive for temporary files */ %let mcTestAppLoc=/Public/temp/macrocore; +/* set defaults */ +%mp_init() + %macro loglevel(); %if &_debug=2477 %then %do; options mprint;