diff --git a/all.sas b/all.sas
index 532b9c2..baaf4a3 100644
--- a/all.sas
+++ b/all.sas
@@ -29,7 +29,7 @@ options noquotelenmax;
@cond
**/
-%macro mf_abort(mac=mf_abort.sas, type=deprecated, msg=, iftrue=%str(1=1)
+%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)
)/*/STORE SOURCE*/;
%if not(%eval(%unquote(&iftrue))) %then %return;
@@ -3036,7 +3036,7 @@ run;
@li COMPARE - compare the current macro variables against previous values
@param [in] scope= (GLOBAL) The scope of the variables to be checked. This
corresponds to the values in the SCOPE column in `sashelp.vmacro`.
- @param [in] desc= (Testing variable scope) The user provided test description
+ @param [in] desc= (Testing scope leakage) The user provided test description
@param [in,out] scopeds= (work.mp_assertscope) The dataset to contain the
scope snapshot
@param [out] outds= (work.test_results) The output dataset to contain the
@@ -3058,7 +3058,7 @@ run;
**/
%macro mp_assertscope(action,
- desc=0,
+ desc=Testing Scope Leakage,
scope=GLOBAL,
scopeds=work.mp_assertscope,
outds=work.test_results
@@ -5028,6 +5028,123 @@ run;
%end;
%mend mp_ds2md;/**
+ @file
+ @brief Create a smaller version of a dataset, without data loss
+ @details This macro will scan the input dataset and create a new one, that
+ has the minimum variable lengths needed to store the data without data loss.
+
+ Inspiration was taken from [How to Reduce the Disk Space Required by a
+ SASĀ® Data Set](https://www.lexjansen.com/nesug/nesug06/io/io18.pdf) by
+ Selvaratnam Sridharma. The end of the referenced paper presents a macro named
+ "squeeze", hence the nomenclature.
+
+ Usage:
+
+ data big;
+ length my big $32000;
+ do i=1 to 1e4;
+ my=repeat('oh my',100);
+ big='dawg';
+ special=._;
+ output;
+ end;
+ run;
+
+ %mp_ds2squeeze(work.big,outds=work.smaller)
+
+ The following will also be printed to the log (exact values may differ
+ depending on your OS and COMPRESS settings):
+
+ > MP_DS2SQUEEZE: work.big was 625MB
+ > MP_DS2SQUEEZE: work.smaller is 5MB
+
+ @param [in] libds The library.dataset to be squeezed
+ @param [out] outds= (work.mp_ds2squeeze) The squeezed dataset to create
+ @param [in] mdebug= (0) Set to 1 to enable DEBUG messages
+
+
SAS Macros
+ @li mf_getfilesize.sas
+ @li mf_getuniquefileref.sas
+ @li mf_getuniquename.sas
+ @li mp_getmaxvarlengths.sas
+
+ Related Programs
+ @li mp_ds2squeeze.test.sas
+
+ @version 9.3
+ @author Allan Bowe
+**/
+
+%macro mp_ds2squeeze(
+ libds,
+ outds=work.work.mp_ds2squeeze,
+ mdebug=0
+)/*/STORE SOURCE*/;
+%local dbg source;
+%if &mdebug=1 %then %do;
+ %put &sysmacroname entry vars:;
+ %put _local_;
+%end;
+%else %do;
+ %let dbg=*;
+ %let source=/source2;
+%end;
+
+%local optval ds fref;
+%let ds=%mf_getuniquename();
+%let fref=%mf_getuniquefileref();
+
+%mp_getmaxvarlengths(&libds,outds=&ds)
+
+data _null_;
+ set &ds end=last;
+ file &fref;
+ /* grab the types */
+ retain dsid;
+ if _n_=1 then dsid=open("&libds",'is');
+ if dsid le 0 then do;
+ msg=sysmsg();
+ put msg=;
+ stop;
+ end;
+ type=vartype(dsid,varnum(dsid, name));
+ if last then rc=close(dsid);
+ /* write out the length statement */
+ if _n_=1 then put 'length ';
+ length len $6;
+ if type='C' then do;
+ if maxlen=0 then len='$1';
+ else len=cats('$',maxlen);
+ end;
+ else do;
+ if maxlen=0 then len='3';
+ else len=cats(maxlen);
+ end;
+ put ' ' name ' ' len;
+ if last then put ';';
+run;
+
+/* configure varlenchk - as we are explicitly shortening the variables */
+%let optval=%sysfunc(getoption(varlenchk));
+options varlenchk=NOWARN;
+
+data &outds;
+ %inc &fref &source;
+ set &libds;
+run;
+
+options varlenchk=&optval;
+
+%if &mdebug=0 %then %do;
+ proc sql;
+ drop table &ds;
+ filename &fref clear;
+%end;
+
+%put &sysmacroname: &libds was %mf_getfilesize(libds=&libds,format=yes);
+%put &sysmacroname: &outds is %mf_getfilesize(libds=&outds,format=yes);
+
+%mend mp_ds2squeeze;/**
@file
@brief Checks an input filter table for validity
@details Performs checks on the input table to ensure it arrives in the
@@ -6703,30 +6820,48 @@ create table &outsummary as
%end;
%mend mp_getformats;/**
- @file mp_getmaxvarlengths.sas
+ @file
@brief Scans a dataset to find the max length of the variable values
@details
This macro will scan a base dataset and produce an output dataset with two
columns:
- NAME Name of the base dataset column
- - MAXLEN Maximum length of the data contained therein.
+ - MAXLEN Maximum length of the data contained therein.
- Character fields may be allocated very large widths (eg 32000) of which the
- maximum value is likely to be much narrower. This macro was designed to
- enable a HTML table to be appropriately sized however this could be used as
- part of a data audit to ensure we aren't over-sizing our tables in relation to
- the data therein.
+ Character fields are often allocated very large widths (eg 32000) of which the
+ maximum value is likely to be much narrower. Identifying such cases can be
+ helpful in the following scenarios:
+
+ @li Enabling a HTML table to be appropriately sized (`num2char=YES`)
+ @li Reducing the size of a dataset to save on storage (mp_ds2squeeze.sas)
+ @li Identifying columns containing nothing but missing values (`MAXLEN=0` in
+ the output table)
+
+ If the entire column is made up of (non-special) missing values then a value
+ of 0 is returned.
- Numeric fields are converted using the relevant format to determine the width.
Usage:
%mp_getmaxvarlengths(sashelp.class,outds=work.myds)
- @param libds Two part dataset (or view) reference.
- @param outds= The output dataset to create
+ @param [in] libds Two part dataset (or view) reference.
+ @param [in] num2char= (NO) When set to NO, numeric fields are sized according
+ to the number of bytes used (or set to zero in the case of non-special
+ missings). When YES, the numeric field is converted to character (using the
+ format, if available), and that is sized instead, using `lengthn()`.
+ @param [out] outds= The output dataset to create, eg:
+ |NAME:$8.|MAXLEN:best.|
+ |---|---|
+ |`Name `|`7 `|
+ |`Sex `|`1 `|
+ |`Age `|`3 `|
+ |`Height `|`8 `|
+ |`Weight `|`3 `|
SAS Macros
+ @li mcf_length.sas
+ @li mf_getuniquename.sas
@li mf_getvarlist.sas
@li mf_getvartype.sas
@li mf_getvarformat.sas
@@ -6734,20 +6869,32 @@ create table &outsummary as
@version 9.2
@author Allan Bowe
+ Related Macros
+ @li mp_ds2squeeze.sas
+ @li mp_getmaxvarlengths.test.sas
+
**/
%macro mp_getmaxvarlengths(
- libds /* libref.dataset to analyse */
- ,outds=work.mp_getmaxvarlengths /* name of output dataset to create */
+ libds
+ ,num2char=NO
+ ,outds=work.mp_getmaxvarlengths
)/*/STORE SOURCE*/;
-%local vars x var fmt;
+%local vars prefix x var fmt;
%let vars=%mf_getvarlist(libds=&libds);
+%let prefix=%substr(%mf_getuniquename(),1,25);
+%let num2char=%upcase(&num2char);
+
+%if &num2char=NO %then %do;
+ /* compile length function for numeric fields */
+ %mcf_length(wrap=YES, insert_cmplib=YES)
+%end;
proc sql;
create table &outds (rename=(
%do x=1 %to %sysfunc(countw(&vars,%str( )));
- ________&x=%scan(&vars,&x)
+ &prefix.&x=%scan(&vars,&x)
%end;
))
as select
@@ -6755,18 +6902,21 @@ create table &outds (rename=(
%let var=%scan(&vars,&x);
%if &x>1 %then ,;
%if %mf_getvartype(&libds,&var)=C %then %do;
- max(length(&var)) as ________&x
+ max(lengthn(&var)) as &prefix.&x
%end;
- %else %do;
+ %else %if &num2char=YES %then %do;
%let fmt=%mf_getvarformat(&libds,&var);
%put fmt=&fmt;
%if %str(&fmt)=%str() %then %do;
- max(length(cats(&var))) as ________&x
+ max(lengthn(cats(&var))) as &prefix.&x
%end;
%else %do;
- max(length(put(&var,&fmt))) as ________&x
+ max(lengthn(put(&var,&fmt))) as &prefix.&x
%end;
%end;
+ %else %do;
+ max(mcf_length(&var)) as &prefix.&x
+ %end;
%end;
from &libds;
@@ -7616,38 +7766,42 @@ filename &tempref clear;
%macro mp_init(prefix=SASJS
)/*/STORE SOURCE*/;
- %global
- &prefix._INIT_NUM /* initialisation time as numeric */
- &prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */
- &prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */
- ;
- %if %eval(&&&prefix._INIT_NUM>0) %then %return; /* only run once */
+%if %symexist(SASJS_PREFIX) %then %return; /* only run once */
- data _null_;
- dttm=datetime();
- call symputx("&prefix._init_num",dttm,'g');
- call symputx("&prefix._init_dttm",put(dttm,E8601DT26.6),'g');
- call symputx("&prefix.work",pathname('WORK'),'g');
- run;
+%global
+ SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */
+ &prefix._INIT_NUM /* initialisation time as numeric */
+ &prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */
+ &prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */
+;
- options
- noautocorrect /* disallow misspelled procedure names */
- compress=CHAR /* default is none so ensure we have something! */
- datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */
- dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */
- %str(err)orcheck=STRICT /* catch errs in libname/filename statements */
- fmterr /* ensure err when a format cannot be found */
- mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */
- missing=. /* changing this can cause hard to detect errs */
- noquotelenmax /* avoid warnings for long strings */
- noreplace /* avoid overwriting permanent datasets */
- ps=max /* reduce log size slightly */
- ls=max /* reduce log even more and avoid word truncation */
- validmemname=COMPATIBLE /* avoid special characters etc in table names */
- validvarname=V7 /* avoid special characters etc in variable names */
- varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */
- varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */
- ;
+%let sasjs_prefix=&prefix;
+
+data _null_;
+ dttm=datetime();
+ call symputx("&sasjs_prefix._init_num",dttm,'g');
+ call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),'g');
+ call symputx("&sasjs_prefix.work",pathname('WORK'),'g');
+run;
+
+options
+ noautocorrect /* disallow misspelled procedure names */
+ compress=CHAR /* default is none so ensure we have something! */
+ datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */
+ dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */
+ %str(err)orcheck=STRICT /* catch errs in libname/filename statements */
+ fmterr /* ensure err when a format cannot be found */
+ mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */
+ missing=. /* changing this can cause hard to detect errs */
+ noquotelenmax /* avoid warnings for long strings */
+ noreplace /* avoid overwriting permanent datasets */
+ ps=max /* reduce log size slightly */
+ ls=max /* reduce log even more and avoid word truncation */
+ validmemname=COMPATIBLE /* avoid special characters etc in table names */
+ validvarname=V7 /* avoid special characters etc in variable names */
+ varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */
+ varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */
+;
%mend mp_init;/**
@file mp_jsonout.sas
@@ -16389,9 +16543,6 @@ run;
@param [in] stpcode= the source file (or fileref) containing the SAS code to load
into the stp. For multiple files, they should simply be concatenated first.
@param [in] minify= set to YES in order to strip comments, blank lines, and CRLFs.
-
- @param frefin= deprecated - a unique fileref is now always used
- @param frefout= deprecated - a unique fileref is now always used
@param mDebug= set to 1 to show debug messages in the log
@version 9.3
@@ -16406,16 +16557,8 @@ run;
,stpcode=
,minify=NO
,mdebug=0
- /* deprecated */
- ,frefin=inmeta
- ,frefout=outmeta
);
-%if &frefin ne inmeta or &frefout ne outmeta %then %do;
- %put %str(WARN)ING: the frefin and frefout parameters will be deprecated in
- an upcoming release.;
-%end;
-
/* first, check if STP exists */
%local tsuri;
%let tsuri=stopifempty ;
@@ -18671,6 +18814,7 @@ libname &libref1a JSON fileref=&fname1a;
%let found=0;
%put Getting object uri from &libref1a..items;
data _null_;
+ length contenttype name $1000;
set &libref1a..items;
if contenttype="&contenttype" and upcase(name)="%upcase(&name)" then do;
call symputx('uri',uri,'l');
@@ -18818,6 +18962,7 @@ libname &libref1a JSON fileref=&fname1a;
%let found=0;
%put Getting object uri from &libref1a..items;
data _null_;
+ length contenttype name $1000;
set &libref1a..items;
if contenttype='jobDefinition' and upcase(name)="%upcase(&name)" then do;
call symputx('uri',cats("&base_uri",uri),'l');
@@ -18990,59 +19135,6 @@ filename &fname2 clear;
libname &libref1 clear;
%mend mv_deleteviyafolder;/**
- @file mv_getaccesstoken.sas
- @brief deprecated - replaced by mv_tokenrefresh.sas
-
- @version VIYA V.03.04
- @author Allan Bowe, source: https://github.com/sasjs/core
-
- SAS Macros
- @li mv_tokenrefresh.sas
-
-**/
-
-%macro mv_getaccesstoken(client_id=someclient
- ,client_secret=somesecret
- ,grant_type=authorization_code
- ,code=
- ,user=
- ,pass=
- ,access_token_var=ACCESS_TOKEN
- ,refresh_token_var=REFRESH_TOKEN
- );
-
-%mv_tokenrefresh(client_id=&client_id
- ,client_secret=&client_secret
- ,grant_type=&grant_type
- ,user=&user
- ,pass=&pass
- ,access_token_var=&access_token_var
- ,refresh_token_var=&refresh_token_var
-)
-
-%mend mv_getaccesstoken;/**
- @file
- @brief deprecated - replaced by mv_registerclient.sas
-
- @version VIYA V.03.04
- @author Allan Bowe, source: https://github.com/sasjs/core
-
- SAS Macros
- @li mv_registerclient.sas
-
-**/
-
-%macro mv_getapptoken(client_id=someclient
- ,client_secret=somesecret
- ,grant_type=authorization_code
- );
-
-%mv_registerclient(client_id=&client_id
- ,client_secret=&client_secret
- ,grant_type=&grant_type
-)
-
-%mend mv_getapptoken;/**
@file mv_getclients.sas
@brief Get a list of Viya Clients
@details First, be sure you have an access token (which requires an app token).
@@ -20369,38 +20461,6 @@ filename &fname0 clear;
%mend mv_getjobstate;
/**
- @file mv_getrefreshtoken.sas
- @brief deprecated - replaced by mv_tokenauth.sas
-
- @version VIYA V.03.04
- @author Allan Bowe, source: https://github.com/sasjs/core
-
- SAS Macros
- @li mv_tokenauth.sas
-
-**/
-
-%macro mv_getrefreshtoken(client_id=someclient
- ,client_secret=somesecret
- ,grant_type=authorization_code
- ,code=
- ,user=
- ,pass=
- ,access_token_var=ACCESS_TOKEN
- ,refresh_token_var=REFRESH_TOKEN
- );
-
-%mv_tokenauth(client_id=&client_id
- ,client_secret=&client_secret
- ,grant_type=&grant_type
- ,code=&code
- ,user=&user
- ,pass=&pass
- ,access_token_var=&access_token_var
- ,refresh_token_var=&refresh_token_var
-)
-
-%mend mv_getrefreshtoken;/**
@file mv_getusergroups.sas
@brief Creates a dataset with a list of groups for a particular user
@details If using outside of Viya SPRE, then an access token is needed.
@@ -22718,6 +22778,9 @@ run;
@param [out] pkg= (utils) The output package in which to create the function.
Uses a 3 part format: libref.catalog.package
+ SAS Macros
+ @li mf_existfunction.sas
+
Related Macros
@li mcf_length.test.sas
@@ -22730,13 +22793,15 @@ run;
,pkg=UTILS
)/*/STORE SOURCE*/;
+%if %mf_existfunction(mcf_length)=1 %then %return;
+
%if &wrap=YES %then %do;
proc fcmp outlib=&lib..&cat..&pkg;
%end;
function mcf_length(var);
- if missing(var) then len=0;
- else if trunc(var,3)=var then len=3;
+ if var=. then len=0;
+ else if missing(var) or trunc(var,3)=var then len=3;
else if trunc(var,4)=var then len=4;
else if trunc(var,5)=var then len=5;
else if trunc(var,6)=var then len=6;
@@ -22892,6 +22957,9 @@ endsub;
@param [out] pkg= (utils) The output package in which to create the function.
Uses a 3 part format: libref.catalog.package
+ SAS Macros
+ @li mf_existfunction.sas
+
**/
%macro mcf_string2file(wrap=NO
@@ -22901,6 +22969,8 @@ endsub;
,pkg=UTILS
)/*/STORE SOURCE*/;
+%if %mf_existfunction(mcf_string2file)=1 %then %return;
+
%if &wrap=YES %then %do;
proc fcmp outlib=&lib..&cat..&pkg;
%end;
diff --git a/base/mp_ds2squeeze.sas b/base/mp_ds2squeeze.sas
index a729fdb..01bf766 100644
--- a/base/mp_ds2squeeze.sas
+++ b/base/mp_ds2squeeze.sas
@@ -89,7 +89,7 @@ data _null_;
end;
else do;
if maxlen=0 then len='3';
- else len=maxlen;
+ else len=cats(maxlen);
end;
put ' ' name ' ' len;
if last then put ';';
diff --git a/tests/crossplatform/mcf_length.test.sas b/tests/crossplatform/mcf_length.test.sas
index 90a7587..532a6c2 100644
--- a/tests/crossplatform/mcf_length.test.sas
+++ b/tests/crossplatform/mcf_length.test.sas
@@ -12,7 +12,7 @@
data test;
call symputx('null',mcf_length(.));
- call symputx('special',mcf_length(._))
+ call symputx('special',mcf_length(._));
call symputx('three',mcf_length(1));
call symputx('four',mcf_length(10000000));
call symputx('five',mcf_length(12345678));
diff --git a/viya/mv_deletefoldermember.sas b/viya/mv_deletefoldermember.sas
index d857ae1..332c815 100644
--- a/viya/mv_deletefoldermember.sas
+++ b/viya/mv_deletefoldermember.sas
@@ -117,6 +117,7 @@ libname &libref1a JSON fileref=&fname1a;
%let found=0;
%put Getting object uri from &libref1a..items;
data _null_;
+ length contenttype name $1000;
set &libref1a..items;
if contenttype="&contenttype" and upcase(name)="%upcase(&name)" then do;
call symputx('uri',uri,'l');
diff --git a/viya/mv_deletejes.sas b/viya/mv_deletejes.sas
index a0c8a54..4a88d97 100644
--- a/viya/mv_deletejes.sas
+++ b/viya/mv_deletejes.sas
@@ -114,6 +114,7 @@ libname &libref1a JSON fileref=&fname1a;
%let found=0;
%put Getting object uri from &libref1a..items;
data _null_;
+ length contenttype name $1000;
set &libref1a..items;
if contenttype='jobDefinition' and upcase(name)="%upcase(&name)" then do;
call symputx('uri',cats("&base_uri",uri),'l');