From 436b430389387108c1ddf5333446b19898acc3fb Mon Sep 17 00:00:00 2001 From: munja Date: Wed, 22 Dec 2021 11:23:57 +0000 Subject: [PATCH 1/2] feat: mf_islibds() macro to test if a library.dataset reference is syntactically valid --- base/mf_islibds.sas | 40 +++++++++++++++++++++ tests/crossplatform/mf_islibds.test.sas | 46 +++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 base/mf_islibds.sas create mode 100644 tests/crossplatform/mf_islibds.test.sas diff --git a/base/mf_islibds.sas b/base/mf_islibds.sas new file mode 100644 index 0000000..28e65f2 --- /dev/null +++ b/base/mf_islibds.sas @@ -0,0 +1,40 @@ +/** + @file + @brief Checks whether a string follows correct library.dataset format + @details Many macros in the core library accept a library.dataset parameter + referred to as 'libds'. This macro validates the structure of that parameter, + eg: + + @li 8 character libref? + @li 32 character dataset? + @li contains a period? + + It does NOT check whether the dataset exists, or if the library is assigned. + + Usage: + + %put %mf_islibds(work.something)=1; + %put %mf_islibds(nolib)=0; + %put %mf_islibds(badlibref.ds)=0; + %put %mf_islibds(w.t.f)=0; + + @param [in] libds The string to be checked + + @return output Returns 1 if libds is valid, 0 if it is not + +

Related Macros

+ @li mf_islibds.test.sas + @li mp_validatecol.sas + + @version 9.2 +**/ + +%macro mf_islibds(libds +)/*/STORE SOURCE*/; + +%local regex; +%let regex=%sysfunc(prxparse(%str(/^[_a-z]\w{0,7}\.[_a-z]\w{0,31}$/i))); + +%sysfunc(prxmatch(®ex,&libds)) + +%mend mf_islibds; \ No newline at end of file diff --git a/tests/crossplatform/mf_islibds.test.sas b/tests/crossplatform/mf_islibds.test.sas new file mode 100644 index 0000000..3f6d401 --- /dev/null +++ b/tests/crossplatform/mf_islibds.test.sas @@ -0,0 +1,46 @@ +/** + @file + @brief Testing mf_islibds macro + + %put %mf_islibds(work.something)=1; + %put %mf_islibds(nolib)=0; + %put %mf_islibds(badlibref.ds)=0; + %put %mf_islibds(w.t.f)=0; + +

SAS Macros

+ @li mf_islibds.sas + @li mp_assert.sas + +**/ + +%mp_assert( + iftrue=( + %mf_islibds(work.something)=1 + ), + desc=%str(Checking mf_islibds(work.something)=1), + outds=work.test_results +) + +%mp_assert( + iftrue=( + %mf_islibds(nolib)=0 + ), + desc=%str(Checking mf_islibds(nolib)=0), + outds=work.test_results +) + +%mp_assert( + iftrue=( + %mf_islibds(badlibref.ds)=0 + ), + desc=%str(Checking mf_islibds(badlibref.ds)=0), + outds=work.test_results +) + +%mp_assert( + iftrue=( + %mf_islibds(w.t.f)=0 + ), + desc=%str(Checking mf_islibds(w.t.f)=0), + outds=work.test_results +) From f90dc069dc3ac0a3fe65f397e123382c48ed321b Mon Sep 17 00:00:00 2001 From: munja Date: Wed, 22 Dec 2021 15:12:10 +0000 Subject: [PATCH 2/2] feat: update to makedata to respect primary keys (and enable joins to other tables) --- all.sas | 90 +++++++++++++++++++++++++++++++++++--------- base/mp_makedata.sas | 51 ++++++++++++++++--------- 2 files changed, 105 insertions(+), 36 deletions(-) diff --git a/all.sas b/all.sas index 349bdb3..9de7022 100644 --- a/all.sas +++ b/all.sas @@ -1532,6 +1532,45 @@ Usage: %else %do;1%end; %mend mf_isint;/** + @file + @brief Checks whether a string follows correct library.dataset format + @details Many macros in the core library accept a library.dataset parameter + referred to as 'libds'. This macro validates the structure of that parameter, + eg: + + @li 8 character libref? + @li 32 character dataset? + @li contains a period? + + It does NOT check whether the dataset exists, or if the library is assigned. + + Usage: + + %put %mf_islibds(work.something)=1; + %put %mf_islibds(nolib)=0; + %put %mf_islibds(badlibref.ds)=0; + %put %mf_islibds(w.t.f)=0; + + @param [in] libds The string to be checked + + @return output Returns 1 if libds is valid, 0 if it is not + +

Related Macros

+ @li mf_islibds.test.sas + @li mp_validatecol.sas + + @version 9.2 +**/ + +%macro mf_islibds(libds +)/*/STORE SOURCE*/; + +%local regex; +%let regex=%sysfunc(prxparse(%str(/^[_a-z]\w{0,7}\.[_a-z]\w{0,31}$/i))); + +%sysfunc(prxmatch(®ex,&libds)) + +%mend mf_islibds;/** @file @brief Returns physical location of various SAS items @details Returns location of the PlatformObjectFramework tools @@ -7640,8 +7679,6 @@ lock &libds clear; according to the variable types and formats. TODO: - @li Respect PKs - @li Respect NOT NULLs @li Consider dates, datetimes, times, integers etc Usage: @@ -7657,12 +7694,15 @@ lock &libds clear; ); %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. + @param [in] libds The empty table (libref.dataset) in which to create data + @param [out] obs= (500) The maximum number of records to create. The table + is sorted with nodup on the primary key, so the actual number of records may + be lower than this.

SAS Macros

@li mf_getuniquename.sas @li mf_getvarlen.sas + @li mf_islibds.sas @li mf_nobs.sas @li mp_getcols.sas @li mp_getpk.sas @@ -7674,45 +7714,59 @@ lock &libds clear; %macro mp_makedata(libds ,obs=500 + ,seed=1 )/*/STORE SOURCE*/; -%local ds1 c1 n1 i col charvars numvars; +%local ds1 ds2 lib ds pk_fields i col charvars numvars ispk; -%if %mf_nobs(&libds)>0 %then %do; +%if %mf_islibds(&libds)=0 %then %do; + %put &sysmacroname: Invalid libds (&libds) - should be library.dataset format; + %return; +%end; +%else %if %mf_nobs(&libds)>0 %then %do; %put &sysmacroname: &libds has data, it will not be recreated; %return; %end; -%local ds1 c1 n1; -%let ds1=%mf_getuniquename(prefix=mp_makedata); -%let c1=%mf_getuniquename(prefix=mp_makedatacol); -%let n1=%mf_getuniquename(prefix=mp_makedatacol); -data &ds1; +/* set up temporary vars */ +%let ds1=%mf_getuniquename(prefix=mp_makedatads1); +%let ds2=%mf_getuniquename(prefix=mp_makedatads2); +%let lib=%scan(&libds,1,.); +%let ds=%scan(&libds,2,.); + +/* grab the primary key vars */ +%mp_getpk(&lib,ds=&ds,outds=&ds1) + +proc sql noprint; +select pk_fields into: pk_fields from &ds1; + +data &ds2; if 0 then set &libds; do _n_=1 to &obs; - &c1=repeat(uuidgen(),10); - &n1=ranuni(1)*5000000; - drop &c1 &n1; %let charvars=%mf_getvarlist(&libds,typefilter=C); %if &charvars ^= %then %do i=1 %to %sysfunc(countw(&charvars)); %let col=%scan(&charvars,&i); - &col=subpad(&c1,1,%mf_getvarlen(&libds,&col)); + /* create random value based on observation number and colum length */ + &col=substr(put(md5(_n_),$hex32.),1,%mf_getvarlen(&libds,&col)); %end; %let numvars=%mf_getvarlist(&libds,typefilter=N); %if &numvars ^= %then %do i=1 %to %sysfunc(countw(&numvars)); %let col=%scan(&numvars,&i); - &col=&n1; + &col=_n_; %end; output; end; run; +proc sort data=&ds2 nodupkey; + by &pk_fields; +run; -proc append base=&libds data=&ds1; +proc append base=&libds data=&ds2; run; proc sql; -drop table &ds1; +drop table &ds1, &ds2; %mend mp_makedata;/** @file diff --git a/base/mp_makedata.sas b/base/mp_makedata.sas index b822ee4..a5a1fac 100644 --- a/base/mp_makedata.sas +++ b/base/mp_makedata.sas @@ -10,8 +10,6 @@ according to the variable types and formats. TODO: - @li Respect PKs - @li Respect NOT NULLs @li Consider dates, datetimes, times, integers etc Usage: @@ -27,12 +25,15 @@ ); %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. + @param [in] libds The empty table (libref.dataset) in which to create data + @param [out] obs= (500) The maximum number of records to create. The table + is sorted with nodup on the primary key, so the actual number of records may + be lower than this.

SAS Macros

@li mf_getuniquename.sas @li mf_getvarlen.sas + @li mf_islibds.sas @li mf_nobs.sas @li mp_getcols.sas @li mp_getpk.sas @@ -44,44 +45,58 @@ %macro mp_makedata(libds ,obs=500 + ,seed=1 )/*/STORE SOURCE*/; -%local ds1 c1 n1 i col charvars numvars; +%local ds1 ds2 lib ds pk_fields i col charvars numvars ispk; -%if %mf_nobs(&libds)>0 %then %do; +%if %mf_islibds(&libds)=0 %then %do; + %put &sysmacroname: Invalid libds (&libds) - should be library.dataset format; + %return; +%end; +%else %if %mf_nobs(&libds)>0 %then %do; %put &sysmacroname: &libds has data, it will not be recreated; %return; %end; -%local ds1 c1 n1; -%let ds1=%mf_getuniquename(prefix=mp_makedata); -%let c1=%mf_getuniquename(prefix=mp_makedatacol); -%let n1=%mf_getuniquename(prefix=mp_makedatacol); -data &ds1; +/* set up temporary vars */ +%let ds1=%mf_getuniquename(prefix=mp_makedatads1); +%let ds2=%mf_getuniquename(prefix=mp_makedatads2); +%let lib=%scan(&libds,1,.); +%let ds=%scan(&libds,2,.); + +/* grab the primary key vars */ +%mp_getpk(&lib,ds=&ds,outds=&ds1) + +proc sql noprint; +select pk_fields into: pk_fields from &ds1; + +data &ds2; if 0 then set &libds; do _n_=1 to &obs; - &c1=repeat(uuidgen(),10); - &n1=ranuni(1)*5000000; - drop &c1 &n1; %let charvars=%mf_getvarlist(&libds,typefilter=C); %if &charvars ^= %then %do i=1 %to %sysfunc(countw(&charvars)); %let col=%scan(&charvars,&i); - &col=subpad(&c1,1,%mf_getvarlen(&libds,&col)); + /* create random value based on observation number and colum length */ + &col=substr(put(md5(_n_),$hex32.),1,%mf_getvarlen(&libds,&col)); %end; %let numvars=%mf_getvarlist(&libds,typefilter=N); %if &numvars ^= %then %do i=1 %to %sysfunc(countw(&numvars)); %let col=%scan(&numvars,&i); - &col=&n1; + &col=_n_; %end; output; end; run; +proc sort data=&ds2 nodupkey; + by &pk_fields; +run; -proc append base=&libds data=&ds1; +proc append base=&libds data=&ds2; run; proc sql; -drop table &ds1; +drop table &ds1, &ds2; %mend mp_makedata; \ No newline at end of file