1
0
mirror of https://github.com/sasjs/core.git synced 2025-12-12 15:04:36 +00:00

Compare commits

...

70 Commits

Author SHA1 Message Date
Allan Bowe
f4c2be7411 Merge pull request #150 from sasjs/mp_ds2squeeze
sasjs/core - v4
2022-01-24 15:16:37 +02:00
munja
16489a9494 fix: missing macro dependency in mp_ds2squeeze.test.sas 2022-01-24 13:12:31 +01:00
munja
0e03b06a4b fix: adjustments to ensure the tests work, also building all.sas 2022-01-24 12:53:36 +01:00
munja
c3b89c7f7d feat: mp_ds2squeeze macro 2022-01-24 11:17:21 +01:00
munja
142b46570d feat: adding mcf_length to mp_getmaxvarlengths
BREAKING CHANGE: mp_getmaxvarlengths now returns 0 for non-special missings, and will use numeric length (as opposed to cast-to-character length) by default
2022-01-23 23:26:10 +01:00
munja
f7fac50108 fix: removing deprecated functionality ahead of planned breaking change 2022-01-22 21:16:15 +01:00
Allan Bowe
ae5fbcf857 Merge pull request #149 from sasjs/mcf_length
feat: new mcf_length.sas fcmp macro
2022-01-22 19:32:49 +02:00
Allan Bowe
2579b4c929 feat: new mcf_length.sas fcmp macro 2022-01-22 17:16:08 +00:00
Allan Bowe
90a831f59b Merge pull request #148 from sasjs/outcat
fix: renaming outcat to outlib for wider compatibility
2022-01-20 11:34:45 +02:00
Allan Bowe
9fb218f0be fix: renaming outcat to outlib for wider compatibility 2022-01-20 09:14:11 +00:00
Allan Bowe
ccc9dfa4aa Merge pull request #147 from sasjs/allanbowe/macro-scope-test-assertion-146
feat: adding mp_assertscope.sas, closes #146.
2022-01-18 20:46:29 +02:00
Allan Bowe
a37a72b7db feat: adding mp_assertscope.sas, closes #146. Also adding test for mp_assert.sas 2022-01-18 18:24:53 +00:00
Allan Bowe
c6dcf919e2 Merge pull request #143 from sasjs/issue142
feat: ensuring mX_webout services run without MEMSIZE, closes #142.
2022-01-12 22:46:51 +02:00
munja
42541373af chore: running all.sas 2022-01-12 21:25:15 +01:00
munja
208c88f5a4 feat: ensuring mX_webout services run without MEMSIZE, closes #142. Also adding note2err in mp_init(). 2022-01-12 21:23:42 +01:00
Allan Bowe
5605bc74df Merge pull request #141 from sasjs/dirlistfix
fix: dirlist logic
2022-01-11 12:13:01 +02:00
munja
4bec574011 fix: dirlist logic 2022-01-11 11:06:38 +01:00
Allan Bowe
8cfa37ce8b Merge pull request #140 from sasjs/fix_dirlist
fix: proc append warnings for file attributes
2022-01-11 11:35:43 +02:00
Allan Bowe
351ceeb357 fix: tidy up 2022-01-10 18:42:52 +00:00
Ivor Townsend
259bcc0173 fix: proc append warnings for file attributes 2022-01-10 16:49:33 +00:00
Ivor Townsend
db195a8311 fix: proc append warnings for file attributes 2022-01-10 16:33:44 +00:00
munja
4307bfb1b5 fix: adding showmeta option to mX_createwebservce macros 2022-01-09 13:18:14 +01:00
Allan Bowe
df46ee6939 Merge pull request #139 from sasjs/webout_mac
feat: adding SHOWMETA option to mp_jsonout
2022-01-07 15:29:06 +02:00
munja
70b9b71104 fix: base table on mp_lockanytable.test.sas 2022-01-07 14:12:08 +01:00
munja
cd33355418 fix: formats 2022-01-07 13:14:40 +01:00
munja
77d1cdb753 feat: adding length to mp_webout meta 2022-01-07 12:23:40 +01:00
munja
545218e3b9 fix: avoiding type clash 2022-01-06 23:44:42 +01:00
munja
cb07305a87 feat: adding SHOWMETA option to mp_jsonout
Includes updates to associated wrapper macros, and a 30% performance improvement (for small tables).  Addresses https://github.com/sasjs/adapter/issues/607
2022-01-06 23:36:54 +01:00
Allan Bowe
76a39cad20 Merge pull request #138 from sasjs/webout_mac
fix: adding missing param to mx_createwebservice macros
2021-12-30 16:29:50 +02:00
munja
ebd567af48 fix: adding missing param to mx_createwebservice macros 2021-12-30 09:53:08 +00:00
Allan Bowe
a9c418e3f2 Merge pull request #137 from sasjs/specials
feat: updating mp_jsonout() to support special missing numeric values.
2021-12-30 11:43:00 +02:00
munja
e143acd67d chore: automated commit 2021-12-30 00:30:14 +00:00
munja
84eb2f1845 chore: automated commit 2021-12-30 00:29:48 +00:00
munja
b075e5d5d5 feat: updating mp_jsonout() to support special missing numeric values. Closes #136 2021-12-30 00:21:02 +00:00
Allan Bowe
a08f6aeea2 Merge pull request #135 from sasjs/abortfix
fix: removing 'Log Extract' from abort MSG in mp_abort when not capturing the log
2021-12-29 14:54:01 +02:00
munja
469bd574ac fix: removing 'Log Extract' from abort MSG in mp_abort when not capturing the log 2021-12-29 12:35:25 +00:00
Allan Bowe
c41918c0a8 Merge pull request #134 from sasjs/fmtfix
fix: preventing error when mp_applyformats has no formats to apply
2021-12-29 14:19:59 +02:00
munja
0361ca574d fix: preventing error when mp_applyformats has no formats to apply 2021-12-29 12:19:34 +00:00
Allan Bowe
c75c169b80 Merge pull request #133 from sasjs/fmtname
fix: adding fmtname to mp_getcols() macro
2021-12-28 15:39:07 +02:00
munja
eac47bd5db fix: adding fmtname to mp_getcols() macro 2021-12-28 13:25:53 +00:00
munja
d302ef266d chore: doc page formatting & content 2021-12-27 11:54:46 +00:00
munja
fdfe9b8250 fix: enabling further debugging in mp_filterstore.sas 2021-12-26 20:32:56 +00:00
munja
9b1f0d7bcb fix: avoiding append warning: syswarningtext=Variable processed_dttm has format DATETIME19. on the BASE data set and format E8601DT26. on the DATA data set. 2021-12-26 16:27:04 +00:00
Allan Bowe
98b1c44283 Merge pull request #131 from sasjs/mp_filterstore
feat: mp_filterstore macro
2021-12-26 17:28:45 +02:00
munja
ce026f19b5 feat: new mp_filterstore macro 2021-12-26 15:06:06 +00:00
munja
8e723d06b0 feat: mp_filterstore macro 2021-12-26 01:06:56 +00:00
Allan Bowe
a6d84cc65a Merge pull request #130 from sasjs/newvalidation
feat: new macro (mp_retainedkey) for adding retained key values to a …
2021-12-25 22:37:43 +02:00
munja
536ce8e95d feat: new macro (mp_retainedkey) for adding retained key values to a staging table 2021-12-25 20:21:32 +00:00
Allan Bowe
bc1d9e619b Merge pull request #129 from sasjs/newvalidation
feat: new validation (ISINT) in the mp_validatecol.sas macro
2021-12-25 00:19:50 +02:00
munja
1062a97cfe feat: new validation (ISINT) in the mp_validatecol.sas macro - https://core.sasjs.io/mp__validatecol_8sas_source.html 2021-12-24 22:02:23 +00:00
Allan Bowe
51db64c90a Merge pull request #128 from sasjs/writefile
Writefile
2021-12-23 22:13:20 +02:00
munja
7c4278c3f9 chore: updating all.sas 2021-12-23 19:29:54 +00:00
munja
6c6b55dcea chore: updating header, adding stop statement in mp_makedata(), writing test for mf_existvar() 2021-12-23 19:29:37 +00:00
munja
66b0c9e77e feat: new mf_writefile() macro 2021-12-23 19:29:01 +00:00
Allan Bowe
caf3b95269 Merge pull request #126 from sasjs/applyformats
Applyformats
2021-12-23 16:15:09 +02:00
munja
3866b97416 chore: updating doc header 2021-12-23 13:55:53 +00:00
munja
d687658687 chore: updating all.sas 2021-12-23 13:51:11 +00:00
munja
9f815c73e9 feat: new mp_applyformats macro (and test), plus new addition to mp_validatecol (is_format) 2021-12-23 13:50:58 +00:00
Allan Bowe
a13c782074 Merge pull request #125 from sasjs/makedata
`mp_makedata()` improvements
2021-12-23 13:11:36 +02:00
munja
f2991cfd63 fix: enabling makedata support for charvars > and tables without primary keys. Also added tests. 2021-12-23 10:56:01 +00:00
Allan Bowe
8eb4f0844c Merge pull request #124 from sasjs/updates
feat: mf_islibds() macro to test if a library.dataset reference is valid
2021-12-22 17:28:22 +02:00
munja
f90dc069dc feat: update to makedata to respect primary keys (and enable joins to other tables) 2021-12-22 15:12:10 +00:00
munja
436b430389 feat: mf_islibds() macro to test if a library.dataset reference is syntactically valid 2021-12-22 11:23:57 +00:00
Allan Bowe
6667b91ced Merge pull request #123 from sasjs/words
feat: new wordsinstr1andstr2() macro and associated tests
2021-12-22 00:07:39 +02:00
munja
bce56d8105 feat: new wordsinstr1andstr2() macro and associated tests 2021-12-21 21:54:48 +00:00
munja
2ec440b321 fix: removing termstr=lf as it breaks on SAS 9 deploys 2021-12-21 19:34:08 +00:00
munja
3d2ad531cf chore: docs 2021-12-18 14:37:54 +00:00
Allan Bowe
09136cfdbb Merge pull request #122 from sasjs/serverfix
fix: making ms_webout work with SAS on Windows Desktop
2021-12-18 14:12:35 +00:00
munja
0ca16f3d04 fix: quoting the server option in mm_assigndirectlib() to avoid assignment errors when the name contains dashes aetc 2021-12-18 14:11:38 +00:00
Allan Bowe
1e72f13f2d fix: making ms_webout work with SAS on Windows Desktop 2021-12-17 23:01:43 +00:00
74 changed files with 5293 additions and 1451 deletions

View File

@@ -125,6 +125,7 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
- macro names must be lowercase - macro names must be lowercase
- one macro per file - one macro per file
- prefixes: - prefixes:
- _mcf_ for macro compiled functions (proc fcmp)
- _mf_ for macro functions (can be used in open code). - _mf_ for macro functions (can be used in open code).
- _ml_ for macros that are used to compile LUA modules - _ml_ for macros that are used to compile LUA modules
- _mm_ for metadata macros (interface with the metadata server). - _mm_ for metadata macros (interface with the metadata server).
@@ -166,7 +167,7 @@ SAS code can contain one of two types of dependency - SAS Macros, and SAS Includ
@li someprogram.sas FREFTWO @li someprogram.sas FREFTWO
``` ```
The CLI can then extract all the dependencies and insert as precode (SAS Macros) or in a temp engine fileref (SAS Includes) when creating SAS Jobs and Services. The CLI can then extract all the dependencies and insert as precode (SAS Macros) or in a temp engine fileref (SAS Includes) when creating SAS Jobs and Services (and Tests).
When contributing to this library, it is therefore important to ensure that all dependencies are listed in the header in this format. When contributing to this library, it is therefore important to ensure that all dependencies are listed in the header in this format.
@@ -182,12 +183,20 @@ When contributing to this library, it is therefore important to ensure that all
- Mandatory parameters should be positional, all optional parameters should be keyword (var=) style. - Mandatory parameters should be positional, all optional parameters should be keyword (var=) style.
- 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. - 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.
- 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;` - 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 optional unless you are looking to benefit from the timing statistics.
- Use [sasjs lint](https://github.com/sasjs/lint)!
## General Notes ## General Notes
- All macros should be compatible with SAS versions from support level B and above (so currently 9.2 and later). If an earlier version is not supported, then the macro should say as such in the header documentation, and exit gracefully (eg `%if %sysevalf(&sysver<9.3) %then %return`). - All macros should be compatible with SAS versions from support level B and above (so currently 9.2 and later). If an earlier version is not supported, then the macro should say as such in the header documentation, and exit gracefully (eg `%if %sysevalf(&sysver<9.3) %then %return`).
## Breaking Changes
We are currently on major release v4. Breaking changes should be marked with the [deprecated](https://www.doxygen.nl/manual/commands.html#cmddeprecated) doxygen tag. The following changes are planned when the next major (breaking) release becomes necessary:
* (None as yet)
## Star Gazing ## Star Gazing
If you find this library useful, please leave a [star](https://github.com/sasjs/core/stargazers) and help us grow our star graph! If you find this library useful, please leave a [star](https://github.com/sasjs/core/stargazers) and help us grow our star graph!

2639
all.sas

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@
@cond @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*/; )/*/STORE SOURCE*/;
%if not(%eval(%unquote(&iftrue))) %then %return; %if not(%eval(%unquote(&iftrue))) %then %return;

View File

@@ -7,8 +7,12 @@
%put %mf_existvar(work.someds, somevar) %put %mf_existvar(work.someds, somevar)
@param libds (positional) - 2 part dataset or view reference @param [in] libds 2 part dataset or view reference
@param var (positional) - variable name @param [in] var variable name
<h4> Related Macros </h4>
@li mf_existvar.test.sas
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe
**/ **/

View File

@@ -12,9 +12,10 @@
contributors of Chris Hemedingers blog [post]( contributors of Chris Hemedingers blog [post](
http://blogs.sas.com/content/sasdummy/2013/06/04/find-a-sas-library-engine/) http://blogs.sas.com/content/sasdummy/2013/06/04/find-a-sas-library-engine/)
@param libref Library reference (also accepts a 2 level libds ref). @param [in] libref Library reference (also accepts a 2 level libds ref).
@return output returns the library engine for the FIRST library encountered. @return output returns the library engine (uppercase) for the FIRST library
encountered.
@warning will only return the FIRST library engine - for concatenated @warning will only return the FIRST library engine - for concatenated
libraries, with different engines, inconsistent results may be encountered. libraries, with different engines, inconsistent results may be encountered.
@@ -46,7 +47,7 @@
%let rc= %sysfunc(close(&dsid)); %let rc= %sysfunc(close(&dsid));
%end; %end;
&engine %upcase(&engine)
%mend mf_getengine; %mend mf_getengine;

40
base/mf_islibds.sas Normal file
View File

@@ -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
<h4> Related Macros </h4>
@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(&regex,&libds))
%mend mf_islibds;

View File

@@ -0,0 +1,54 @@
/**
@file
@brief Returns words that are in both string 1 and string 2
@details Compares two space separated strings and returns the words that are
in both.
Usage:
%put %mf_wordsInStr1andStr2(
Str1=blah sss blaaah brah bram boo
,Str2= blah blaaah brah ssss
);
returns:
> blah blaaah brah
@param str1= string containing words to extract
@param str2= used to compare with the extract string
@warning CASE SENSITIVE!
@version 9.2
@author Allan Bowe
**/
%macro mf_wordsInStr1andStr2(
Str1= /* string containing words to extract */
,Str2= /* used to compare with the extract string */
)/*/STORE SOURCE*/;
%local count_base count_extr i i2 extr_word base_word match outvar;
%if %length(&str1)=0 or %length(&str2)=0 %then %do;
%put %str(WARN)ING: empty string provided!;
%put base string (str1)= &str1;
%put compare string (str2) = &str2;
%return;
%end;
%let count_base=%sysfunc(countw(&Str2));
%let count_extr=%sysfunc(countw(&Str1));
%do i=1 %to &count_extr;
%let extr_word=%scan(&Str1,&i,%str( ));
%let match=0;
%do i2=1 %to &count_base;
%let base_word=%scan(&Str2,&i2,%str( ));
%if &extr_word=&base_word %then %let match=1;
%end;
%if &match=1 %then %let outvar=&outvar &extr_word;
%end;
&outvar
%mend mf_wordsInStr1andStr2;

67
base/mf_writefile.sas Normal file
View File

@@ -0,0 +1,67 @@
/**
@file
@brief Creates a text file using pure macro
@details Creates a text file of up to 10 lines. If further lines are
desired, feel free to [create an issue](
https://github.com/sasjs/core/issues/new), or make a pull request!
The use of PARMBUFF was considered for this macro, but it would have made
things problematic for writing lines containing commas.
Usage:
%mf_writefile(&sasjswork/myfile.txt,l1=some content,l2=more content)
data _null_;
infile "&sasjswork/myfile.txt";
input;
list;
run;
@param [in] fpath Full path to file to be created or appended to
@param [in] mode= (O) Available options are A or O as follows:
@li A APPEND mode, writes new records after the current end of the file.
@li O OUTPUT mode, writes new records from the beginning of the file.
@param [in] l1= First line
@param [in] l2= Second line (etc through to l10)
<h4> Related Macros </h4>
@li mf_writefile.test.sas
@version 9.2
@author Allan Bowe
**/
/** @cond */
%macro mf_writefile(fpath,mode=O,l1=,l2=,l3=,l4=,l5=,l6=,l7=,l8=,l9=,l10=
)/*/STORE SOURCE*/;
%local fref rc fid i total_lines;
/* find number of lines by reference to first non-blank param */
%do i=10 %to 1 %by -1;
%if %str(&&l&i) ne %str() %then %goto continue;
%end;
%continue:
%let total_lines=&i;
%if %sysfunc(filename(fref,&fpath)) ne 0 %then %do;
%put &=fref &=fpath;
%put %str(ERR)OR: %sysfunc(sysmsg());
%return;
%end;
%let fid=%sysfunc(fopen(&fref,&mode));
%if &fid=0 %then %do;
%put %str(ERR)OR: %sysfunc(sysmsg());
%return;
%end;
%do i=1 %to &total_lines;
%let rc=%sysfunc(fput(&fid, &&l&i));
%let rc=%sysfunc(fwrite(&fid));
%end;
%let rc=%sysfunc(fclose(&fid));
%let rc=%sysfunc(filename(&fref));
%mend mf_writefile;
/** @endcond */

View File

@@ -110,8 +110,8 @@
%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG; %if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;
%else %let logloc=%qsysfunc(getoption(LOG)); %else %let logloc=%qsysfunc(getoption(LOG));
proc printto log=log;run; proc printto log=log;run;
%let logline=0;
%if %length(&logloc)>0 %then %do; %if %length(&logloc)>0 %then %do;
%let logline=0;
data _null_; data _null_;
infile &logloc lrecl=5000; infile &logloc lrecl=5000;
input; putlog _infile_; input; putlog _infile_;
@@ -160,7 +160,10 @@
file _webout mod lrecl=32000 encoding='utf-8'; file _webout mod lrecl=32000 encoding='utf-8';
length msg $32767 ; length msg $32767 ;
sasdatetime=datetime(); sasdatetime=datetime();
msg=cats(symget('msg'),'\n\nLog Extract:\n',symget('logmsg')); msg=symget('msg');
%if &logline>0 %then %do;
msg=cats(msg,'\n\nLog Extract:\n',symget('logmsg'));
%end;
/* escape the quotes */ /* escape the quotes */
msg=tranwrd(msg,'"','\"'); msg=tranwrd(msg,'"','\"');
/* ditch the CRLFs as chrome complains */ /* ditch the CRLFs as chrome complains */
@@ -170,7 +173,8 @@
if symexist('_debug') then debug=quote(trim(symget('_debug'))); if symexist('_debug') then debug=quote(trim(symget('_debug')));
else debug='""'; else debug='""';
put '>>weboutBEGIN<<'; put '>>weboutBEGIN<<';
put '{"START_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '"'; put '{"SYSDATE" : "' "&SYSDATE" '"';
put ',"SYSTIME" : "' "&SYSTIME" '"';
put ',"sasjsAbort" : [{'; put ',"sasjsAbort" : [{';
put ' "MSG":' msg ; put ' "MSG":' msg ;
put ' ,"MAC": "' "&mac" '"}]'; put ' ,"MAC": "' "&mac" '"}]';
@@ -199,7 +203,7 @@
put ',"SYSVLONG" : ' sysvlong; put ',"SYSVLONG" : ' sysvlong;
syswarningtext=quote(trim(symget('syswarningtext'))); syswarningtext=quote(trim(symget('syswarningtext')));
put ",""SYSWARNINGTEXT"" : " syswarningtext; put ",""SYSWARNINGTEXT"" : " syswarningtext;
put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" '; put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
put "}" @; put "}" @;
put '>>weboutEND<<'; put '>>weboutEND<<';
run; run;

181
base/mp_applyformats.sas Normal file
View File

@@ -0,0 +1,181 @@
/**
@file
@brief Apply a set of formats to a table
@details Applies a set of formats to the metadata of one or more SAS datasets.
Can be used to migrate formats from one table to another. The input table
must contain the following columns:
@li lib - the libref of the table to be updated
@li ds - the dataset to be updated
@li var - the variable to be updated
@li fmt - the format to apply. Missing or default ($CHAR, 8.) formats are
ignored.
The macro will abort in the following scenarios:
@li Libref not assigned
@li Dataset does not exist
@li Input table contains null or invalid values
Example usage:
data work.example;
set sashelp.prdsale;
format _all_ clear;
run;
%mp_getcols(sashelp.prdsale,outds=work.cols)
data work.cols2;
set work.cols;
lib='WORK';
ds='EXAMPLE';
var=name;
fmt=format;
keep lib ds var fmt;
run;
%mp_applyformats(work.cols2)
@param [in] inds The input dataset containing the formats to apply (and where
to apply them). Example structure:
|LIB:$8.|DS:$32.|VAR:$32.|FMT:$49.|
|---|---|---|---|
|`WORK `|`EXAMPLE `|`ACTUAL `|`DOLLAR12.2 `|
|`WORK `|`EXAMPLE `|`COUNTRY `|`$CHAR10. `|
|`WORK `|`EXAMPLE `|`DIVISION `|`$CHAR10. `|
|`WORK `|`EXAMPLE `|`MONTH `|`MONNAME3. `|
|`WORK `|`EXAMPLE `|`PREDICT `|`DOLLAR12.2 `|
|`WORK `|`EXAMPLE `|`PRODTYPE `|`$CHAR10. `|
|`WORK `|`EXAMPLE `|`PRODUCT `|`$CHAR10. `|
|`WORK `|`EXAMPLE `|`QUARTER `|`8. `|
|`WORK `|`EXAMPLE `|`REGION `|`$CHAR10. `|
|`WORK `|`EXAMPLE `|`YEAR `|`8. `|
@param [out] errds= (0) Provide a libds reference here to export the
error messages to a table. In this case, they will not be printed to the
log.
<h4> SAS Macros </h4>
@li mf_getengine.sas
@li mf_getuniquefileref.sas
@li mf_getuniquename.sas
@li mf_nobs.sas
@li mp_validatecol.sas
<h4> Related Macros </h4>
@li mp_getformats.sas
@version 9.2
@author Allan Bowe
**/
%macro mp_applyformats(inds,errds=0
)/*/STORE SOURCE*/;
%local outds liblist i engine lib msg ;
/**
* Validations
*/
proc sort data=&inds;
by lib ds var fmt;
run;
%if &errds=0 %then %let outds=%mf_getuniquename(prefix=mp_applyformats);
%else %let outds=&errds;
data &outds;
set &inds;
where fmt not in ('','.', '$', '$CHAR.','8.');
length msg $128;
by lib ds var fmt;
if libref(lib) ne 0 then do;
msg=catx(' ','libref',lib,'is not assigned!');
%if &errds=0 %then %do;
putlog "%str(ERR)OR: " msg;
%end;
output;
return;
end;
if exist(cats(lib,'.',ds)) ne 1 then do;
msg=catx(' ','libds',lib,'.',ds,'does not exist!');
%if &errds=0 %then %do;
putlog "%str(ERR)OR: " msg;
%end;
output;
return;
end;
%mp_validatecol(fmt,FORMAT,is_fmt)
if is_fmt=0 then do;
msg=catx(' ','format',fmt,'on libds',lib,'.',ds,'.',var,'is not valid!');
%if &errds=0 %then %do;
putlog "%str(ERR)OR: " msg;
%end;
output;
return;
end;
if first.ds then do;
retain dsid;
dsid=open(cats(lib,'.',ds));
if dsid=0 then do;
msg=catx(' ','libds',lib,'.',ds,' could not be opened!');
%if &errds=0 %then %do;
putlog "%str(ERR)OR: " msg;
%end;
output;
return;
end;
if varnum(dsid,var)<1 then do;
msg=catx(' ','Variable',lib,'.',ds,'.',var,' was not found!');
%if &errds=0 %then %do;
putlog "%str(ERR)OR: " msg;
%end;
output;
end;
end;
if last.ds then rc=close(dsid);
run;
proc sql noprint;
select distinct lib into: liblist separated by ' ' from &inds;
%put &=liblist;
%if %length(&liblist)>0 %then %do i=1 %to %sysfunc(countw(&liblist));
%let lib=%scan(&liblist,1);
%let engine=%mf_getengine(&lib);
%if &engine ne V9 and &engine ne BASE %then %do;
%let msg=&lib has &engine engine - formats cannot be applied;
insert into &outds set lib="&lib",ds="_all_",var="_all", msg="&msg" ;
%if &errds=0 %then %put %str(ERR)OR: &msg;
%end;
%end;
quit;
%if %mf_nobs(&outds)>0 %then %return;
/**
* Validations complete - now apply the actual formats!
*/
%let fref=%mf_getuniquefileref();
data _null_;
set &inds;
by lib ds var fmt;
where fmt not in ('','.', '$', '$CHAR.','8.');
file &fref;
if first.lib then put 'proc datasets nolist lib=' lib ';';
if first.ds then put ' modify ' ds ';';
put ' format ' var fmt ';';
if last.ds then put ' run;';
if last.lib then put 'quit;';
run;
%inc &fref/source2;
%if &errds=0 %then %do;
proc sql;
drop table &outds;
%end;
%mend mp_applyformats;

119
base/mp_assertscope.sas Normal file
View File

@@ -0,0 +1,119 @@
/**
@file
@brief Used to capture scope leakage of macro variables
@details A common 'difficult to detect' bug in macros is where a nested
macro over-writes variables in a higher level macro.
This assertion takes a snapshot of the macro variables before and after
a macro invocation. This makes it easy to detect whether any macro
variables were modified or changed.
Currently, the macro only checks for global scope variables. In the future
it may be extended to work at multiple levels of nesting.
If you would like this feature, feel free to contribute / raise an issue /
engage the SASjs team directly.
Example usage:
%mp_assertscope(SNAPSHOT)
%let oops=I did it again;
%mp_assertscope(COMPARE,
desc=Checking macro variables against previous snapshot
)
@param [in] action (SNAPSHOT) The action to take. Valid values:
@li SNAPSHOT - take a copy of the current macro variables
@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 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
results. If it does not exist, it will be created, with the following format:
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|---|---|---|
|User Provided description|PASS|No out of scope variables created or modified|
<h4> Related Macros </h4>
@li mp_assert.sas
@li mp_assertcols.sas
@li mp_assertcolvals.sas
@li mp_assertdsobs.sas
@li mp_assertscope.test.sas
@version 9.2
@author Allan Bowe
**/
%macro mp_assertscope(action,
desc=Testing Scope Leakage,
scope=GLOBAL,
scopeds=work.mp_assertscope,
outds=work.test_results
)/*/STORE SOURCE*/;
%local ds test_result test_comments del add mod;
/* get current variables */
%if &action=SNAPSHOT %then %do;
proc sql;
create table &scopeds as
select name,offset,value
from dictionary.macros
where scope="&scope"
order by name,offset;
%end;
%else %if &action=COMPARE %then %do;
proc sql;
create table _data_ as
select name,offset,value
from dictionary.macros
where scope="&scope"
order by name,offset;
%let ds=&syslast;
proc compare base=&scopeds compare=&ds;
run;
%if &sysinfo=0 %then %do;
%let test_result=PASS;
%let test_comments=&scope Variables Unmodified;
%end;
%else %do;
proc sql noprint undo_policy=none;
select distinct name into: del separated by ' ' from &scopeds
where name not in (select name from &ds);
select distinct name into: add separated by ' ' from &ds
where name not in (select name from &scopeds);
select distinct a.name into: mod separated by ' '
from &scopeds a
inner join &ds b
on a.name=b.name
and a.offset=b.offset
where a.value ne b.value;
%let test_result=FAIL;
%let test_comments=%str(Mod:(&mod) Add:(&add) Del:(&del));
%end;
data ;
length test_description $256 test_result $4 test_comments $256;
test_description=symget('desc');
test_comments=symget('test_comments');
test_result=symget('test_result');
run;
%let ds=&syslast;
proc append base=&outds data=&ds;
run;
proc sql;
drop table &ds;
%end;
%mend mp_assertscope;

92
base/mp_coretable.sas Normal file
View File

@@ -0,0 +1,92 @@
/**
@file
@brief Create the permanent Core tables
@details Several macros in the [core](https://github.com/sasjs/core) library
make use of permanent tables. To avoid duplication in definitions, this
macro provides a central location for managing the corresponding DDL.
Example usage:
%mp_coretable(LOCKTABLE,libds=work.locktable)
@param [in] table_ref The type of table to create. Example values:
@li FILTER_DETAIL - For storing detailed filter values. Used by
mp_filterstore.sas.
@li FILTER_SUMMARY - For storing summary filter values. Used by
mp_filterstore.sas.
@li LOCKANYTABLE - For "locking" tables prior to multipass loads. Used by
mp_lockanytable.sas
@li MAXKEYTABLE - For storing the maximum retained key information. Used
by mp_retainedkey.sas
@param [in] libds= (0) The library.dataset reference used to create the table.
If not provided, then the DDL is simply printed to the log.
<h4> Related Macros </h4>
@li mp_filterstore.sas
@li mp_lockanytable.sas
@li mp_retainedkey.sas
@version 9.2
@author Allan Bowe
**/
%macro mp_coretable(table_ref,libds=0
)/*/STORE SOURCE*/;
%local outds ;
%let outds=%sysfunc(ifc(&libds=0,_data_,&libds));
proc sql;
%if &table_ref=LOCKTABLE %then %do;
create table &outds(
lock_lib char(8),
lock_ds char(32),
lock_status_cd char(10) not null,
lock_user_nm char(100) not null ,
lock_ref char(200),
lock_pid char(10),
lock_start_dttm num format=E8601DT26.6,
lock_end_dttm num format=E8601DT26.6,
constraint pk_mp_lockanytable primary key(lock_lib,lock_ds));
%end;
%else %if &table_ref=FILTER_SUMMARY %then %do;
create table &outds(
filter_rk num not null,
filter_hash char(32) not null,
filter_table char(41) not null,
processed_dttm num not null format=E8601DT26.6,
constraint pk_mpe_filteranytable
primary key(filter_rk));
%end;
%else %if &table_ref=FILTER_DETAIL %then %do;
create table &outds(
filter_hash char(32) not null,
filter_line num not null,
group_logic char(3) not null,
subgroup_logic char(3) not null,
subgroup_id num not null,
variable_nm varchar(32) not null,
operator_nm varchar(12) not null,
raw_value varchar(4000) not null,
processed_dttm num not null format=E8601DT26.6,
constraint pk_mpe_filteranytable
primary key(filter_hash,filter_line));
%end;
%else %if &table_ref=MAXKEYTABLE %then %do;
create table &outds(
keytable varchar(41) label='Base table in libref.dataset format',
keycolumn char(32) format=$32.
label='The Retained key field containing the key values.',
max_key num label=
'Integer representing current max RK or SK value in the KEYTABLE',
processed_dttm num format=E8601DT26.6
label='Datetime this value was last updated',
constraint pk_mpe_maxkeyvalues
primary key(keytable));
%end;
%if &libds=0 %then %do;
describe table &syslast;
drop table &syslast;
%end;
%mend mp_coretable;

View File

@@ -6,8 +6,7 @@
Credit for the rename approach: Credit for the rename approach:
https://communities.sas.com/t5/SAS-Programming/SAS-Function-to-convert-string-to-Legal-SAS-Name/m-p/27375/highlight/true#M5003 https://communities.sas.com/t5/SAS-Programming/SAS-Function-to-convert-string-to-Legal-SAS-Name/m-p/27375/highlight/true#M5003
Usage:
usage:
%mp_dirlist(path=/some/location, outds=myTable, maxdepth=MAX) %mp_dirlist(path=/some/location, outds=myTable, maxdepth=MAX)
@@ -23,12 +22,12 @@
X CMD) do please raise an issue! X CMD) do please raise an issue!
@param [in] path= for which to return contents @param [in] path= (%sysfunc(pathname(work))) Path for which to return contents
@param [in] fref= Provide a DISK engine fileref as an alternative to PATH @param [in] fref= (0) Provide a DISK engine fileref as an alternative to PATH
@param [in] maxdepth= (0) Set to a positive integer to indicate the level of @param [in] maxdepth= (0) Set to a positive integer to indicate the level of
subdirectory scan recursion - eg 3, to go `./3/levels/deep`. For unlimited subdirectory scan recursion - eg 3, to go `./3/levels/deep`. For unlimited
recursion, set to MAX. recursion, set to MAX.
@param [out] outds= the output dataset to create @param [out] outds= (work.mp_dirlist) The output dataset to create
@param [out] getattrs= (NO) If getattrs=YES then the doptname / foptname @param [out] getattrs= (NO) If getattrs=YES then the doptname / foptname
functions are used to scan all properties - any characters that are not functions are used to scan all properties - any characters that are not
valid in a SAS name (v7) are simply stripped, and the table is transposed valid in a SAS name (v7) are simply stripped, and the table is transposed
@@ -49,13 +48,15 @@
- OS SPECIFIC variables, if <code>getattrs=</code> is used. - OS SPECIFIC variables, if <code>getattrs=</code> is used.
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_existds.sas
@li mf_getvarlist.sas
@li mf_wordsinstr1butnotstr2.sas
@li mp_dropmembers.sas @li mp_dropmembers.sas
<h4> Related Macros </h4> <h4> Related Macros </h4>
@li mp_dirlist.test.sas @li mp_dirlist.test.sas
@version 9.2 @version 9.2
@author Allan Bowe
**/ **/
%macro mp_dirlist(path=%sysfunc(pathname(work)) %macro mp_dirlist(path=%sysfunc(pathname(work))
@@ -193,9 +194,29 @@ data &out_ds;
set &out_ds(where=(filepath ne '')); set &out_ds(where=(filepath ne ''));
run; run;
/* update main table */ /**
proc append base=&outds data=&out_ds; * The above transpose can mean that some updates create additional columns.
run; * This necessitates the occasional use of datastep over proc append.
*/
%if %mf_existds(&outds) %then %do;
%local basevars appvars newvars;
%let basevars=%mf_getvarlist(&outds);
%let appvars=%mf_getvarlist(&out_ds);
%let newvars=%length(%mf_wordsinstr1butnotstr2(Str1=&appvars,Str2=&basevars));
%if &newvars>0 %then %do;
data &outds;
set &outds &out_ds;
run;
%end;
%else %do;
proc append base=&outds data=&out_ds force nowarn;
run;
%end;
%end;
%else %do;
proc append base=&outds data=&out_ds;
run;
%end;
/* recursive call */ /* recursive call */
%if &maxdepth>&level or &maxdepth=MAX %then %do; %if &maxdepth>&level or &maxdepth=MAX %then %do;

View File

@@ -139,8 +139,9 @@ create table datalines1 as
/** /**
Due to long decimals cannot use best. format Due to long decimals cannot use best. format
So - use bestd. format and then use character functions to strip trailing So - use bestd. format and then use character functions to strip trailing
zeros, if NOT an integer!! zeros, if NOT an integer or missing!! Cannot use int() as it upsets
resolved code = ifc(int(VARIABLE)=VARIABLE note2err when there are missings.
resolved code = ifc( mod(coalesce(VARIABLE,0),1)=0
,put(VARIABLE,best32.) ,put(VARIABLE,best32.)
,substrn(put(VARIABLE,bestd32.),1 ,substrn(put(VARIABLE,bestd32.),1
,findc(put(VARIABLE,bestd32.),'0','TBK'))); ,findc(put(VARIABLE,bestd32.),'0','TBK')));
@@ -151,7 +152,7 @@ data datalines_2;
set datalines1 (where=(upcase(name) not in set datalines1 (where=(upcase(name) not in
('PROCESSED_DTTM','VALID_FROM_DTTM','VALID_TO_DTTM'))); ('PROCESSED_DTTM','VALID_FROM_DTTM','VALID_TO_DTTM')));
if type='num' then dataline= if type='num' then dataline=
cats('ifc(int(',name,')=',name,' cats('ifc(mod(coalesce(',name,',0),1)=0
,put(',name,',best32.-l) ,put(',name,',best32.-l)
,substrn(put(',name,',bestd32.-l),1 ,substrn(put(',name,',bestd32.-l),1
,findc(put(',name,',bestd32.-l),"0","TBK")))'); ,findc(put(',name,',bestd32.-l),"0","TBK")))');

View File

@@ -41,7 +41,7 @@ data _null_;
dsid=open("&ds.","i"); dsid=open("&ds.","i");
num=attrn(dsid,"nvars"); num=attrn(dsid,"nvars");
do i=1 to num; do i=1 to num;
header = trim(left(coalescec(varlabel(dsid,i),varname(dsid,i)))); header = cats(coalescec(varlabel(dsid,i),varname(dsid,i)));
put header @; put header @;
end; end;
rc=close(dsid); rc=close(dsid);

119
base/mp_ds2squeeze.sas Normal file
View File

@@ -0,0 +1,119 @@
/**
@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
<h4> SAS Macros </h4>
@li mf_getfilesize.sas
@li mf_getuniquefileref.sas
@li mf_getuniquename.sas
@li mp_getmaxvarlengths.sas
<h4> Related Programs </h4>
@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;

View File

@@ -109,7 +109,7 @@ data &outds;
output; output;
end; end;
if mod(SUBGROUP_ID,1) ne 0 then do; if mod(SUBGROUP_ID,1) ne 0 then do;
REASON_CD='SUBGROUP_ID should be integer, not '!!left(subgroup_id); REASON_CD='SUBGROUP_ID should be integer, not '!!cats(subgroup_id);
putlog REASON_CD= SUBGROUP_ID=; putlog REASON_CD= SUBGROUP_ID=;
call symputx('reason_cd',reason_cd,'l'); call symputx('reason_cd',reason_cd,'l');
call symputx('nobs',_n_,'l'); call symputx('nobs',_n_,'l');
@@ -127,7 +127,7 @@ data &outds;
if OPERATOR_NM not in if OPERATOR_NM not in
('=','>','<','<=','>=','BETWEEN','IN','NOT IN','NE','CONTAINS') ('=','>','<','<=','>=','BETWEEN','IN','NOT IN','NE','CONTAINS')
then do; then do;
REASON_CD='Invalid OPERATOR_NM: '!!left(OPERATOR_NM); REASON_CD='Invalid OPERATOR_NM: '!!cats(OPERATOR_NM);
putlog REASON_CD= OPERATOR_NM=; putlog REASON_CD= OPERATOR_NM=;
call symputx('reason_cd',reason_cd,'l'); call symputx('reason_cd',reason_cd,'l');
call symputx('nobs',_n_,'l'); call symputx('nobs',_n_,'l');

230
base/mp_filterstore.sas Normal file
View File

@@ -0,0 +1,230 @@
/**
@file
@brief Checks & Stores an input filter table and returns the Filter Key
@details Used to generate a FILTER_RK from an input query dataset. This
process requires several permanent tables (names are configurable). The
benefit of storing query values at backend is to enable stored 'views' of
filtered tables at frontend (ie, when building [SAS-Powered Apps](
https://sasapps.io)). This macro is also used in [Data Controller for SAS](
https://datacontroller.io).
@param [in] libds= The target dataset to be filtered (lib should be assigned)
@param [in] queryds= (WORK.FILTERQUERY) The temporary input query dataset to
be validated. Has the following format:
|GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767|
|---|---|---|---|---|---|
|AND|AND|1|SOME_BESTNUM|>|1|
|AND|AND|1|SOME_TIME|=|77333|
@param [in] filter_summary= (PERM.FILTER_SUMMARY) Permanent table containing
summary filter values. The definition is available by running
mp_coretable.sas as follows: `mp_coretable(FILTER_SUMMARY)`. Example
values:
|FILTER_RK:best.|FILTER_HASH:$32.|FILTER_TABLE:$41.|PROCESSED_DTTM:datetime19.|
|---|---|---|---|
|`1 `|`540E96F566D194AB58DD4C413C99C9DB `|`VIYA6014.MPE_TABLES `|`1956084246 `|
|`2 `|`87737DB9EEE2650F5C89956CEAD0A14F `|`VIYA6014.MPE_X_TEST `|`1956084452.1`|
|`3 `|`8048BD908DBBD83D013560734E90D394 `|`VIYA6014.MPE_TABLES `|`1956093620.6`|
@param [in] filter_detail= (PERM.FILTER_DETAIL) Permanent table containing
detailed (raw) filter values. The definition is available by running
mp_coretable.sas as follows: `mp_coretable(FILTER_DETAIL)`. Example
values:
|FILTER_HASH:$32.|FILTER_LINE:best.|GROUP_LOGIC:$3.|SUBGROUP_LOGIC:$3.|SUBGROUP_ID:best.|VARIABLE_NM:$32.|OPERATOR_NM:$12.|RAW_VALUE:$4000.|PROCESSED_DTTM:datetime19.|
|---|---|---|---|---|---|---|---|---|
|`540E96F566D194AB58DD4C413C99C9DB `|`1 `|`AND `|`AND `|`1 `|`LIBREF `|`CONTAINS `|`DC`|`1956084245.8 `|
|`540E96F566D194AB58DD4C413C99C9DB `|`2 `|`AND `|`OR `|`2 `|`DSN `|`= `|` MPE_LOCK_ANYTABLE `|`1956084245.8 `|
|`87737DB9EEE2650F5C89956CEAD0A14F `|`1 `|`AND `|`AND `|`1 `|`PRIMARY_KEY_FIELD `|`IN `|`(1,2,3) `|`1956084451.9 `|
@param [in] lock_table= (PERM.LOCK_TABLE) Permanent locking table. Used to
manage concurrent access. The definition is available by running
mp_coretable.sas as follows: `mp_coretable(LOCKTABLE)`.
@param [in] maxkeytable= (0) Optional permanent reference table used for
retained key tracking. Described in mp_retainedkey.sas.
@param [in] mdebug= set to 1 to enable DEBUG messages
@param [out] outresult= The result table with the FILTER_RK
@param [out] outquery= The original query, taken as extract after table load
<h4> SAS Macros </h4>
@li mf_getuniquename.sas
@li mf_getvalue.sas
@li mf_islibds.sas
@li mf_nobs.sas
@li mp_abort.sas
@li mp_filtercheck.sas
@li mp_hashdataset.sas
@li mp_retainedkey.sas
<h4> Related Macros </h4>
@li mp_filtercheck.sas
@li mp_filtergenerate.sas
@li mp_filtervalidate.sas
@li mp_filterstore.test.sas
@version 9.2
@author [Allan Bowe](https://www.linkedin.com/in/allanbowe)
**/
%macro mp_filterstore(libds=,
queryds=work.filterquery,
filter_summary=PERM.FILTER_SUMMARY,
filter_detail=PERM.FILTER_DETAIL,
lock_table=PERM.LOCK_TABLE,
maxkeytable=PERM.MAXKEYTABLE,
outresult=work.result,
outquery=work.query,
mdebug=1
);
%put &sysmacroname entry vars:;
%put _local_;
%local ds1 ds2 ds3 ds4 filter_hash;
%mp_abort(iftrue= (&syscc ne 0)
,mac=mp_filterstore
,msg=%str(syscc=&syscc on macro entry)
)
%mp_abort(iftrue= (%mf_islibds(&filter_summary)=0)
,mac=mp_filterstore
,msg=%str(Invalid filter_summary value: &filter_summary)
)
%mp_abort(iftrue= (%mf_islibds(&filter_detail)=0)
,mac=mp_filterstore
,msg=%str(Invalid filter_detail value: &filter_detail)
)
%mp_abort(iftrue= (%mf_islibds(&lock_table)=0)
,mac=mp_filterstore
,msg=%str(Invalid lock_table value: &lock_table)
)
/* validate query */
%mp_filtercheck(&queryds,targetds=&libds,abort=YES)
/* hash the result */
%let ds1=%mf_getuniquename(prefix=hashds);
%mp_hashdataset(&queryds,outds=&ds1,salt=&libds)
%let filter_hash=%upcase(%mf_getvalue(&ds1,hashkey));
%if &mdebug=1 %then %do;
data _null_;
putlog "filter_hash=&filter_hash";
set &ds1;
putlog (_all_)(=);
run;
%end;
/* check if data already exists for this hash */
data &outresult;
set &filter_summary;
where filter_hash="&filter_hash";
run;
%mp_abort(iftrue= (&syscc ne 0)
,mac=mp_filterstore
,msg=%str(syscc=&syscc after hash check)
)
%mp_abort(iftrue= ("&filter_hash "=" ")
,mac=mp_filterstore
,msg=%str(problem with filter_hash generation)
)
%if %mf_nobs(&outresult)=0 %then %do;
/* first update summary table */
%let ds3=%mf_getuniquename(prefix=filtersum);
data work.&ds3;
if 0 then set &filter_summary;
filter_table=symget('libds');
filter_hash="&filter_hash";
PROCESSED_DTTM=%sysfunc(datetime());
output;
stop;
run;
%mp_lockanytable(LOCK,
lib=%scan(&filter_summary,1,.)
,ds=%scan(&filter_summary,2,.)
,ref=MP_FILTERSTORE summary update - &filter_hash
,ctl_ds=&lock_table
)
%let ds4=%mf_getuniquename(prefix=filtersumappend);
%mp_retainedkey(
base_lib=%scan(&filter_summary,1,.)
,base_dsn=%scan(&filter_summary,2,.)
,append_lib=work
,append_dsn=&ds3
,retained_key=filter_rk
,business_key=filter_hash
,maxkeytable=&maxkeytable
,locktable=&lock_table
,outds=work.&ds4
)
proc append base=&filter_summary data=&ds4;
run;
%mp_lockanytable(UNLOCK,
lib=%scan(&filter_summary,1,.)
,ds=%scan(&filter_summary,2,.)
,ref=MP_FILTERSTORE summary update - &filter_hash
,ctl_ds=&lock_table
)
%if &syscc ne 0 %then %do;
data _null_;
set &ds4;
putlog (_all_)(=);
run;
%goto err;
%end;
data &outresult;
set &filter_summary;
where filter_hash="&filter_hash";
run;
/* Next, update detail table */
%let ds2=%mf_getuniquename(prefix=filterdetail);
data &ds2;
if 0 then set &filter_detail;
set &queryds;
format filter_hash $hex32. filter_line 8.;
filter_hash="&filter_hash";
filter_line=_n_;
PROCESSED_DTTM=%sysfunc(datetime());
run;
%mp_lockanytable(LOCK,
lib=%scan(&filter_detail,1,.)
,ds=%scan(&filter_detail,2,.)
,ref=MP_FILTERSTORE update - &filter_hash
,ctl_ds=&lock_table
)
proc append base=&filter_detail data=&ds2;
run;
%mp_lockanytable(UNLOCK,
lib=%scan(&filter_detail,1,.)
,ds=%scan(&filter_detail,2,.)
,ref=MP_FILTERSTORE detail update &filter_hash
,ctl_ds=&lock_table
)
%if &syscc ne 0 %then %do;
data _null_;
set &ds2;
putlog (_all_)(=);
run;
%goto err;
%end;
%end;
proc sort data=&filter_detail(where=(filter_hash="&filter_hash")) out=&outquery;
by filter_line;
run;
%err:
%mp_abort(iftrue= (&syscc ne 0)
,mac=mp_filterstore
,msg=%str(syscc=&syscc on macro exit)
)
%mend mp_filterstore;

View File

@@ -14,11 +14,11 @@
@param ds The dataset from which to obtain column metadata @param ds The dataset from which to obtain column metadata
@param outds= (work.cols) The output dataset to create. Sample data: @param outds= (work.cols) The output dataset to create. Sample data:
|NAME $|LENGTH 8|VARNUM 8|LABEL $|FORMAT $49|TYPE $1 |DDTYPE $| |NAME:$32.|LENGTH:best.|VARNUM:best.|LABEL:$256.|FMTNAME:$32.|FORMAT:$49.|TYPE:$1.|DDTYPE:$9.|
|---|---|---|---|---|---|---| |---|---|---|---|---|---|---|---|
|AIR|8|2|international airline travel (thousands)|8.|N|NUMERIC| |`AIR `|`8 `|`2 `|`international airline travel (thousands) `|` `|`8. `|`N `|`NUMERIC `|
|DATE|8|1|DATE|MONYY.|N|DATE| |`DATE `|`8 `|`1 `|`DATE `|`MONYY `|`MONYY. `|`N `|`DATE `|
|REGION|3|3|REGION|$3.|C|CHARACTER| |`REGION `|`3 `|`3 `|`REGION `|` `|`$3. `|`C `|`CHARACTER `|
<h4> Related Macros </h4> <h4> Related Macros </h4>
@li mf_getvarlist.sas @li mf_getvarlist.sas
@@ -30,26 +30,27 @@
**/ **/
%macro mp_getcols(ds, outds=work.cols); %macro mp_getcols(ds, outds=work.cols);
%local dropds;
proc contents noprint data=&ds proc contents noprint data=&ds
out=_data_ (keep=name type length label varnum format:); out=_data_ (keep=name type length label varnum format:);
run; run;
data &outds(keep=name type length varnum format label ddtype); %let dropds=&syslast;
set &syslast(rename=(format=format2 type=type2)); data &outds(keep=name type length varnum format label ddtype fmtname);
set &dropds(rename=(format=fmtname type=type2));
name=upcase(name); name=upcase(name);
if type2=2 then do; if type2=2 then do;
length format $49.; length format $49.;
if format2='' then format=cats('$',length,'.'); if fmtname='' then format=cats('$',length,'.');
else if formatl=0 then format=cats(format2,'.'); else if formatl=0 then format=cats(fmtname,'.');
else format=cats(format2,formatl,'.'); else format=cats(fmtname,formatl,'.');
type='C'; type='C';
ddtype='CHARACTER'; ddtype='CHARACTER';
end; end;
else do; else do;
if format2='' then format=cats(length,'.'); if fmtname='' then format=cats(length,'.');
else if formatl=0 then format=cats(format2,'.'); else if formatl=0 then format=cats(fmtname,'.');
else if formatd=0 then format=cats(format2,formatl,'.'); else if formatd=0 then format=cats(fmtname,formatl,'.');
else format=cats(format2,formatl,'.',formatd); else format=cats(fmtname,formatl,'.',formatd);
type='N'; type='N';
if format=:'DATETIME' or format=:'E8601DT' then ddtype='DATETIME'; if format=:'DATETIME' or format=:'E8601DT' then ddtype='DATETIME';
else if format=:'DATE' or format=:'DDMMYY' or format=:'MMDDYY' else if format=:'DATE' or format=:'DDMMYY' or format=:'MMDDYY'
@@ -61,5 +62,6 @@ data &outds(keep=name type length varnum format label ddtype);
end; end;
if label='' then label=name; if label='' then label=name;
run; run;
proc sql;
drop table &dropds;
%mend mp_getcols; %mend mp_getcols;

View File

@@ -158,7 +158,7 @@ run;
lab=" label="!!cats("'",tranwrd(label,"'","''"),"'"); lab=" label="!!cats("'",tranwrd(label,"'","''"),"'");
if notnull='yes' then notnul=' not null'; if notnull='yes' then notnul=' not null';
if type='char' then typ=cats('char(',length,')'); if type='char' then typ=cats('char(',length,')');
else if length ne 8 then typ='num length='!!left(length); else if length ne 8 then typ='num length='!!cats(length);
else typ='num'; else typ='num';
put name typ fmt notnul lab; put name typ fmt notnul lab;
run; run;

View File

@@ -48,6 +48,7 @@ https://support.sas.com/documentation/cdl/en/proc/61895/HTML/default/viewer.htm#
<h4> Related Macros </h4> <h4> Related Macros </h4>
@li mp_applyformats.sas
@li mp_getformats.test.sas @li mp_getformats.test.sas
@version 9.2 @version 9.2

View File

@@ -1,28 +1,46 @@
/** /**
@file mp_getmaxvarlengths.sas @file
@brief Scans a dataset to find the max length of the variable values @brief Scans a dataset to find the max length of the variable values
@details @details
This macro will scan a base dataset and produce an output dataset with two This macro will scan a base dataset and produce an output dataset with two
columns: columns:
- NAME Name of the base dataset column - 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 Character fields are often allocated very large widths (eg 32000) of which the
maximum value is likely to be much narrower. This macro was designed to maximum value is likely to be much narrower. Identifying such cases can be
enable a HTML table to be appropriately sized however this could be used as helpful in the following scenarios:
part of a data audit to ensure we aren't over-sizing our tables in relation to
the data therein. @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: Usage:
%mp_getmaxvarlengths(sashelp.class,outds=work.myds) %mp_getmaxvarlengths(sashelp.class,outds=work.myds)
@param libds Two part dataset (or view) reference. @param [in] libds Two part dataset (or view) reference.
@param outds= The output dataset to create @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 `|
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mcf_length.sas
@li mf_getuniquename.sas
@li mf_getvarlist.sas @li mf_getvarlist.sas
@li mf_getvartype.sas @li mf_getvartype.sas
@li mf_getvarformat.sas @li mf_getvarformat.sas
@@ -30,20 +48,32 @@
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe
<h4> Related Macros </h4>
@li mp_ds2squeeze.sas
@li mp_getmaxvarlengths.test.sas
**/ **/
%macro mp_getmaxvarlengths( %macro mp_getmaxvarlengths(
libds /* libref.dataset to analyse */ libds
,outds=work.mp_getmaxvarlengths /* name of output dataset to create */ ,num2char=NO
,outds=work.mp_getmaxvarlengths
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%local vars x var fmt; %local vars prefix x var fmt;
%let vars=%mf_getvarlist(libds=&libds); %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; proc sql;
create table &outds (rename=( create table &outds (rename=(
%do x=1 %to %sysfunc(countw(&vars,%str( ))); %do x=1 %to %sysfunc(countw(&vars,%str( )));
________&x=%scan(&vars,&x) &prefix.&x=%scan(&vars,&x)
%end; %end;
)) ))
as select as select
@@ -51,18 +81,21 @@ create table &outds (rename=(
%let var=%scan(&vars,&x); %let var=%scan(&vars,&x);
%if &x>1 %then ,; %if &x>1 %then ,;
%if %mf_getvartype(&libds,&var)=C %then %do; %if %mf_getvartype(&libds,&var)=C %then %do;
max(length(&var)) as ________&x max(lengthn(&var)) as &prefix.&x
%end; %end;
%else %do; %else %if &num2char=YES %then %do;
%let fmt=%mf_getvarformat(&libds,&var); %let fmt=%mf_getvarformat(&libds,&var);
%put fmt=&fmt; %put fmt=&fmt;
%if %str(&fmt)=%str() %then %do; %if %str(&fmt)=%str() %then %do;
max(length(cats(&var))) as ________&x max(lengthn(cats(&var))) as &prefix.&x
%end; %end;
%else %do; %else %do;
max(length(put(&var,&fmt))) as ________&x max(lengthn(put(&var,&fmt))) as &prefix.&x
%end; %end;
%end; %end;
%else %do;
max(mcf_length(&var)) as &prefix.&x
%end;
%end; %end;
from &libds; from &libds;

View File

@@ -33,36 +33,41 @@
%macro mp_init(prefix=SASJS %macro mp_init(prefix=SASJS
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%global %if %symexist(SASJS_PREFIX) %then %return; /* only run once */
&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 */
data _null_; %global
dttm=datetime(); SASJS_PREFIX /* the ONLY hard-coded global macro variable in SASjs */
call symputx("&prefix._init_num",dttm,'g'); &prefix._INIT_NUM /* initialisation time as numeric */
call symputx("&prefix._init_dttm",put(dttm,E8601DT26.6),'g'); &prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */
call symputx("&prefix.work",pathname('WORK'),'g'); &prefix.WORK /* avoid typing %sysfunc(pathname(work)) every time */
run; ;
options %let sasjs_prefix=&prefix;
noautocorrect /* disallow misspelled procedure names */
compress=CHAR /* default is none so ensure we have something! */ data _null_;
datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */ dttm=datetime();
%str(err)orcheck=STRICT /* catch errs in libname/filename statements */ call symputx("&sasjs_prefix._init_num",dttm,'g');
fmterr /* ensure err when a format cannot be found */ call symputx("&sasjs_prefix._init_dttm",put(dttm,E8601DT26.6),'g');
mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */ call symputx("&sasjs_prefix.work",pathname('WORK'),'g');
missing=. /* changing this can cause hard to detect errs */ run;
noquotelenmax /* avoid warnings for long strings */
noreplace /* avoid overwriting permanent datasets */ options
ps=max /* reduce log size slightly */ noautocorrect /* disallow misspelled procedure names */
ls=max /* reduce log even more and avoid word truncation */ compress=CHAR /* default is none so ensure we have something! */
validmemname=COMPATIBLE /* avoid special characters etc in table names */ datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */
validvarname=V7 /* avoid special characters etc in variable names */ dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */
varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */ %str(err)orcheck=STRICT /* catch errs in libname/filename statements */
varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */ 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; %mend mp_init;

View File

@@ -19,11 +19,12 @@
%mp_jsonout(OPEN,jref=tmp) %mp_jsonout(OPEN,jref=tmp)
%mp_jsonout(OBJ,class,jref=tmp) %mp_jsonout(OBJ,class,jref=tmp)
%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=YES)
%mp_jsonout(CLOSE,jref=tmp) %mp_jsonout(CLOSE,jref=tmp)
data _null_; data _null_;
infile tmp; infile tmp;
input;list; input;putlog _infile_;
run; run;
If you are building web apps with SAS then you are strongly encouraged to use If you are building web apps with SAS then you are strongly encouraged to use
@@ -31,22 +32,24 @@
[sasjs adapter](https://github.com/sasjs/adapter). [sasjs adapter](https://github.com/sasjs/adapter).
For more information see https://sasjs.io For more information see https://sasjs.io
@param action Valid values: @param [in] action Valid values:
@li OPEN - opens the JSON @li OPEN - opens the JSON
@li OBJ - sends a table with each row as an object @li OBJ - sends a table with each row as an object
@li ARR - sends a table with each row in an array @li ARR - sends a table with each row in an array
@li CLOSE - closes the JSON @li CLOSE - closes the JSON
@param [in] ds The dataset to send. Must be a work table.
@param ds the dataset to send. Must be a work table. @param [out] jref= (_webout) The fileref to which to send the JSON
@param jref= the fileref to which to send the JSON @param [out] dslabel= The name to give the table in the exported JSON
@param dslabel= the name to give the table in the exported JSON @param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table
@param fmt= Whether to keep or strip formats from the table @param [in] engine= (DATASTEP) Which engine to use to send the JSON. Options:
@param engine= Which engine to use to send the JSON, valid options are:
@li PROCJSON (default) @li PROCJSON (default)
@li DATASTEP (more reliable when data has non standard characters) @li DATASTEP (more reliable when data has non standard characters)
@param [in] missing= (NULL) Special numeric missing values can be sent as NULL
@param dbg= DEPRECATED - was used to conditionally add PRETTY to (eg `null`) or as STRING values (eg `".a"` or `".b"`)
proc json but this can cause line truncation in large files. @param [in] showmeta= (NO) Set to YES to output metadata alongside each table,
such as the column formats and types. The metadata is contained inside an
object with the same name as the table but prefixed with a dollar sign - ie,
`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`
<h4> Related Macros <h4> <h4> Related Macros <h4>
@li mp_ds2fmtds.sas @li mp_ds2fmtds.sas
@@ -57,129 +60,140 @@
**/ **/
%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=DATASTEP,dbg=0 %macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y
,engine=DATASTEP
,missing=NULL
,showmeta=NO
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%put output location=&jref; %local tempds colinfo fmtds i numcols;
%let numcols=0;
%if &action=OPEN %then %do; %if &action=OPEN %then %do;
options nobomfile; options nobomfile;
data _null_;file &jref encoding='utf-8'; data _null_;file &jref encoding='utf-8' ;
put '{"PROCESSED_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '"'; put '{"PROCESSED_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '"';
run; run;
%end; %end;
%else %if (&action=ARR or &action=OBJ) %then %do; %else %if (&action=ARR or &action=OBJ) %then %do;
options validvarname=upcase; options validvarname=upcase;
data _null_;file &jref mod encoding='utf-8'; data _null_; file &jref encoding='utf-8' mod;
put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";
/* grab col defs */
proc contents noprint data=&ds
out=_data_(keep=name type length format formatl formatd varnum label);
run;
%let colinfo=%scan(&syslast,2,.);
proc sort data=&colinfo;
by varnum;
run;
/* move meta to mac vars */
data _null_;
if _n_=1 then call symputx('numcols',nobs,'l');
set &colinfo end=last nobs=nobs;
name=upcase(name);
/* fix formats */
if type=2 or type=6 then do;
typelong='char';
length fmt $49.;
if format='' then fmt=cats('$',length,'.');
else if formatl=0 then fmt=cats(format,'.');
else fmt=cats(format,formatl,'.');
newlen=max(formatl,length);
end;
else do;
typelong='num';
if format='' then fmt='best.';
else if formatl=0 then fmt=cats(format,'.');
else if formatd=0 then fmt=cats(format,formatl,'.');
else fmt=cats(format,formatl,'.',formatd);
/* needs to be wide, for datetimes etc */
newlen=max(length,formatl,24);
end;
/* 32 char unique name */
newname='sasjs'!!substr(cats(put(md5(name),$hex32.)),1,27);
call symputx(cats('name',_n_),name,'l');
call symputx(cats('newname',_n_),newname,'l');
call symputx(cats('len',_n_),newlen,'l');
call symputx(cats('length',_n_),length,'l');
call symputx(cats('fmt',_n_),fmt,'l');
call symputx(cats('type',_n_),type,'l');
call symputx(cats('typelong',_n_),typelong,'l');
call symputx(cats('label',_n_),coalescec(label,name),'l');
run;
%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);
%if &engine=PROCJSON %then %do; %if &engine=PROCJSON %then %do;
data;run;%let tempds=&syslast; %if &missing=STRING %then %do;
proc sql;drop table &tempds; %put &sysmacroname: Special Missings not supported in proc json.;
%put &sysmacroname: Switching to DATASTEP engine;
%goto datastep;
%end;
data &tempds /view=&tempds;set &ds; data &tempds /view=&tempds;set &ds;
%if &fmt=N %then format _numeric_ best32.;; %if &fmt=N %then format _numeric_ best32.;;
/* PRETTY is necessary to avoid line truncation in large files */
proc json out=&jref pretty proc json out=&jref pretty
%if &action=ARR %then nokeys ; %if &action=ARR %then nokeys ;
;export &tempds / nosastags fmtnumeric; ;export &tempds / nosastags fmtnumeric;
run; run;
proc sql;drop view &tempds;
%end; %end;
%else %if &engine=DATASTEP %then %do; %else %if &engine=DATASTEP %then %do;
%local cols i tempds; %datastep:
%let cols=0; %if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1
%if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1 %then %do; %then %do;
%put &sysmacroname: &ds NOT FOUND!!!; %put &sysmacroname: &ds NOT FOUND!!!;
%return; %return;
%end; %end;
%if &fmt=Y %then %do;
%put converting every variable to a formatted variable;
/* see mp_ds2fmtds.sas for source */
proc contents noprint data=&ds
out=_data_(keep=name type length format formatl formatd varnum);
run;
proc sort;
by varnum;
run;
%local fmtds;
%let fmtds=%scan(&syslast,2,.);
/* prepare formats and varnames */
data _null_;
if _n_=1 then call symputx('nobs',nobs,'l');
set &fmtds end=last nobs=nobs;
name=upcase(name);
/* fix formats */
if type=2 or type=6 then do;
length fmt $49.;
if format='' then fmt=cats('$',length,'.');
else if formatl=0 then fmt=cats(format,'.');
else fmt=cats(format,formatl,'.');
newlen=max(formatl,length);
end;
else do;
if format='' then fmt='best.';
else if formatl=0 then fmt=cats(format,'.');
else if formatd=0 then fmt=cats(format,formatl,'.');
else fmt=cats(format,formatl,'.',formatd);
/* needs to be wide, for datetimes etc */
newlen=max(length,formatl,24);
end;
/* 32 char unique name */
newname='sasjs'!!substr(cats(put(md5(name),$hex32.)),1,27);
call symputx(cats('name',_n_),name,'l'); %if &fmt=Y %then %do;
call symputx(cats('newname',_n_),newname,'l'); data _data_;
call symputx(cats('len',_n_),newlen,'l');
call symputx(cats('fmt',_n_),fmt,'l');
call symputx(cats('type',_n_),type,'l');
run;
data &fmtds;
/* rename on entry */ /* rename on entry */
set &ds(rename=( set &ds(rename=(
%local i; %do i=1 %to &numcols;
%do i=1 %to &nobs;
&&name&i=&&newname&i &&name&i=&&newname&i
%end; %end;
)); ));
%do i=1 %to &nobs; %do i=1 %to &numcols;
length &&name&i $&&len&i; length &&name&i $&&len&i;
&&name&i=left(put(&&newname&i,&&fmt&i)); &&name&i=left(put(&&newname&i,&&fmt&i));
drop &&newname&i; drop &&newname&i;
%end; %end;
if _error_ then call symputx('syscc',1012); if _error_ then call symputx('syscc',1012);
run; run;
%let ds=&fmtds; %let fmtds=&syslast;
%end; /* &fmt=Y */ %end;
data _null_;file &jref mod encoding='utf-8';
put "["; call symputx('cols',0,'l');
proc sort
data=sashelp.vcolumn(where=(libname='WORK' & memname="%upcase(&ds)"))
out=_data_;
by varnum;
data _null_;
set _last_ end=last;
call symputx(cats('name',_n_),name,'l');
call symputx(cats('type',_n_),type,'l');
call symputx(cats('len',_n_),length,'l');
if last then call symputx('cols',_n_,'l');
run;
proc format; /* credit yabwon for special null removal */ proc format; /* credit yabwon for special null removal */
value bart ._ - .z = null value bart (default=40)
%if &missing=NULL %then %do;
._ - .z = null
%end;
%else %do;
._ = [quote()]
. = null
.a - .z = [quote()]
%end;
other = [best.]; other = [best.];
data;run; %let tempds=&syslast; /* temp table for spesh char management */
proc sql; drop table &tempds;
data &tempds/view=&tempds; data &tempds/view=&tempds;
attrib _all_ label=''; attrib _all_ label='';
%do i=1 %to &cols; %do i=1 %to &numcols;
%if &&type&i=char %then %do; %if &&typelong&i=char or &fmt=Y %then %do;
length &&name&i $32767; length &&name&i $32767;
format &&name&i $32767.; format &&name&i $32767.;
%end; %end;
%end; %end;
set &ds; %if &fmt=Y %then %do;
set &fmtds;
%end;
%else %do;
set &ds;
%end;
format _numeric_ bart.; format _numeric_ bart.;
%do i=1 %to &cols; %do i=1 %to &numcols;
%if &&type&i=char %then %do; %if &&typelong&i=char or &fmt=Y %then %do;
&&name&i='"'!!trim(prxchange('s/"/\"/',-1, &&name&i='"'!!trim(prxchange('s/"/\"/',-1,
prxchange('s/'!!'0A'x!!'/\n/',-1, prxchange('s/'!!'0A'x!!'/\n/',-1,
prxchange('s/'!!'0D'x!!'/\r/',-1, prxchange('s/'!!'0D'x!!'/\r/',-1,
@@ -189,44 +203,65 @@
%end; %end;
%end; %end;
run; run;
/* write to temp loc to avoid _webout truncation /* write to temp loc to avoid _webout truncation
- https://support.sas.com/kb/49/325.html */ - https://support.sas.com/kb/49/325.html */
filename _sjs temp lrecl=131068 encoding='utf-8'; filename _sjs temp lrecl=131068 encoding='utf-8';
data _null_; file _sjs lrecl=131068 encoding='utf-8' mod; data _null_; file _sjs lrecl=131068 encoding='utf-8' mod ;
if _n_=1 then put "[";
set &tempds; set &tempds;
if _n_>1 then put "," @; put if _n_>1 then put "," @; put
%if &action=ARR %then "[" ; %else "{" ; %if &action=ARR %then "[" ; %else "{" ;
%do i=1 %to &cols; %do i=1 %to &numcols;
%if &i>1 %then "," ; %if &i>1 %then "," ;
%if &action=OBJ %then """&&name&i"":" ; %if &action=OBJ %then """&&name&i"":" ;
&&name&i &&name&i
%end; %end;
%if &action=ARR %then "]" ; %else "}" ; ; %if &action=ARR %then "]" ; %else "}" ; ;
proc sql;
drop view &tempds;
/* now write the long strings to _webout 1 byte at a time */ /* now write the long strings to _webout 1 byte at a time */
data _null_; data _null_;
length filein 8 fileid 8; length filein 8 fileid 8;
filein = fopen("_sjs",'I',1,'B'); filein=fopen("_sjs",'I',1,'B');
fileid = fopen("&jref",'A',1,'B'); fileid=fopen("&jref",'A',1,'B');
rec = '20'x; rec='20'x;
do while(fread(filein)=0); do while(fread(filein)=0);
rc = fget(filein,rec,1); rc=fget(filein,rec,1);
rc = fput(fileid, rec); rc=fput(fileid, rec);
rc =fwrite(fileid); rc=fwrite(fileid);
end; end;
rc = fclose(filein); /* close out the table */
rc = fclose(fileid); rc=fput(fileid, "]");
rc=fwrite(fileid);
rc=fclose(filein);
rc=fclose(fileid);
run; run;
filename _sjs clear; filename _sjs clear;
data _null_; file &jref mod encoding='utf-8'; %end;
put "]";
proc sql;
drop view &tempds;
drop table &colinfo;
%if &showmeta=YES %then %do;
data _null_; file &jref encoding='utf-8' mod;
put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";
do i=1 to &numcols;
name=quote(trim(symget(cats('name',i))));
format=quote(trim(symget(cats('fmt',i))));
label=quote(trim(symget(cats('label',i))));
length=quote(trim(symget(cats('length',i))));
type=quote(trim(symget(cats('typelong',i))));
if i>1 then put "," @@;
put name ':{"format":' format ',"label":' label
',"length":' length ',"type":' type '}';
end;
put '}}';
run; run;
%end; %end;
%end; %end;
%else %if &action=CLOSE %then %do; %else %if &action=CLOSE %then %do;
data _null_;file &jref encoding='utf-8' mod; data _null_; file &jref encoding='utf-8' mod ;
put "}"; put "}";
run; run;
%end; %end;

View File

@@ -5,19 +5,18 @@
Only useful if every update uses the macro! Used heavily within Only useful if every update uses the macro! Used heavily within
[Data Controller for SAS](https://datacontroller.io). [Data Controller for SAS](https://datacontroller.io).
The underlying table is structured as per the MAKETABLE action.
@param [in] action The action to be performed. Valid values: @param [in] action The action to be performed. Valid values:
@li LOCK - Sets the lock flag, also confirms if a SAS lock is available @li LOCK - Sets the lock flag, also confirms if a SAS lock is available
@li UNLOCK - Unlocks the table @li UNLOCK - Unlocks the table
@li MAKETABLE - creates the control table (ctl_ds)
@param [in] lib= (WORK) The libref of the table to lock. Should already be @param [in] lib= (WORK) The libref of the table to lock. Should already be
assigned. assigned.
@param [in] ds= The dataset to lock @param [in] ds= The dataset to lock
@param [in] ref= A meaningful reference to enable the lock to be traced. Max @param [in] ref= A meaningful reference to enable the lock to be traced. Max
length is 200 characters. length is 200 characters.
@param [out] ctl_ds= (0) The control table which controls the actual locking. @param [out] ctl_ds= (0) The control table which controls the actual locking.
Should already be assigned and available. Should already be assigned and available. The definition is available by
running mp_coretable.sas as follows: `%mp_coretable(LOCKTABLE)`.
@param [in] loops= (25) Number of times to check for a lock. @param [in] loops= (25) Number of times to check for a lock.
@param [in] loop_secs= (1) Seconds to wait between each lock attempt @param [in] loop_secs= (1) Seconds to wait between each lock attempt
@@ -221,19 +220,6 @@ run;
%let abortme=1; %let abortme=1;
%end; %end;
%end; %end;
%else %if &action=MAKETABLE %then %do;
proc sql;
create table &ctl_ds(
lock_lib char(8),
lock_ds char(32),
lock_status_cd char(10) not null,
lock_user_nm char(100) not null ,
lock_ref char(200),
lock_pid char(10),
lock_start_dttm num format=E8601DT26.6,
lock_end_dttm num format=E8601DT26.6,
constraint pk_mp_lockanytable primary key(lock_lib,lock_ds));
%end;
%else %do; %else %do;
%let msg=lock_anytable given unsupported action (&action); %let msg=lock_anytable given unsupported action (&action);
%let abortme=1; %let abortme=1;

View File

@@ -10,8 +10,6 @@
according to the variable types and formats. according to the variable types and formats.
TODO: TODO:
@li Respect PKs
@li Respect NOT NULLs
@li Consider dates, datetimes, times, integers etc @li Consider dates, datetimes, times, integers etc
Usage: Usage:
@@ -27,16 +25,23 @@
); );
%mp_makedata(work.example) %mp_makedata(work.example)
@param [in] libds The empty table in which to create data @param [in] libds The empty table (libref.dataset) in which to create data
@param [out] obs= (500) The number of records to create. @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.
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_getuniquename.sas @li mf_getuniquename.sas
@li mf_getvarlen.sas @li mf_getvarlen.sas
@li mf_getvarlist.sas
@li mf_islibds.sas
@li mf_nobs.sas @li mf_nobs.sas
@li mp_getcols.sas @li mp_getcols.sas
@li mp_getpk.sas @li mp_getpk.sas
<h4> Related Macros </h4>
@li mp_makedata.test.sas
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe
@@ -44,44 +49,59 @@
%macro mp_makedata(libds %macro mp_makedata(libds
,obs=500 ,obs=500
,seed=1
)/*/STORE SOURCE*/; )/*/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; %put &sysmacroname: &libds has data, it will not be recreated;
%return; %return;
%end; %end;
%local ds1 c1 n1; /* set up temporary vars */
%let ds1=%mf_getuniquename(prefix=mp_makedata); %let ds1=%mf_getuniquename(prefix=mp_makedatads1);
%let c1=%mf_getuniquename(prefix=mp_makedatacol); %let ds2=%mf_getuniquename(prefix=mp_makedatads2);
%let n1=%mf_getuniquename(prefix=mp_makedatacol); %let lib=%scan(&libds,1,.);
data &ds1; %let ds=%scan(&libds,2,.);
/* grab the primary key vars */
%mp_getpk(&lib,ds=&ds,outds=&ds1)
proc sql noprint;
select coalescec(pk_fields,'_all_') into: pk_fields from &ds1;
data &ds2;
if 0 then set &libds; if 0 then set &libds;
do _n_=1 to &obs; do _n_=1 to &obs;
&c1=repeat(uuidgen(),10);
&n1=ranuni(1)*5000000;
drop &c1 &n1;
%let charvars=%mf_getvarlist(&libds,typefilter=C); %let charvars=%mf_getvarlist(&libds,typefilter=C);
%if &charvars ^= %then %do i=1 %to %sysfunc(countw(&charvars)); %if &charvars ^= %then %do i=1 %to %sysfunc(countw(&charvars));
%let col=%scan(&charvars,&i); %let col=%scan(&charvars,&i);
&col=subpad(&c1,1,%mf_getvarlen(&libds,&col)); /* create random value based on observation number and colum length */
&col=repeat(put(md5(cats(_n_)),$hex32.),%mf_getvarlen(&libds,&col)/32);
%end; %end;
%let numvars=%mf_getvarlist(&libds,typefilter=N); %let numvars=%mf_getvarlist(&libds,typefilter=N);
%if &numvars ^= %then %do i=1 %to %sysfunc(countw(&numvars)); %if &numvars ^= %then %do i=1 %to %sysfunc(countw(&numvars));
%let col=%scan(&numvars,&i); %let col=%scan(&numvars,&i);
&col=&n1; &col=_n_;
%end; %end;
output; output;
end; end;
stop;
run;
proc sort data=&ds2 nodupkey;
by &pk_fields;
run; run;
proc append base=&libds data=&ds1; proc append base=&libds data=&ds2;
run; run;
proc sql; proc sql;
drop table &ds1; drop table &ds1, &ds2;
%mend mp_makedata; %mend mp_makedata;

View File

@@ -3,13 +3,15 @@
@brief Reset an option to original value @brief Reset an option to original value
@details Inspired by the SAS Jedi - @details Inspired by the SAS Jedi -
https://blogs.sas.com/content/sastraining/2012/08/14/jedi-sas-tricks-reset-sas-system-options https://blogs.sas.com/content/sastraining/2012/08/14/jedi-sas-tricks-reset-sas-system-options
Called as follows:
options obs=30; Called as follows:
%mp_resetoption(OBS)
options obs=30 ps=max;
%mp_resetoption(OBS)
%mp_resetoption(PS)
@param option the option to reset @param [in] option the option to reset
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe

246
base/mp_retainedkey.sas Normal file
View File

@@ -0,0 +1,246 @@
/**
@file
@brief Generate and apply retained key values to a staging table
@details This macro will populate a staging table with a Retained Key based on
a business key and a base (target) table.
Definition of retained key ([source](
http://bukhantsov.org/2012/04/what-is-data-vault/)):
> The retained key is a key which is mapped to business key one-to-one. In
> comparison, the surrogate key includes time and there can be many surrogate
> keys corresponding to one business key. This explains the name of the key,
> it is retained with insertion of a new version of a row while surrogate key
> is increasing.
This macro is designed to be used as part of a wider load / ETL process (such
as the one in [Data Controller for SAS](https://datacontroller.io)).
Specifically, the macro assumes that the base table has already been 'locked'
(eg with the mp_lockanytable.sas macro) prior to invocation. Also, several
tables are assumed to exist (names are configurable):
@li work.staging_table - the staged data, minus the retained key element
@li permlib.base_table - the target table to be loaded (**not** loaded by this
macro)
@li permlib.maxkeytable - optional, used to store load metaadata.
The definition is available by running mp_coretable.sas as follows:
`mp_coretable(MAXKEYTABLE)`.
@li permlib.locktable - Necessary if maxkeytable is being populated. The
definition is available by running mp_coretable.sas as follows:
`mp_coretable(LOCKTABLE)`.
@param [in] base_lib= (WORK) Libref of the base (target) table.
@param [in] base_dsn= (BASETABLE) Name of the base (target) table.
@param [in] append_lib= (WORK) Libref of the staging table
@param [in] append_dsn= (APPENDTABLE) Name of the staging table
@param [in] retained_key= (DEFAULT_RK) Name of RK to generate (should exist on
base table)
@param [in] business_key= (PK1 PK2) Business key against which to generate
RK values. Should be unique and not null on the staging table.
@param [in] check_uniqueness=(NO) Set to yes to perform a uniqueness check.
Recommended if there is a chance that the staging data is not unique on the
business key.
@param [in] maxkeytable= (0) Provide a maxkeytable libds reference here, to
store load metadata (maxkey val, load time). Set to zero if metadata is not
required, eg, when preparing a 'dummy' load. Structure is described above.
See below for sample data.
|KEYTABLE:$32.|KEYCOLUMN:$32.|MAX_KEY:best.|PROCESSED_DTTM:E8601DT26.6|
|---|---|---|---|
|`DC487173.MPE_SELECTBOX `|`SELECTBOX_RK `|`55 `|`1950427787.8 `|
|`DC487173.MPE_FILTERANYTABLE `|`filter_rk `|`14 `|`1951053886.8 `|
@param [in] locktable= (0) If updating the maxkeytable, provide the libds
reference to the lock table (per mp_lockanytable.sas macro)
@param [in] filter_str= Apply a filter - useful for SCD2 or BITEMPORAL loads.
Example: `filter_str=%str( (where=( &now < &tech_to)) )`
@param [out] outds= (WORK.APPEND) Output table (staging table + retained key)
<h4> SAS Macros </h4>
@li mf_existvar.sas
@li mf_getquotedstr.sas
@li mf_getuniquename.sas
@li mf_nobs.sas
@li mp_abort.sas
@li mp_lockanytable.sas
<h4> Related Macros </h4>
@li mp_filterstore.sas
@li mp_retainedkey.test.sas
@version 9.2
**/
%macro mp_retainedkey(
base_lib=WORK
,base_dsn=BASETABLE
,append_lib=WORK
,append_dsn=APPENDTABLE
,retained_key=DEFAULT_RK
,business_key= PK1 PK2
,check_uniqueness=NO
,maxkeytable=0
,locktable=0
,outds=WORK.APPEND
,filter_str=
);
%put &sysmacroname entry vars:;
%put _local_;
%local base_libds app_libds key_field check maxkey idx_pk newkey_cnt iserr
msg x tempds1 tempds2 comma_pk appnobs checknobs dropvar tempvar idx_val;
%let base_libds=%upcase(&base_lib..&base_dsn);
%let app_libds=%upcase(&append_lib..&append_dsn);
%let tempds1=%mf_getuniquename();
%let tempds2=%mf_getuniquename();
%let comma_pk=%mf_getquotedstr(in_str=%str(&business_key),dlm=%str(,),quote=);
%let outds=%sysfunc(ifc(%index(&outds,.)=0,work.&outds,&outds));
/* validation checks */
%let iserr=0;
%if &syscc>0 %then %do;
%let iserr=1;
%let msg=%str(SYSCC=&syscc on macro entry);
%end;
%else %if %sysfunc(exist(&base_libds))=0 %then %do;
%let iserr=1;
%let msg=%str(Base LIBDS (&base_libds) expected but NOT FOUND);
%end;
%else %if %sysfunc(exist(&app_libds))=0 %then %do;
%let iserr=1;
%let msg=%str(Append LIBDS (&app_libds) expected but NOT FOUND);
%end;
%else %if &maxkeytable ne 0 and %sysfunc(exist(&maxkeytable))=0 %then %do;
%let iserr=1;
%let msg=%str(Maxkeytable (&maxkeytable) expected but NOT FOUND);
%end;
%else %if &maxkeytable ne 0 and %sysfunc(exist(&locktable))=0 %then %do;
%let iserr=1;
%let msg=%str(Locktable (&locktable) expected but NOT FOUND);
%end;
%else %if %length(&business_key)=0 %then %do;
%let iserr=1;
%let msg=%str(Business key (&business_key) expected but NOT FOUND);
%end;
%do x=1 %to %sysfunc(countw(&business_key));
/* check business key values exist */
%let key_field=%scan(&business_key,&x,%str( ));
%if not %mf_existvar(&app_libds,&key_field) %then %do;
%let iserr=1;
%let msg=Business key (&key_field) not found on &app_libds!;
%goto err;
%end;
%else %if not %mf_existvar(&base_libds,&key_field) %then %do;
%let iserr=1;
%let msg=Business key (&key_field) not found on &base_libds!;
%goto err;
%end;
%end;
%err:
%if &iserr=1 %then %do;
/* err case so first perform an unlock of the base table before exiting */
%mp_lockanytable(
UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable
)
%end;
%mp_abort(iftrue=(&iserr=1),mac=mp_retainedkey,msg=%superq(msg))
proc sql noprint;
select sum(max(&retained_key),0) into: maxkey from &base_libds;
/**
* get base table RK and bus field values for lookup
*/
proc sql noprint;
create table &tempds1 as
select distinct &comma_pk,&retained_key
from &base_libds &filter_str
order by &comma_pk,&retained_key;
%if &check_uniqueness=YES %then %do;
select count(*) into:checknobs
from (select distinct &comma_pk from &app_libds);
select count(*) into: appnobs from &app_libds; /* might be view */
%if &checknobs ne &appnobs %then %do;
%let msg=Source table &app_libds is not unique on (&business_key);
%let iserr=1;
%end;
%end;
%if &iserr=1 %then %do;
/* err case so first perform an unlock of the base table before exiting */
%mp_lockanytable(
UNLOCK,lib=&base_lib,ds=&base_dsn,ref=%superq(msg),ctl_ds=&locktable
)
%end;
%mp_abort(iftrue= (&iserr=1),mac=mp_retainedkey,msg=%superq(msg))
%if %mf_existvar(&app_libds,&retained_key)
%then %let dropvar=(drop=&retained_key);
/* prepare interim table with retained key populated for matching keys */
proc sql noprint;
create table &tempds2 as
select b.&retained_key, a.*
from &app_libds &dropvar a
left join &tempds1 b
on 1
%do idx_pk=1 %to %sysfunc(countw(&business_key));
%let idx_val=%scan(&business_key,&idx_pk);
and a.&idx_val=b.&idx_val
%end;
order by &retained_key;
/* identify the number of entries without retained keys (new records) */
select count(*) into: newkey_cnt
from &tempds2
where missing(&retained_key);
quit;
/**
* Update maxkey table if link provided
*/
%if &maxkeytable ne 0 %then %do;
proc sql noprint;
select count(*) into: check from &maxkeytable
where upcase(keytable)="&base_libds";
%mp_lockanytable(LOCK
,lib=%scan(&maxkeytable,1,.)
,ds=%scan(&maxkeytable,2,.)
,ref=Updating maxkeyvalues with mp_retainedkey
,ctl_ds=&locktable
)
proc sql;
%if &check=0 %then %do;
insert into &maxkeytable
set keytable="&base_libds"
,keycolumn="&retained_key"
,max_key=%eval(&maxkey+&newkey_cnt)
,processed_dttm="%sysfunc(datetime(),E8601DT26.6)"dt;
%end;
%else %do;
update &maxkeytable
set max_key=%eval(&maxkey+&newkey_cnt)
,processed_dttm="%sysfunc(datetime(),E8601DT26.6)"dt
where keytable="&base_libds";
%end;
%mp_lockanytable(UNLOCK
,lib=%scan(&maxkeytable,1,.)
,ds=%scan(&maxkeytable,2,.)
,ref=Updating maxkeyvalues with maxkey=%eval(&maxkey+&newkey_cnt)
,ctl_ds=&locktable
)
%end;
/* fill in the missing retained key values */
%let tempvar=%mf_getuniquename();
data &outds(drop=&tempvar);
retain &tempvar %eval(&maxkey+1);
set &tempds2;
if &retained_key =. then &retained_key=&tempvar;
&tempvar=&tempvar+1;
run;
%mend mp_retainedkey;

View File

@@ -11,9 +11,7 @@
This macro will only work for BASE (V9) engine libraries. It works by This macro will only work for BASE (V9) engine libraries. It works by
creating a copy of the dataset (without data, WITH constraints) in the same creating a copy of the dataset (without data, WITH constraints) in the same
library, appending a sorted view into it, and finally - renaming it. By library, appending a sorted view into it, and finally - renaming it.
default, COMPRESS=CHAR and REUSE=YES will be applied, this behaviour can
be adjusted using the `dsoptions=` parameter.
Example usage: Example usage:
@@ -41,7 +39,6 @@
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe
@source https://github.com/sasjs/core
**/ **/

View File

@@ -76,7 +76,7 @@
else do; else do;
x+1; x+1;
call symputx(name,quote(cats(value)),'l'); call symputx(name,quote(cats(value)),'l');
call symputx('pval'!!left(x),name,'l'); call symputx(cats('pval',x),name,'l');
call symputx('pcnt',x,'l'); call symputx('pcnt',x,'l');
end; end;
run; run;

View File

@@ -20,15 +20,24 @@
;;;; ;;;;
run; run;
For more examples, see mp_validatecol.test.sas
Tip - when contributing, use https://regex101.com to test the regex validity!
@param [in] incol The column to be validated @param [in] incol The column to be validated
@param [in] rule The rule to apply. Current rules: @param [in] rule The rule to apply. Current rules:
@li ISINT - checks if the variable is an integer
@li ISNUM - checks if the variable is numeric @li ISNUM - checks if the variable is numeric
@li LIBDS - matches LIBREF.DATASET format @li LIBDS - matches LIBREF.DATASET format
@li FORMAT - checks if the provided format is syntactically valid
@param [out] outcol The variable to create, with the results of the match @param [out] outcol The variable to create, with the results of the match
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_getuniquename.sas @li mf_getuniquename.sas
<h4> Related Macros </h4>
@li mp_validatecol.test.sas
@version 9.3 @version 9.3
**/ **/
@@ -38,7 +47,15 @@
%local tempcol; %local tempcol;
%let tempcol=%mf_getuniquename(); %let tempcol=%mf_getuniquename();
%if &rule=ISNUM %then %do; %if &rule=ISINT %then %do;
&outcol=0;
if not missing(&incol) then do;
&tempcol=input(&incol,?? best32.);
if not missing(&tempcol) then if mod(&tempcol,1)=0 then &outcol=1;
end;
drop &tempcol;
%end;
%else %if &rule=ISNUM %then %do;
/* /*
credit SØREN LASSEN credit SØREN LASSEN
https://sasmacro.blogspot.com/2009/06/welcome-isnum-macro.html https://sasmacro.blogspot.com/2009/06/welcome-isnum-macro.html
@@ -62,5 +79,19 @@
if prxmatch(&tempcol, trim(&incol)) then &outcol=1; if prxmatch(&tempcol, trim(&incol)) then &outcol=1;
else &outcol=0; else &outcol=0;
%end; %end;
%else %if &rule=FORMAT %then %do;
/* match valid format - regex could probably be improved */
if _n_=1 then do;
retain &tempcol;
&tempcol=prxparse('/^[_a-z\$]\w{0,31}\.[0-9]*$/i');
if missing(&tempcol) then do;
putlog "%str(ERR)OR: Invalid expression for FORMAT";
stop;
end;
drop &tempcol;
end;
if prxmatch(&tempcol, trim(&incol)) then &outcol=1;
else &outcol=0;
%end;
%mend mp_validatecol; %mend mp_validatecol;

82
fcmp/mcf_length.sas Normal file
View File

@@ -0,0 +1,82 @@
/**
@file
@brief Returns the length of a numeric value
@details
Returns the length, in bytes, of a numeric value. If the value is
missing, then 0 is returned.
The function itself takes the following (positional) parameters:
| PARAMETER | DESCRIPTION |
|---|---|
| var | variable (or value) to be tested|
Usage:
%mcf_length(wrap=YES, insert_cmplib=YES)
data _null_;
ina=1;
inb=10000000;
inc=12345678;
ind=.;
outa=mcf_length(ina);
outb=mcf_length(inb);
outc=mcf_length(inc);
outd=mcf_length(ind);
put (out:)(=);
run;
Returns:
> outa=3 outb=4 outc=5 outd=0
@param [out] wrap= (NO) Choose YES to add the proc fcmp wrapper.
@param [out] insert_cmplib= (NO) Choose YES to insert the package into the
CMPLIB reference.
@param [out] lib= (work) The output library in which to create the catalog.
@param [out] cat= (sasjs) The output catalog in which to create the package.
@param [out] pkg= (utils) The output package in which to create the function.
Uses a 3 part format: libref.catalog.package
<h4> SAS Macros </h4>
@li mf_existfunction.sas
<h4> Related Macros </h4>
@li mcf_length.test.sas
**/
%macro mcf_length(wrap=NO
,insert_cmplib=NO
,lib=WORK
,cat=SASJS
,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 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;
else if trunc(var,7)=var then len=7;
else len=8;
return(len);
endsub;
%if &wrap=YES %then %do;
quit;
%end;
%if &insert_cmplib=YES %then %do;
options insert=(CMPLIB=(&lib..&cat));
%end;
%mend mcf_length;

View File

@@ -69,7 +69,7 @@
%if %mf_existfunction(stpsrv_header)=1 %then %return; %if %mf_existfunction(stpsrv_header)=1 %then %return;
%if &wrap=YES %then %do; %if &wrap=YES %then %do;
proc fcmp outcat=&lib..&cat..&pkg; proc fcmp outlib=&lib..&cat..&pkg;
%end; %end;
function stpsrv_header(name $, value $); function stpsrv_header(name $, value $);

View File

@@ -39,6 +39,9 @@
@param [out] pkg= (utils) The output package in which to create the function. @param [out] pkg= (utils) The output package in which to create the function.
Uses a 3 part format: libref.catalog.package Uses a 3 part format: libref.catalog.package
<h4> SAS Macros </h4>
@li mf_existfunction.sas
**/ **/
%macro mcf_string2file(wrap=NO %macro mcf_string2file(wrap=NO
@@ -48,8 +51,10 @@
,pkg=UTILS ,pkg=UTILS
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%if %mf_existfunction(mcf_string2file)=1 %then %return;
%if &wrap=YES %then %do; %if &wrap=YES %then %do;
proc fcmp outcat=&lib..&cat..&pkg; proc fcmp outlib=&lib..&cat..&pkg;
%end; %end;
function mcf_string2file(filepath $, string $, mode $); function mcf_string2file(filepath $, string $, mode $);

View File

@@ -295,7 +295,7 @@ run;
prop='Connection.DBMS.Property.SERVER.Name.xmlKey.txt'; prop='Connection.DBMS.Property.SERVER.Name.xmlKey.txt';
rc=metadata_getprop(uri,prop,server,""); rc=metadata_getprop(uri,prop,server,"");
end; end;
if server^='' then server='server='!!server; if server^='' then server='server='!!quote(cats(server));
call symputx('server',server,'l'); call symputx('server',server,'l');
/* get SCHEMA value */ /* get SCHEMA value */
@@ -441,11 +441,11 @@ run;
run; run;
%put NOTE: Executing the following:/; %put NOTE-; %put NOTE: Executing the following:/; %put NOTE-;
%put NOTE- libname &libref TERADATA server=&path schema=&schema ; %put NOTE- libname &libref TERADATA server="&path" schema=&schema ;
%put NOTe- authdomain=&authdomain; %put NOTe- authdomain=&authdomain;
%put NOTE-; %put NOTE-;
libname &libref TERADATA server=&path schema=&schema authdomain=&authdomain; libname &libref TERADATA server="&path" schema=&schema authdomain=&authdomain;
%end; %end;
%else %if &engine= %then %do; %else %if &engine= %then %do;
%put NOTE: Libref &libref is not registered in metadata; %put NOTE: Libref &libref is not registered in metadata;

View File

@@ -39,14 +39,6 @@
,Server=SASApp ,Server=SASApp
,stptype=2) ,stptype=2)
<h4> SAS Macros </h4>
@li mf_nobs.sas
@li mf_verifymacvars.sas
@li mm_getdirectories.sas
@li mm_updatestpsourcecode.sas
@li mp_dropmembers.sas
@li mm_getservercontexts.sas
@param stpname= Stored Process name. Avoid spaces - testing has shown that @param stpname= Stored Process name. Avoid spaces - testing has shown that
the check to avoid creating multiple STPs in the same folder with the same the check to avoid creating multiple STPs in the same folder with the same
name does not work when the name contains spaces. name does not work when the name contains spaces.
@@ -77,6 +69,17 @@
- fileuri - fileuri
- texturi - texturi
<h4> SAS Macros </h4>
@li mf_nobs.sas
@li mf_verifymacvars.sas
@li mm_getdirectories.sas
@li mm_updatestpsourcecode.sas
@li mp_dropmembers.sas
@li mm_getservercontexts.sas
<h4> Related Macros </h4>
@li mm_createwebservice.sas
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe

View File

@@ -12,6 +12,7 @@ Usage:
%* parmcards lets us write to a text file from open code ; %* parmcards lets us write to a text file from open code ;
filename ft15f001 temp; filename ft15f001 temp;
parmcards4; parmcards4;
%webout(FETCH)
%* do some sas, any inputs are now already WORK tables; %* do some sas, any inputs are now already WORK tables;
data example1 example2; data example1 example2;
set sashelp.class; set sashelp.class;
@@ -24,11 +25,8 @@ Usage:
;;;; ;;;;
%mm_createwebservice(path=/Public/app/common,name=appInit,code=ft15f001) %mm_createwebservice(path=/Public/app/common,name=appInit,code=ft15f001)
<h4> SAS Macros </h4> For more examples of using these web services with the SASjs Adapter, see:
@li mm_createstp.sas https://github.com/sasjs/adapter#readme
@li mf_getuser.sas
@li mm_createfolder.sas
@li mm_deletestp.sas
@param path= The full path (in SAS Metadata) where the service will be created @param path= The full path (in SAS Metadata) where the service will be created
@param name= Stored Process name. Avoid spaces - testing has shown that @param name= Stored Process name. Avoid spaces - testing has shown that
@@ -37,16 +35,22 @@ Usage:
@param desc= The description of the service (optional) @param desc= The description of the service (optional)
@param precode= Space separated list of filerefs, pointing to the code that @param precode= Space separated list of filerefs, pointing to the code that
needs to be attached to the beginning of the service (optional) needs to be attached to the beginning of the service (optional)
@param code=(ft15f001) Space seperated fileref(s) of the actual code to be @param code= (ft15f001) Space seperated fileref(s) of the actual code to be
added added
@param server=(SASApp) The server which will run the STP. Server name or uri @param server= (SASApp) The server which will run the STP. Server name or uri
is fine. is fine.
@param mDebug=(0) set to 1 to show debug messages in the log @param mDebug= (0) set to 1 to show debug messages in the log
@param replace=(YES) select NO to avoid replacing an existing service in that @param replace= (YES) select NO to avoid replacing an existing service in that
location location
@param adapter=(sasjs) the macro uses the sasjs adapter by default. To use @param adapter= (sasjs) the macro uses the sasjs adapter by default. To use
another adapter, add a (different) fileref here. another adapter, add a (different) fileref here.
<h4> SAS Macros </h4>
@li mm_createstp.sas
@li mf_getuser.sas
@li mm_createfolder.sas
@li mm_deletestp.sas
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe
@@ -89,129 +93,140 @@ data _null_;
put "/* Created on %sysfunc(datetime(),datetime19.) by %mf_getuser() */"; put "/* Created on %sysfunc(datetime(),datetime19.) by %mf_getuser() */";
/* WEBOUT BEGIN */ /* WEBOUT BEGIN */
put ' '; put ' ';
put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=DATASTEP,dbg=0 '; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y ';
put ' ,engine=DATASTEP ';
put ' ,missing=NULL ';
put ' ,showmeta=NO ';
put ')/*/STORE SOURCE*/; '; put ')/*/STORE SOURCE*/; ';
put '%put output location=&jref; '; put '%local tempds colinfo fmtds i numcols; ';
put '%let numcols=0; ';
put ' ';
put '%if &action=OPEN %then %do; '; put '%if &action=OPEN %then %do; ';
put ' options nobomfile; '; put ' options nobomfile; ';
put ' data _null_;file &jref encoding=''utf-8''; '; put ' data _null_;file &jref encoding=''utf-8'' ; ';
put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; '; put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
put ' run; '; put ' run; ';
put '%end; '; put '%end; ';
put '%else %if (&action=ARR or &action=OBJ) %then %do; '; put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
put ' options validvarname=upcase; '; put ' options validvarname=upcase; ';
put ' data _null_;file &jref mod encoding=''utf-8''; '; put ' data _null_; file &jref encoding=''utf-8'' mod; ';
put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; '; put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; ';
put ' '; put ' ';
put ' /* grab col defs */ ';
put ' proc contents noprint data=&ds ';
put ' out=_data_(keep=name type length format formatl formatd varnum label); ';
put ' run; ';
put ' %let colinfo=%scan(&syslast,2,.); ';
put ' proc sort data=&colinfo; ';
put ' by varnum; ';
put ' run; ';
put ' /* move meta to mac vars */ ';
put ' data _null_; ';
put ' if _n_=1 then call symputx(''numcols'',nobs,''l''); ';
put ' set &colinfo end=last nobs=nobs; ';
put ' name=upcase(name); ';
put ' /* fix formats */ ';
put ' if type=2 or type=6 then do; ';
put ' typelong=''char''; ';
put ' length fmt $49.; ';
put ' if format='''' then fmt=cats(''$'',length,''.''); ';
put ' else if formatl=0 then fmt=cats(format,''.''); ';
put ' else fmt=cats(format,formatl,''.''); ';
put ' newlen=max(formatl,length); ';
put ' end; ';
put ' else do; ';
put ' typelong=''num''; ';
put ' if format='''' then fmt=''best.''; ';
put ' else if formatl=0 then fmt=cats(format,''.''); ';
put ' else if formatd=0 then fmt=cats(format,formatl,''.''); ';
put ' else fmt=cats(format,formatl,''.'',formatd); ';
put ' /* needs to be wide, for datetimes etc */ ';
put ' newlen=max(length,formatl,24); ';
put ' end; ';
put ' /* 32 char unique name */ ';
put ' newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27); ';
put ' ';
put ' call symputx(cats(''name'',_n_),name,''l''); ';
put ' call symputx(cats(''newname'',_n_),newname,''l''); ';
put ' call symputx(cats(''len'',_n_),newlen,''l''); ';
put ' call symputx(cats(''length'',_n_),length,''l''); ';
put ' call symputx(cats(''fmt'',_n_),fmt,''l''); ';
put ' call symputx(cats(''type'',_n_),type,''l''); ';
put ' call symputx(cats(''typelong'',_n_),typelong,''l''); ';
put ' call symputx(cats(''label'',_n_),coalescec(label,name),''l''); ';
put ' run; ';
put ' ';
put ' %let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
put ' ';
put ' %if &engine=PROCJSON %then %do; '; put ' %if &engine=PROCJSON %then %do; ';
put ' data;run;%let tempds=&syslast; '; put ' %if &missing=STRING %then %do; ';
put ' proc sql;drop table &tempds; '; put ' %put &sysmacroname: Special Missings not supported in proc json.; ';
put ' %put &sysmacroname: Switching to DATASTEP engine; ';
put ' %goto datastep; ';
put ' %end; ';
put ' data &tempds /view=&tempds;set &ds; '; put ' data &tempds /view=&tempds;set &ds; ';
put ' %if &fmt=N %then format _numeric_ best32.;; '; put ' %if &fmt=N %then format _numeric_ best32.;; ';
put ' /* PRETTY is necessary to avoid line truncation in large files */ ';
put ' proc json out=&jref pretty '; put ' proc json out=&jref pretty ';
put ' %if &action=ARR %then nokeys ; '; put ' %if &action=ARR %then nokeys ; ';
put ' ;export &tempds / nosastags fmtnumeric; '; put ' ;export &tempds / nosastags fmtnumeric; ';
put ' run; '; put ' run; ';
put ' proc sql;drop view &tempds; ';
put ' %end; '; put ' %end; ';
put ' %else %if &engine=DATASTEP %then %do; '; put ' %else %if &engine=DATASTEP %then %do; ';
put ' %local cols i tempds; '; put ' %datastep: ';
put ' %let cols=0; '; put ' %if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1 ';
put ' %if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1 %then %do; '; put ' %then %do; ';
put ' %put &sysmacroname: &ds NOT FOUND!!!; '; put ' %put &sysmacroname: &ds NOT FOUND!!!; ';
put ' %return; '; put ' %return; ';
put ' %end; '; put ' %end; ';
put ' %if &fmt=Y %then %do; ';
put ' %put converting every variable to a formatted variable; ';
put ' /* see mp_ds2fmtds.sas for source */ ';
put ' proc contents noprint data=&ds ';
put ' out=_data_(keep=name type length format formatl formatd varnum); ';
put ' run; ';
put ' proc sort; ';
put ' by varnum; ';
put ' run; ';
put ' %local fmtds; ';
put ' %let fmtds=%scan(&syslast,2,.); ';
put ' /* prepare formats and varnames */ ';
put ' data _null_; ';
put ' if _n_=1 then call symputx(''nobs'',nobs,''l''); ';
put ' set &fmtds end=last nobs=nobs; ';
put ' name=upcase(name); ';
put ' /* fix formats */ ';
put ' if type=2 or type=6 then do; ';
put ' length fmt $49.; ';
put ' if format='''' then fmt=cats(''$'',length,''.''); ';
put ' else if formatl=0 then fmt=cats(format,''.''); ';
put ' else fmt=cats(format,formatl,''.''); ';
put ' newlen=max(formatl,length); ';
put ' end; ';
put ' else do; ';
put ' if format='''' then fmt=''best.''; ';
put ' else if formatl=0 then fmt=cats(format,''.''); ';
put ' else if formatd=0 then fmt=cats(format,formatl,''.''); ';
put ' else fmt=cats(format,formatl,''.'',formatd); ';
put ' /* needs to be wide, for datetimes etc */ ';
put ' newlen=max(length,formatl,24); ';
put ' end; ';
put ' /* 32 char unique name */ ';
put ' newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27); ';
put ' '; put ' ';
put ' call symputx(cats(''name'',_n_),name,''l''); '; put ' %if &fmt=Y %then %do; ';
put ' call symputx(cats(''newname'',_n_),newname,''l''); '; put ' data _data_; ';
put ' call symputx(cats(''len'',_n_),newlen,''l''); ';
put ' call symputx(cats(''fmt'',_n_),fmt,''l''); ';
put ' call symputx(cats(''type'',_n_),type,''l''); ';
put ' run; ';
put ' data &fmtds; ';
put ' /* rename on entry */ '; put ' /* rename on entry */ ';
put ' set &ds(rename=( '; put ' set &ds(rename=( ';
put ' %local i; '; put ' %do i=1 %to &numcols; ';
put ' %do i=1 %to &nobs; ';
put ' &&name&i=&&newname&i '; put ' &&name&i=&&newname&i ';
put ' %end; '; put ' %end; ';
put ' )); '; put ' )); ';
put ' %do i=1 %to &nobs; '; put ' %do i=1 %to &numcols; ';
put ' length &&name&i $&&len&i; '; put ' length &&name&i $&&len&i; ';
put ' &&name&i=left(put(&&newname&i,&&fmt&i)); '; put ' &&name&i=left(put(&&newname&i,&&fmt&i)); ';
put ' drop &&newname&i; '; put ' drop &&newname&i; ';
put ' %end; '; put ' %end; ';
put ' if _error_ then call symputx(''syscc'',1012); '; put ' if _error_ then call symputx(''syscc'',1012); ';
put ' run; '; put ' run; ';
put ' %let ds=&fmtds; '; put ' %let fmtds=&syslast; ';
put ' %end; /* &fmt=Y */ '; put ' %end; ';
put ' data _null_;file &jref mod encoding=''utf-8''; ';
put ' put "["; call symputx(''cols'',0,''l''); ';
put ' proc sort ';
put ' data=sashelp.vcolumn(where=(libname=''WORK'' & memname="%upcase(&ds)")) ';
put ' out=_data_; ';
put ' by varnum; ';
put ' ';
put ' data _null_; ';
put ' set _last_ end=last; ';
put ' call symputx(cats(''name'',_n_),name,''l''); ';
put ' call symputx(cats(''type'',_n_),type,''l''); ';
put ' call symputx(cats(''len'',_n_),length,''l''); ';
put ' if last then call symputx(''cols'',_n_,''l''); ';
put ' run; ';
put ' '; put ' ';
put ' proc format; /* credit yabwon for special null removal */ '; put ' proc format; /* credit yabwon for special null removal */ ';
put ' value bart ._ - .z = null '; put ' value bart (default=40) ';
put ' %if &missing=NULL %then %do; ';
put ' ._ - .z = null ';
put ' %end; ';
put ' %else %do; ';
put ' ._ = [quote()] ';
put ' . = null ';
put ' .a - .z = [quote()] ';
put ' %end; ';
put ' other = [best.]; '; put ' other = [best.]; ';
put ' '; put ' ';
put ' data;run; %let tempds=&syslast; /* temp table for spesh char management */ ';
put ' proc sql; drop table &tempds; ';
put ' data &tempds/view=&tempds; '; put ' data &tempds/view=&tempds; ';
put ' attrib _all_ label=''''; '; put ' attrib _all_ label=''''; ';
put ' %do i=1 %to &cols; '; put ' %do i=1 %to &numcols; ';
put ' %if &&type&i=char %then %do; '; put ' %if &&typelong&i=char or &fmt=Y %then %do; ';
put ' length &&name&i $32767; '; put ' length &&name&i $32767; ';
put ' format &&name&i $32767.; '; put ' format &&name&i $32767.; ';
put ' %end; '; put ' %end; ';
put ' %end; '; put ' %end; ';
put ' set &ds; '; put ' %if &fmt=Y %then %do; ';
put ' set &fmtds; ';
put ' %end; ';
put ' %else %do; ';
put ' set &ds; ';
put ' %end; ';
put ' format _numeric_ bart.; '; put ' format _numeric_ bart.; ';
put ' %do i=1 %to &cols; '; put ' %do i=1 %to &numcols; ';
put ' %if &&type&i=char %then %do; '; put ' %if &&typelong&i=char or &fmt=Y %then %do; ';
put ' &&name&i=''"''!!trim(prxchange(''s/"/\"/'',-1, '; put ' &&name&i=''"''!!trim(prxchange(''s/"/\"/'',-1, ';
put ' prxchange(''s/''!!''0A''x!!''/\n/'',-1, '; put ' prxchange(''s/''!!''0A''x!!''/\n/'',-1, ';
put ' prxchange(''s/''!!''0D''x!!''/\r/'',-1, '; put ' prxchange(''s/''!!''0D''x!!''/\r/'',-1, ';
@@ -221,49 +236,72 @@ data _null_;
put ' %end; '; put ' %end; ';
put ' %end; '; put ' %end; ';
put ' run; '; put ' run; ';
put ' ';
put ' /* write to temp loc to avoid _webout truncation '; put ' /* write to temp loc to avoid _webout truncation ';
put ' - https://support.sas.com/kb/49/325.html */ '; put ' - https://support.sas.com/kb/49/325.html */ ';
put ' filename _sjs temp lrecl=131068 encoding=''utf-8''; '; put ' filename _sjs temp lrecl=131068 encoding=''utf-8''; ';
put ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod; '; put ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod ; ';
put ' if _n_=1 then put "["; ';
put ' set &tempds; '; put ' set &tempds; ';
put ' if _n_>1 then put "," @; put '; put ' if _n_>1 then put "," @; put ';
put ' %if &action=ARR %then "[" ; %else "{" ; '; put ' %if &action=ARR %then "[" ; %else "{" ; ';
put ' %do i=1 %to &cols; '; put ' %do i=1 %to &numcols; ';
put ' %if &i>1 %then "," ; '; put ' %if &i>1 %then "," ; ';
put ' %if &action=OBJ %then """&&name&i"":" ; '; put ' %if &action=OBJ %then """&&name&i"":" ; ';
put ' &&name&i '; put ' &&name&i ';
put ' %end; '; put ' %end; ';
put ' %if &action=ARR %then "]" ; %else "}" ; ; '; put ' %if &action=ARR %then "]" ; %else "}" ; ; ';
put ' proc sql; ';
put ' drop view &tempds; ';
put ' /* now write the long strings to _webout 1 byte at a time */ '; put ' /* now write the long strings to _webout 1 byte at a time */ ';
put ' data _null_; '; put ' data _null_; ';
put ' length filein 8 fileid 8; '; put ' length filein 8 fileid 8; ';
put ' filein = fopen("_sjs",''I'',1,''B''); '; put ' filein=fopen("_sjs",''I'',1,''B''); ';
put ' fileid = fopen("&jref",''A'',1,''B''); '; put ' fileid=fopen("&jref",''A'',1,''B''); ';
put ' rec = ''20''x; '; put ' rec=''20''x; ';
put ' do while(fread(filein)=0); '; put ' do while(fread(filein)=0); ';
put ' rc = fget(filein,rec,1); '; put ' rc=fget(filein,rec,1); ';
put ' rc = fput(fileid, rec); '; put ' rc=fput(fileid, rec); ';
put ' rc =fwrite(fileid); '; put ' rc=fwrite(fileid); ';
put ' end; '; put ' end; ';
put ' rc = fclose(filein); '; put ' /* close out the table */ ';
put ' rc = fclose(fileid); '; put ' rc=fput(fileid, "]"); ';
put ' rc=fwrite(fileid); ';
put ' rc=fclose(filein); ';
put ' rc=fclose(fileid); ';
put ' run; '; put ' run; ';
put ' filename _sjs clear; '; put ' filename _sjs clear; ';
put ' data _null_; file &jref mod encoding=''utf-8''; '; put ' %end; ';
put ' put "]"; '; put ' ';
put ' proc sql; ';
put ' drop view &tempds; ';
put ' drop table &colinfo; ';
put ' ';
put ' %if &showmeta=YES %then %do; ';
put ' data _null_; file &jref encoding=''utf-8'' mod; ';
put ' put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{"; ';
put ' do i=1 to &numcols; ';
put ' name=quote(trim(symget(cats(''name'',i)))); ';
put ' format=quote(trim(symget(cats(''fmt'',i)))); ';
put ' label=quote(trim(symget(cats(''label'',i)))); ';
put ' length=quote(trim(symget(cats(''length'',i)))); ';
put ' type=quote(trim(symget(cats(''typelong'',i)))); ';
put ' if i>1 then put "," @@; ';
put ' put name '':{"format":'' format '',"label":'' label ';
put ' '',"length":'' length '',"type":'' type ''}''; ';
put ' end; ';
put ' put ''}}''; ';
put ' run; '; put ' run; ';
put ' %end; '; put ' %end; ';
put '%end; '; put '%end; ';
put ' '; put ' ';
put '%else %if &action=CLOSE %then %do; '; put '%else %if &action=CLOSE %then %do; ';
put ' data _null_;file &jref encoding=''utf-8'' mod; '; put ' data _null_; file &jref encoding=''utf-8'' mod ; ';
put ' put "}"; '; put ' put "}"; ';
put ' run; '; put ' run; ';
put '%end; '; put '%end; ';
put '%mend mp_jsonout; '; put '%mend mp_jsonout; ';
put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y); '; put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL ';
put ' ,showmeta=NO ';
put '); ';
put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug '; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug ';
put ' sasjs_tables; '; put ' sasjs_tables; ';
put '%local i tempds jsonengine; '; put '%local i tempds jsonengine; ';
@@ -321,14 +359,15 @@ data _null_;
put ' %if %str(&_debug) ge 131 %then %do; '; put ' %if %str(&_debug) ge 131 %then %do; ';
put ' put ''>>weboutBEGIN<<''; '; put ' put ''>>weboutBEGIN<<''; ';
put ' %end; '; put ' %end; ';
put ' put ''{"START_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''"''; '; put ' put ''{"SYSDATE" : "'' "&SYSDATE" ''"''; ';
put ' put '',"SYSTIME" : "'' "&SYSTIME" ''"''; ';
put ' run; '; put ' run; ';
put ' '; put ' ';
put '%end; '; put '%end; ';
put ' '; put ' ';
put '%else %if &action=ARR or &action=OBJ %then %do; '; put '%else %if &action=ARR or &action=OBJ %then %do; ';
put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref '; put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref ';
put ' ,engine=&jsonengine,dbg=%str(&_debug) '; put ' ,engine=&jsonengine,missing=&missing,showmeta=&showmeta ';
put ' ) '; put ' ) ';
put '%end; '; put '%end; ';
put '%else %if &action=CLOSE %then %do; '; put '%else %if &action=CLOSE %then %do; ';
@@ -343,15 +382,12 @@ data _null_;
put ' set &tempds; '; put ' set &tempds; ';
put ' if not (upcase(name) =:"DATA"); /* ignore temp datasets */ '; put ' if not (upcase(name) =:"DATA"); /* ignore temp datasets */ ';
put ' i+1; '; put ' i+1; ';
put ' call symputx(''wt''!!left(i),name,''l''); '; put ' call symputx(cats(''wt'',i),name,''l''); ';
put ' call symputx(''wtcnt'',i,''l''); '; put ' call symputx(''wtcnt'',i,''l''); ';
put ' data _null_; file &fref mod encoding=''utf-8''; '; put ' data _null_; file &fref mod encoding=''utf-8''; ';
put ' put ",""WORK"":{"; '; put ' put ",""WORK"":{"; ';
put ' %do i=1 %to &wtcnt; '; put ' %do i=1 %to &wtcnt; ';
put ' %let wt=&&wt&i; '; put ' %let wt=&&wt&i; ';
put ' proc contents noprint data=&wt ';
put ' out=_data_ (keep=name type length format:); ';
put ' run;%let tempds=%scan(&syslast,2,.); ';
put ' data _null_; file &fref mod encoding=''utf-8''; '; put ' data _null_; file &fref mod encoding=''utf-8''; ';
put ' dsid=open("WORK.&wt",''is''); '; put ' dsid=open("WORK.&wt",''is''); ';
put ' nlobs=attrn(dsid,''NLOBS''); '; put ' nlobs=attrn(dsid,''NLOBS''); ';
@@ -361,8 +397,7 @@ data _null_;
put ' put " ""&wt"" : {"; '; put ' put " ""&wt"" : {"; ';
put ' put ''"nlobs":'' nlobs; '; put ' put ''"nlobs":'' nlobs; ';
put ' put '',"nvars":'' nvars; '; put ' put '',"nvars":'' nvars; ';
put ' %mp_jsonout(OBJ,&tempds,jref=&fref,dslabel=colattrs,engine=&jsonengine) '; put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=YES) ';
put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,engine=&jsonengine) ';
put ' data _null_; file &fref mod encoding=''utf-8''; '; put ' data _null_; file &fref mod encoding=''utf-8''; ';
put ' put "}"; '; put ' put "}"; ';
put ' %end; '; put ' %end; ';
@@ -390,7 +425,11 @@ data _null_;
put ' sysvlong=quote(trim(symget(''sysvlong''))); '; put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
put ' put '',"SYSVLONG" : '' sysvlong; '; put ' put '',"SYSVLONG" : '' sysvlong; ';
put ' put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" "; '; put ' put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" "; ';
put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''" ''; '; put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" ''; ';
put ' length memsize $32; ';
put ' memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)"; ';
put ' memsize=quote(cats(memsize)); ';
put ' put '',"MEMSIZE" : '' memsize; ';
put ' put "}" @; '; put ' put "}" @; ';
put ' %if %str(&_debug) ge 131 %then %do; '; put ' %if %str(&_debug) ge 131 %then %do; ';
put ' put ''>>weboutEND<<''; '; put ' put ''>>weboutEND<<''; ';
@@ -418,8 +457,10 @@ data _null_;
put ' '; put ' ';
put '%mend mf_getuser; '; put '%mend mf_getuser; ';
/* WEBOUT END */ /* WEBOUT END */
put '%macro webout(action,ds,dslabel=,fmt=);'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO);';
put ' %mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt)'; put ' %mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt,missing=&missing';
put ' ,showmeta=&showmeta';
put ' )';
put '%mend;'; put '%mend;';
run; run;

View File

@@ -35,7 +35,7 @@ data _null_;
set repos; set repos;
where repositorytype in('CUSTOM','FOUNDATION'); where repositorytype in('CUSTOM','FOUNDATION');
keep id name ; keep id name ;
call symputx('repo'!!left(_n_),name,'l'); call symputx(cats('repo',_n_),name,'l');
call symputx('repocnt',_n_,'l'); call symputx('repocnt',_n_,'l');
run; run;

View File

@@ -13,9 +13,6 @@
@param [in] stpcode= the source file (or fileref) containing the SAS code to load @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. 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 [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 @param mDebug= set to 1 to show debug messages in the log
@version 9.3 @version 9.3
@@ -30,16 +27,8 @@
,stpcode= ,stpcode=
,minify=NO ,minify=NO
,mdebug=0 ,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 */ /* first, check if STP exists */
%local tsuri; %local tsuri;
%let tsuri=stopifempty ; %let tsuri=stopifempty ;

View File

@@ -23,17 +23,25 @@
%mm_webout(CLOSE) %mm_webout(CLOSE)
@param action Either FETCH, OPEN, ARR, OBJ or CLOSE @param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE
@param ds The dataset to send back to the frontend @param [in] ds The dataset to send back to the frontend
@param dslabel= value to use instead of the real name for sending to JSON @param [out] dslabel= Value to use instead of table name for sending to JSON
@param fmt=(Y) Set to N to send back unformatted values @param [in] fmt=(Y) Set to N to send back unformatted values
@param fref=(_webout) The fileref to which to write the JSON @param [out] fref= (_webout) The fileref to which to write the JSON
@param [in] missing= (NULL) Special numeric missing values can be sent as NULL
(eg `null`) or as STRING values (eg `".a"` or `".b"`)
@param [in] showmeta= (NO) Set to YES to output metadata alongside each table,
such as the column formats and types. The metadata is contained inside an
object with the same name as the table but prefixed with a dollar sign - ie,
`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`
@version 9.3 @version 9.3
@author Allan Bowe @author Allan Bowe
**/ **/
%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y); %macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL
,showmeta=NO
);
%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug %global _webin_file_count _webin_fileref1 _webin_name1 _program _debug
sasjs_tables; sasjs_tables;
%local i tempds jsonengine; %local i tempds jsonengine;
@@ -91,14 +99,15 @@
%if %str(&_debug) ge 131 %then %do; %if %str(&_debug) ge 131 %then %do;
put '>>weboutBEGIN<<'; put '>>weboutBEGIN<<';
%end; %end;
put '{"START_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '"'; put '{"SYSDATE" : "' "&SYSDATE" '"';
put ',"SYSTIME" : "' "&SYSTIME" '"';
run; run;
%end; %end;
%else %if &action=ARR or &action=OBJ %then %do; %else %if &action=ARR or &action=OBJ %then %do;
%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref
,engine=&jsonengine,dbg=%str(&_debug) ,engine=&jsonengine,missing=&missing,showmeta=&showmeta
) )
%end; %end;
%else %if &action=CLOSE %then %do; %else %if &action=CLOSE %then %do;
@@ -113,15 +122,12 @@
set &tempds; set &tempds;
if not (upcase(name) =:"DATA"); /* ignore temp datasets */ if not (upcase(name) =:"DATA"); /* ignore temp datasets */
i+1; i+1;
call symputx('wt'!!left(i),name,'l'); call symputx(cats('wt',i),name,'l');
call symputx('wtcnt',i,'l'); call symputx('wtcnt',i,'l');
data _null_; file &fref mod encoding='utf-8'; data _null_; file &fref mod encoding='utf-8';
put ",""WORK"":{"; put ",""WORK"":{";
%do i=1 %to &wtcnt; %do i=1 %to &wtcnt;
%let wt=&&wt&i; %let wt=&&wt&i;
proc contents noprint data=&wt
out=_data_ (keep=name type length format:);
run;%let tempds=%scan(&syslast,2,.);
data _null_; file &fref mod encoding='utf-8'; data _null_; file &fref mod encoding='utf-8';
dsid=open("WORK.&wt",'is'); dsid=open("WORK.&wt",'is');
nlobs=attrn(dsid,'NLOBS'); nlobs=attrn(dsid,'NLOBS');
@@ -131,8 +137,7 @@
put " ""&wt"" : {"; put " ""&wt"" : {";
put '"nlobs":' nlobs; put '"nlobs":' nlobs;
put ',"nvars":' nvars; put ',"nvars":' nvars;
%mp_jsonout(OBJ,&tempds,jref=&fref,dslabel=colattrs,engine=&jsonengine) %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=YES)
%mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,engine=&jsonengine)
data _null_; file &fref mod encoding='utf-8'; data _null_; file &fref mod encoding='utf-8';
put "}"; put "}";
%end; %end;
@@ -160,7 +165,11 @@
sysvlong=quote(trim(symget('sysvlong'))); sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong; put ',"SYSVLONG" : ' sysvlong;
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" "; put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" ";
put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" '; put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
length memsize $32;
memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";
memsize=quote(cats(memsize));
put ',"MEMSIZE" : ' memsize;
put "}" @; put "}" @;
%if %str(&_debug) ge 131 %then %do; %if %str(&_debug) ge 131 %then %do;
put '>>weboutEND<<'; put '>>weboutEND<<';

365
package-lock.json generated
View File

@@ -10,73 +10,25 @@
"ts-loader": "^9.2.6" "ts-loader": "^9.2.6"
}, },
"devDependencies": { "devDependencies": {
"@sasjs/cli": "^2.39.0" "@sasjs/cli": "3.6.0"
} }
}, },
"node_modules/@sasjs/adapter": { "node_modules/@sasjs/adapter": {
"version": "2.12.0", "version": "3.4.1",
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-2.12.0.tgz", "resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-3.4.1.tgz",
"integrity": "sha512-zzIuohhR8KUDl3DfIFOW38gv3LADPnOBCLOvLoKu4hH5R/UJDkjZ/Gdgc8B35vI7aOprYOLK/T5D/Z44OaTkqw==", "integrity": "sha512-FbsvYDaoJAuH8FMidXhX3Kh4Eb8qIcxy5iiCxHgcSRWM89W29W21WfCqCw0F28yFr+V5vZkvphwXMKFdOGGxlw==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@sasjs/utils": "^2.32.0",
"axios": "^0.21.4",
"axios-cookiejar-support": "^1.0.1",
"form-data": "^4.0.0",
"https": "^1.0.0",
"tough-cookie": "^4.0.0"
}
},
"node_modules/@sasjs/cli": {
"version": "2.39.0",
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-2.39.0.tgz",
"integrity": "sha512-n2LcU4n0QCEbUpXqZnBz/Ey5Td0nMJmgJpZRymMGfYEM0Y0x/CeXemd+kXHPjUvgQ+FX+SQzcvUQTEY/YlT4hA==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"@sasjs/adapter": "2.12.0",
"@sasjs/core": "2.45.2",
"@sasjs/lint": "1.11.2",
"@sasjs/utils": "2.32.0", "@sasjs/utils": "2.32.0",
"chalk": "4.1.2", "axios": "0.25.0",
"csv-stringify": "5.6.5", "axios-cookiejar-support": "1.0.1",
"dotenv": "10.0.0", "form-data": "4.0.0",
"esm": "3.2.25", "https": "1.0.0",
"find": "0.3.0", "tough-cookie": "4.0.0"
"get-installed-path": "4.0.8",
"js-base64": "3.7.2",
"jsdom": "17.0.0",
"jwt-decode": "3.1.2",
"lodash.groupby": "4.6.0",
"lodash.uniqby": "4.7.0",
"node-graphviz": "0.1.0",
"ora": "5.4.1",
"rimraf": "3.0.2",
"shelljs": "0.8.4",
"xml": "1.0.1",
"yargs": "17.2.1"
},
"bin": {
"sasjs": "build/index.js"
} }
}, },
"node_modules/@sasjs/core": { "node_modules/@sasjs/adapter/node_modules/@sasjs/utils": {
"version": "2.45.2",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-2.45.2.tgz",
"integrity": "sha512-tg+oZCD8GFMXsg+vDL66LMnyU+t151Hrqd7yl+pMXH2qwkA14N/j6QdkTBZOchskqOA/3PnpOlAZN/xxMW2gdg==",
"dev": true
},
"node_modules/@sasjs/lint": {
"version": "1.11.2",
"resolved": "https://registry.npmjs.org/@sasjs/lint/-/lint-1.11.2.tgz",
"integrity": "sha512-zEonhvha9kwrD+hxhG0hEhtfqpXwffH4vRDIr6eDiXkC7S8M3yImpjyFBvX/THJO5+8iuY8TYkOXKl7+nK/wAg==",
"dev": true,
"dependencies": {
"@sasjs/utils": "^2.19.0"
}
},
"node_modules/@sasjs/utils": {
"version": "2.32.0", "version": "2.32.0",
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.32.0.tgz", "resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.32.0.tgz",
"integrity": "sha512-xnvdEuI4PhTtulcdDEIMK7IxVj9bOMU1JTnxRuSEKWcsclY9P9Fw3cnMOOEgXCDffrOPn3f54DP7Wb1GXd+f8g==", "integrity": "sha512-xnvdEuI4PhTtulcdDEIMK7IxVj9bOMU1JTnxRuSEKWcsclY9P9Fw3cnMOOEgXCDffrOPn3f54DP7Wb1GXd+f8g==",
@@ -96,6 +48,101 @@
"valid-url": "^1.0.9" "valid-url": "^1.0.9"
} }
}, },
"node_modules/@sasjs/cli": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-3.6.0.tgz",
"integrity": "sha512-px5aFXNoTlML7g4TvP92q/uqWnHGbxnwEzy+4D+sJbXgpzLlCyyuUU7rbd/9sXT+/Pi7vjfHfBCv0JhoGYr6gw==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"@sasjs/adapter": "3.4.1",
"@sasjs/core": "3.10.0",
"@sasjs/lint": "1.11.2",
"@sasjs/utils": "2.35.0",
"chalk": "4.1.2",
"csv-stringify": "5.6.5",
"dotenv": "10.0.0",
"esm": "3.2.25",
"find": "0.3.0",
"get-installed-path": "4.0.8",
"js-base64": "3.7.2",
"jsdom": "17.0.0",
"jwt-decode": "3.1.2",
"lodash.groupby": "4.6.0",
"lodash.uniqby": "4.7.0",
"node-graphviz": "0.1.0",
"ora": "5.4.1",
"rimraf": "3.0.2",
"shelljs": "0.8.5",
"xml": "1.0.1",
"yargs": "17.2.1"
},
"bin": {
"sasjs": "build/index.js"
}
},
"node_modules/@sasjs/cli/node_modules/@sasjs/utils": {
"version": "2.35.0",
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.35.0.tgz",
"integrity": "sha512-q9ZKV+TXqwiaj+0z5U7/00eBpp2QpjKfC9BKx7A6rQjBl10WtoWd5C9Em+RQULWVEdRbVS2XcnNsWelbKq/Zsw==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"@types/fs-extra": "^9.0.13",
"@types/prompts": "^2.0.13",
"chalk": "^4.1.1",
"cli-table": "^0.3.6",
"consola": "^2.15.0",
"csv-stringify": "^5.6.5",
"find": "0.3.0",
"fs-extra": "^10.0.0",
"jwt-decode": "^3.1.2",
"prompts": "^2.4.1",
"rimraf": "^3.0.2",
"valid-url": "^1.0.9"
}
},
"node_modules/@sasjs/core": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-3.10.0.tgz",
"integrity": "sha512-lgLxDYpIvwSrXFaUaTFCR0KXHQEc5QIOL4DU87TvBHEUUAWNQHzuVQWkavLtW5hbvLGnPXnyvspzoSzmBojXzg==",
"dev": true,
"dependencies": {
"ts-loader": "^9.2.6"
}
},
"node_modules/@sasjs/lint": {
"version": "1.11.2",
"resolved": "https://registry.npmjs.org/@sasjs/lint/-/lint-1.11.2.tgz",
"integrity": "sha512-zEonhvha9kwrD+hxhG0hEhtfqpXwffH4vRDIr6eDiXkC7S8M3yImpjyFBvX/THJO5+8iuY8TYkOXKl7+nK/wAg==",
"dev": true,
"dependencies": {
"@sasjs/utils": "^2.19.0"
}
},
"node_modules/@sasjs/utils": {
"version": "2.34.1",
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.34.1.tgz",
"integrity": "sha512-hd1qieH3d7+xH96n5DpRGTEazeAhYyBBKCdnKhOXMgF2TZVoHFdRs5REfT88CKza6DHBGRVGnIVm5ORGP4cVLg==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"@types/fs-extra": "^9.0.11",
"@types/prompts": "^2.0.13",
"chalk": "^4.1.1",
"cli-table": "^0.3.6",
"consola": "^2.15.0",
"csv-stringify": "^5.6.5",
"find": "0.3.0",
"fs-extra": "^10.0.0",
"jwt-decode": "^3.1.2",
"lodash.groupby": "4.6.0",
"lodash.uniqby": "4.7.0",
"prompts": "^2.4.1",
"rimraf": "^3.0.2",
"valid-url": "^1.0.9"
}
},
"node_modules/@tootallnate/once": { "node_modules/@tootallnate/once": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
@@ -132,9 +179,9 @@
"peer": true "peer": true
}, },
"node_modules/@types/fs-extra": { "node_modules/@types/fs-extra": {
"version": "9.0.12", "version": "9.0.13",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.12.tgz", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz",
"integrity": "sha512-I+bsBr67CurCGnSenZZ7v94gd3tc3+Aj2taxMT4yu4ABLuOgOjeFxX3dokG24ztSRg5tnT00sL8BszO7gSMoIw==", "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "*" "@types/node": "*"
@@ -449,12 +496,12 @@
"dev": true "dev": true
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "0.21.4", "version": "0.25.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"follow-redirects": "^1.14.0" "follow-redirects": "^1.14.7"
} }
}, },
"node_modules/axios-cookiejar-support": { "node_modules/axios-cookiejar-support": {
@@ -1051,9 +1098,9 @@
} }
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.14.5", "version": "1.14.7",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
"integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==", "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -1335,9 +1382,9 @@
} }
}, },
"node_modules/is-core-module": { "node_modules/is-core-module": {
"version": "2.4.0", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz",
"integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"has": "^1.0.3" "has": "^1.0.3"
@@ -1887,13 +1934,17 @@
} }
}, },
"node_modules/resolve": { "node_modules/resolve": {
"version": "1.20.0", "version": "1.21.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz",
"integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", "integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"is-core-module": "^2.2.0", "is-core-module": "^2.8.0",
"path-parse": "^1.0.6" "path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@@ -2019,9 +2070,9 @@
} }
}, },
"node_modules/shelljs": { "node_modules/shelljs": {
"version": "0.8.4", "version": "0.8.5",
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz",
"integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"glob": "^7.0.0", "glob": "^7.0.0",
@@ -2111,6 +2162,18 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/symbol-tree": { "node_modules/symbol-tree": {
"version": "3.2.4", "version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
@@ -2581,29 +2644,50 @@
}, },
"dependencies": { "dependencies": {
"@sasjs/adapter": { "@sasjs/adapter": {
"version": "2.12.0", "version": "3.4.1",
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-2.12.0.tgz", "resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-3.4.1.tgz",
"integrity": "sha512-zzIuohhR8KUDl3DfIFOW38gv3LADPnOBCLOvLoKu4hH5R/UJDkjZ/Gdgc8B35vI7aOprYOLK/T5D/Z44OaTkqw==", "integrity": "sha512-FbsvYDaoJAuH8FMidXhX3Kh4Eb8qIcxy5iiCxHgcSRWM89W29W21WfCqCw0F28yFr+V5vZkvphwXMKFdOGGxlw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@sasjs/utils": "^2.32.0", "@sasjs/utils": "2.32.0",
"axios": "^0.21.4", "axios": "0.25.0",
"axios-cookiejar-support": "^1.0.1", "axios-cookiejar-support": "1.0.1",
"form-data": "^4.0.0", "form-data": "4.0.0",
"https": "^1.0.0", "https": "1.0.0",
"tough-cookie": "^4.0.0" "tough-cookie": "4.0.0"
},
"dependencies": {
"@sasjs/utils": {
"version": "2.32.0",
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.32.0.tgz",
"integrity": "sha512-xnvdEuI4PhTtulcdDEIMK7IxVj9bOMU1JTnxRuSEKWcsclY9P9Fw3cnMOOEgXCDffrOPn3f54DP7Wb1GXd+f8g==",
"dev": true,
"requires": {
"@types/fs-extra": "^9.0.11",
"@types/prompts": "^2.0.13",
"chalk": "^4.1.1",
"cli-table": "^0.3.6",
"consola": "^2.15.0",
"csv-stringify": "^5.6.5",
"fs-extra": "^10.0.0",
"jwt-decode": "^3.1.2",
"prompts": "^2.4.1",
"rimraf": "^3.0.2",
"valid-url": "^1.0.9"
}
}
} }
}, },
"@sasjs/cli": { "@sasjs/cli": {
"version": "2.39.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-2.39.0.tgz", "resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-3.6.0.tgz",
"integrity": "sha512-n2LcU4n0QCEbUpXqZnBz/Ey5Td0nMJmgJpZRymMGfYEM0Y0x/CeXemd+kXHPjUvgQ+FX+SQzcvUQTEY/YlT4hA==", "integrity": "sha512-px5aFXNoTlML7g4TvP92q/uqWnHGbxnwEzy+4D+sJbXgpzLlCyyuUU7rbd/9sXT+/Pi7vjfHfBCv0JhoGYr6gw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@sasjs/adapter": "2.12.0", "@sasjs/adapter": "3.4.1",
"@sasjs/core": "2.45.2", "@sasjs/core": "3.10.0",
"@sasjs/lint": "1.11.2", "@sasjs/lint": "1.11.2",
"@sasjs/utils": "2.32.0", "@sasjs/utils": "2.35.0",
"chalk": "4.1.2", "chalk": "4.1.2",
"csv-stringify": "5.6.5", "csv-stringify": "5.6.5",
"dotenv": "10.0.0", "dotenv": "10.0.0",
@@ -2618,16 +2702,41 @@
"node-graphviz": "0.1.0", "node-graphviz": "0.1.0",
"ora": "5.4.1", "ora": "5.4.1",
"rimraf": "3.0.2", "rimraf": "3.0.2",
"shelljs": "0.8.4", "shelljs": "0.8.5",
"xml": "1.0.1", "xml": "1.0.1",
"yargs": "17.2.1" "yargs": "17.2.1"
},
"dependencies": {
"@sasjs/utils": {
"version": "2.35.0",
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.35.0.tgz",
"integrity": "sha512-q9ZKV+TXqwiaj+0z5U7/00eBpp2QpjKfC9BKx7A6rQjBl10WtoWd5C9Em+RQULWVEdRbVS2XcnNsWelbKq/Zsw==",
"dev": true,
"requires": {
"@types/fs-extra": "^9.0.13",
"@types/prompts": "^2.0.13",
"chalk": "^4.1.1",
"cli-table": "^0.3.6",
"consola": "^2.15.0",
"csv-stringify": "^5.6.5",
"find": "0.3.0",
"fs-extra": "^10.0.0",
"jwt-decode": "^3.1.2",
"prompts": "^2.4.1",
"rimraf": "^3.0.2",
"valid-url": "^1.0.9"
}
}
} }
}, },
"@sasjs/core": { "@sasjs/core": {
"version": "2.45.2", "version": "3.10.0",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-2.45.2.tgz", "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-3.10.0.tgz",
"integrity": "sha512-tg+oZCD8GFMXsg+vDL66LMnyU+t151Hrqd7yl+pMXH2qwkA14N/j6QdkTBZOchskqOA/3PnpOlAZN/xxMW2gdg==", "integrity": "sha512-lgLxDYpIvwSrXFaUaTFCR0KXHQEc5QIOL4DU87TvBHEUUAWNQHzuVQWkavLtW5hbvLGnPXnyvspzoSzmBojXzg==",
"dev": true "dev": true,
"requires": {
"ts-loader": "^9.2.6"
}
}, },
"@sasjs/lint": { "@sasjs/lint": {
"version": "1.11.2", "version": "1.11.2",
@@ -2639,9 +2748,9 @@
} }
}, },
"@sasjs/utils": { "@sasjs/utils": {
"version": "2.32.0", "version": "2.34.1",
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.32.0.tgz", "resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.34.1.tgz",
"integrity": "sha512-xnvdEuI4PhTtulcdDEIMK7IxVj9bOMU1JTnxRuSEKWcsclY9P9Fw3cnMOOEgXCDffrOPn3f54DP7Wb1GXd+f8g==", "integrity": "sha512-hd1qieH3d7+xH96n5DpRGTEazeAhYyBBKCdnKhOXMgF2TZVoHFdRs5REfT88CKza6DHBGRVGnIVm5ORGP4cVLg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/fs-extra": "^9.0.11", "@types/fs-extra": "^9.0.11",
@@ -2650,8 +2759,11 @@
"cli-table": "^0.3.6", "cli-table": "^0.3.6",
"consola": "^2.15.0", "consola": "^2.15.0",
"csv-stringify": "^5.6.5", "csv-stringify": "^5.6.5",
"find": "0.3.0",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"lodash.groupby": "4.6.0",
"lodash.uniqby": "4.7.0",
"prompts": "^2.4.1", "prompts": "^2.4.1",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"valid-url": "^1.0.9" "valid-url": "^1.0.9"
@@ -2690,9 +2802,9 @@
"peer": true "peer": true
}, },
"@types/fs-extra": { "@types/fs-extra": {
"version": "9.0.12", "version": "9.0.13",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.12.tgz", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz",
"integrity": "sha512-I+bsBr67CurCGnSenZZ7v94gd3tc3+Aj2taxMT4yu4ABLuOgOjeFxX3dokG24ztSRg5tnT00sL8BszO7gSMoIw==", "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/node": "*" "@types/node": "*"
@@ -2974,12 +3086,12 @@
"dev": true "dev": true
}, },
"axios": { "axios": {
"version": "0.21.4", "version": "0.25.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
"dev": true, "dev": true,
"requires": { "requires": {
"follow-redirects": "^1.14.0" "follow-redirects": "^1.14.7"
} }
}, },
"axios-cookiejar-support": { "axios-cookiejar-support": {
@@ -3421,9 +3533,9 @@
} }
}, },
"follow-redirects": { "follow-redirects": {
"version": "1.14.5", "version": "1.14.7",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
"integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==", "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==",
"dev": true "dev": true
}, },
"form-data": { "form-data": {
@@ -3627,9 +3739,9 @@
"dev": true "dev": true
}, },
"is-core-module": { "is-core-module": {
"version": "2.4.0", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz",
"integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==",
"dev": true, "dev": true,
"requires": { "requires": {
"has": "^1.0.3" "has": "^1.0.3"
@@ -4051,13 +4163,14 @@
"dev": true "dev": true
}, },
"resolve": { "resolve": {
"version": "1.20.0", "version": "1.21.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz",
"integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", "integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==",
"dev": true, "dev": true,
"requires": { "requires": {
"is-core-module": "^2.2.0", "is-core-module": "^2.8.0",
"path-parse": "^1.0.6" "path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
} }
}, },
"resolve-dir": { "resolve-dir": {
@@ -4138,9 +4251,9 @@
} }
}, },
"shelljs": { "shelljs": {
"version": "0.8.4", "version": "0.8.5",
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz",
"integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==",
"dev": true, "dev": true,
"requires": { "requires": {
"glob": "^7.0.0", "glob": "^7.0.0",
@@ -4212,6 +4325,12 @@
"has-flag": "^4.0.0" "has-flag": "^4.0.0"
} }
}, },
"supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true
},
"symbol-tree": { "symbol-tree": {
"version": "3.2.4", "version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@sasjs/core", "name": "@sasjs/core",
"description": "Production Ready Macros for SAS Application Developers", "description": "Macros for SAS Application Developers",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [
"SAS", "SAS",
@@ -33,9 +33,6 @@
"prepare": "git rev-parse --git-dir && git config core.hooksPath ./.git-hooks || true" "prepare": "git rev-parse --git-dir && git config core.hooksPath ./.git-hooks || true"
}, },
"devDependencies": { "devDependencies": {
"@sasjs/cli": "^2.39.0" "@sasjs/cli": "3.6.0"
},
"dependencies": {
"ts-loader": "^9.2.6"
} }
} }

View File

@@ -1,67 +1,73 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- HTML header for doxygen 1.8.17--> <!-- HTML header for doxygen 1.8.17-->
<html xmlns="https://www.w3.org/1999/xhtml" lang="en"> <html xmlns="https://www.w3.org/1999/xhtml" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=9" />
<meta property="og:type" content="website">
<meta name="author" content="Allan Bowe">
<meta name="generator" content="Doxygen $doxygenversion" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!--BEGIN PROJECT_NAME-->
<meta name="description" content="$projectbrief" />
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<title>$title</title>
<!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
$treeview $search $mathjax
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
<link rel="shortcut icon" href="$relpath^favicon.ico" type="image/x-icon" />
$extrastylesheet
</head>
<body>
<div id="top">
<!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA--> <head>
<div id="titlearea"> <meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8" />
<table cellspacing="0" cellpadding="0"> <meta http-equiv="X-UA-Compatible" content="IE=9" />
<tbody> <meta property="og:type" content="website">
<tr style="height: 56px"> <meta property="og:title" content="MacroCore" />
<!--BEGIN PROJECT_LOGO--> <meta property="og:url" content="https://core.sasjs.io" />
<td id="projectlogo"> <meta property="og:image" content="https://core.sasjs.io/Macro_core_website_1.png" />
<a href="$relpath^" <meta name="author" content="Allan Bowe">
><img alt="Logo" src="$relpath^$projectlogo" <meta name="generator" content="Doxygen $doxygenversion" />
/></a> <meta name="viewport" content="width=device-width, initial-scale=1" />
</td> <!--BEGIN PROJECT_NAME-->
<!--END PROJECT_LOGO--> <meta name="description" content="$projectbrief" />
<td id="projectalign" style="padding-left: 0.5em"> <meta name="og:description" content="$projectbrief" />
<div id="projectbrief"> <!--END PROJECT_NAME-->
Production Ready Macros for SAS Application Developers<br /> <!--BEGIN !PROJECT_NAME-->
<a href="https://github.com/sasjs/core"> <title>$title</title>
https://github.com/sasjs/core <!--END !PROJECT_NAME-->
</a> <link href="$relpath^tabs.css" rel="stylesheet" type="text/css" />
</div> <script type="text/javascript" src="$relpath^jquery.js"></script>
</td> <script type="text/javascript" src="$relpath^dynsections.js"></script>
</tr> $treeview $search $mathjax
</table> <link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
</td> <link rel="shortcut icon" href="$relpath^favicon.ico" type="image/x-icon" />
$extrastylesheet
</head>
<body>
<div id="top">
<!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr style="height: 56px">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo">
<a href="$relpath^"><img alt="Logo" src="$relpath^$projectlogo" /></a>
</td>
<!--END PROJECT_LOGO-->
<td id="projectalign" style="padding-left: 0.5em">
<div id="projectbrief">
Macros for SAS Application Developers<br />
<a href="https://github.com/sasjs/core">
https://github.com/sasjs/core
</a>
</div>
</td>
</tr>
</table>
</td>
<!--BEGIN DISABLE_INDEX--> <!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE--> <!--BEGIN SEARCHENGINE-->
<td>$searchbox</td> <td>$searchbox</td>
<!--END SEARCHENGINE--> <!--END SEARCHENGINE-->
<!--END DISABLE_INDEX--> <!--END DISABLE_INDEX-->
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->
</div> </div>
</body> <!--END TITLEAREA-->
</html> <!-- end header part -->
</div>
</body>
</html>

View File

@@ -1,5 +1,5 @@
{ {
"$schema": "https://cli.sasjs.io/sasjsconfig-schema.json", "$schema": "https://raw.githubusercontent.com/sasjs/utils/main/src/types/sasjsconfig-schema.json",
"macroFolders": [ "macroFolders": [
"base", "base",
"fcmp", "fcmp",

View File

@@ -20,11 +20,17 @@
%ms_webout(CLOSE) %ms_webout(CLOSE)
@param action Either FETCH, OPEN, ARR, OBJ or CLOSE @param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE
@param ds The dataset to send back to the frontend @param [in] ds The dataset to send back to the frontend
@param dslabel= value to use instead of the real name for sending to JSON @param [out] dslabel= value to use instead of table name for sending to JSON
@param fmt=(Y) Set to N to send back unformatted values @param [in] fmt= (Y) Set to N to send back unformatted values
@param fref=(_webout) The fileref to which to write the JSON @param [out] fref= (_webout) The fileref to which to write the JSON
@param [in] missing= (NULL) Special numeric missing values can be sent as NULL
(eg `null`) or as STRING values (eg `".a"` or `".b"`)
@param [in] showmeta= (NO) Set to YES to output metadata alongside each table,
such as the column formats and types. The metadata is contained inside an
object with the same name as the table but prefixed with a dollar sign - ie,
`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mp_jsonout.sas @li mp_jsonout.sas
@@ -39,7 +45,9 @@
**/ **/
%macro ms_webout(action,ds,dslabel=,fref=_webout,fmt=Y); %macro ms_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL
,showmeta=NO
);
%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug %global _webin_file_count _webin_fileref1 _webin_name1 _program _debug
sasjs_tables; sasjs_tables;
@@ -79,7 +87,7 @@
OPTIONS NOBOMFILE; OPTIONS NOBOMFILE;
/* setup json */ /* setup json */
data _null_;file &fref encoding='utf-8'; data _null_;file &fref encoding='utf-8' termstr=lf;
%if %str(&_debug) ge 131 %then %do; %if %str(&_debug) ge 131 %then %do;
put '>>weboutBEGIN<<'; put '>>weboutBEGIN<<';
%end; %end;
@@ -91,7 +99,7 @@
%else %if &action=ARR or &action=OBJ %then %do; %else %if &action=ARR or &action=OBJ %then %do;
%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref
,engine=DATASTEP,dbg=%str(&_debug) ,engine=DATASTEP,missing=&missing,showmeta=&showmeta
) )
%end; %end;
%else %if &action=CLOSE %then %do; %else %if &action=CLOSE %then %do;
@@ -106,16 +114,13 @@
set &tempds; set &tempds;
if not (upcase(name) =:"DATA"); /* ignore temp datasets */ if not (upcase(name) =:"DATA"); /* ignore temp datasets */
i+1; i+1;
call symputx('wt'!!left(i),name,'l'); call symputx(cats('wt',i),name,'l');
call symputx('wtcnt',i,'l'); call symputx('wtcnt',i,'l');
data _null_; file &fref mod encoding='utf-8'; data _null_; file &fref mod encoding='utf-8' termstr=lf;
put ",""WORK"":{"; put ",""WORK"":{";
%do i=1 %to &wtcnt; %do i=1 %to &wtcnt;
%let wt=&&wt&i; %let wt=&&wt&i;
proc contents noprint data=&wt data _null_; file &fref mod encoding='utf-8' termstr=lf;
out=_data_ (keep=name type length format:);
run;%let tempds=%scan(&syslast,2,.);
data _null_; file &fref mod encoding='utf-8';
dsid=open("WORK.&wt",'is'); dsid=open("WORK.&wt",'is');
nlobs=attrn(dsid,'NLOBS'); nlobs=attrn(dsid,'NLOBS');
nvars=attrn(dsid,'NVARS'); nvars=attrn(dsid,'NVARS');
@@ -124,17 +129,16 @@
put " ""&wt"" : {"; put " ""&wt"" : {";
put '"nlobs":' nlobs; put '"nlobs":' nlobs;
put ',"nvars":' nvars; put ',"nvars":' nvars;
%mp_jsonout(OBJ,&tempds,jref=&fref,dslabel=colattrs,engine=DATASTEP) %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=YES)
%mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,engine=DATASTEP) data _null_; file &fref mod encoding='utf-8' termstr=lf;
data _null_; file &fref mod encoding='utf-8';
put "}"; put "}";
%end; %end;
data _null_; file &fref mod encoding='utf-8'; data _null_; file &fref mod encoding='utf-8' termstr=lf termstr=lf;
put "}"; put "}";
run; run;
%end; %end;
/* close off json */ /* close off json */
data _null_;file &fref mod encoding='utf-8'; data _null_;file &fref mod encoding='utf-8' termstr=lf;
_PROGRAM=quote(trim(resolve(symget('_PROGRAM')))); _PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
put ",""SYSUSERID"" : ""&sysuserid"" "; put ",""SYSUSERID"" : ""&sysuserid"" ";
put ",""MF_GETUSER"" : ""%mf_getuser()"" "; put ",""MF_GETUSER"" : ""%mf_getuser()"" ";
@@ -147,7 +151,9 @@
put ",""SYSHOSTNAME"" : ""&syshostname"" "; put ",""SYSHOSTNAME"" : ""&syshostname"" ";
put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" "; put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";
put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" "; put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";
put ",""SYSPROCESSNAME"" : ""&SYSPROCESSNAME"" "; length SYSPROCESSNAME $512;
SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));
put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;
put ",""SYSJOBID"" : ""&sysjobid"" "; put ",""SYSJOBID"" : ""&sysjobid"" ";
put ",""SYSSCPL"" : ""&sysscpl"" "; put ",""SYSSCPL"" : ""&sysscpl"" ";
put ",""SYSSITE"" : ""&syssite"" "; put ",""SYSSITE"" : ""&syssite"" ";
@@ -155,9 +161,11 @@
sysvlong=quote(trim(symget('sysvlong'))); sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong; put ',"SYSVLONG" : ' sysvlong;
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" "; put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" ";
put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" '; put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
autoexec=quote(trim(getoption('autoexec'))); length autoexec $512;
autoexec=quote(urlencode(trim(getoption('autoexec'))));
put ',"AUTOEXEC" : ' autoexec; put ',"AUTOEXEC" : ' autoexec;
length memsize $32;
memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)"; memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";
memsize=quote(cats(memsize)); memsize=quote(cats(memsize));
put ',"MEMSIZE" : ' memsize; put ',"MEMSIZE" : ' memsize;

View File

@@ -0,0 +1,67 @@
/**
@file
@brief Testing mcf_length.sas macro
<h4> SAS Macros </h4>
@li mcf_length.sas
@li mp_assert.sas
**/
%mcf_length(wrap=YES, insert_cmplib=YES)
data test;
call symputx('null',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));
call symputx('six',mcf_length(1234567890));
call symputx('seven',mcf_length(12345678901234));
call symputx('eight',mcf_length(12345678901234567));
run;
%mp_assert(
iftrue=(%str(&null)=%str(0)),
desc=Check if NULL returns 0
)
%mp_assert(
iftrue=(%str(&special)=%str(3)),
desc=Check if special missing ._ returns 3
)
%mp_assert(
iftrue=(%str(&three)=%str(3)),
desc=Check for length 3
)
%mp_assert(
iftrue=(%str(&four)=%str(4)),
desc=Check for length 4
)
%mp_assert(
iftrue=(%str(&five)=%str(5)),
desc=Check for length 5
)
%mp_assert(
iftrue=(%str(&six)=%str(6)),
desc=Check for length 6
)
%mp_assert(
iftrue=(%str(&seven)=%str(7)),
desc=Check for length 3
)
%mp_assert(
iftrue=(%str(&eight)=%str(8)),
desc=Check for length 8
)
%mp_assert(
iftrue=(&syscc=0),
desc=Check syscc=0 before re-initialisation
)
/* test 2 - compile again test for warnings */
%mcf_length(wrap=YES, insert_cmplib=YES)
%mp_assert(
iftrue=(&syscc=0),
desc=Check syscc=0 after re-initialisation
)

View File

@@ -0,0 +1,22 @@
/**
@file
@brief Testing mf_existvar macro
<h4> SAS Macros </h4>
@li mf_existvar.sas
@li mp_assert.sas
**/
%mp_assert(
iftrue=(%mf_existvar(sashelp.class,age)=1),
desc=Checking existing var exists,
outds=work.test_results
)
%mp_assert(
iftrue=(%mf_existvar(sashelp.class,isjustanumber)=0),
desc=Checking non existing var does not exist,
outds=work.test_results
)

View File

@@ -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;
<h4> SAS Macros </h4>
@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
)

View File

@@ -0,0 +1,20 @@
/**
@file
@brief Testing mf_wordsinstr1andstr2 macro
<h4> SAS Macros </h4>
@li mf_wordsinstr1andstr2.sas
@li mp_assert.sas
**/
%let x=%mf_wordsinstr1andstr2(str1=xx DOLLAR x $CHAR xxx W MONNAME
,str2=DOLLAR $CHAR W MONNAME xxxxxx
);
%mp_assert(
iftrue=(
"&x"="DOLLAR $CHAR W MONNAME"
),
desc=Checking basic string,
outds=work.test_results
)

View File

@@ -0,0 +1,20 @@
/**
@file
@brief Testing mf_wordsinstr1butnotstr2 macro
<h4> SAS Macros </h4>
@li mf_wordsinstr1butnotstr2.sas
@li mp_assert.sas
**/
%let x=%mf_wordsinstr1butnotstr2(str1=xx DOLLAR x $CHAR xxx W MONNAME
,str2=ff xx x xxx xxxxxx
);
%mp_assert(
iftrue=(
"&x"="DOLLAR $CHAR W MONNAME"
),
desc=Checking basic string,
outds=work.test_results
)

View File

@@ -0,0 +1,74 @@
/**
@file
@brief Testing mf_writefile.sas macro
<h4> SAS Macros </h4>
@li mf_writefile.sas
@li mp_assert.sas
**/
%mf_writefile(&sasjswork/myfile.txt,l1=some content,l2=more content)
data _null_;
infile "&sasjswork/myfile.txt";
input;
if _n_=2 then call symputx('test1',_infile_);
run;
%mp_assert(
iftrue=(&syscc=0),
desc=Check code ran without errors,
outds=work.test_results
)
%mp_assert(
iftrue=(&test1=more content),
desc=Checking line was created,
outds=work.test_results
)
%mf_writefile(&sasjswork/myfile.txt,l1=some content,l2=different content)
data _null_;
infile "&sasjswork/myfile.txt";
input;
if _n_=2 then call symputx('test2',_infile_);
run;
%mp_assert(
iftrue=(&syscc=0),
desc=Check code ran without errors for test2,
outds=work.test_results
)
%mp_assert(
iftrue=(&test2=different content),
desc=Checking second line was overwritten,
outds=work.test_results
)
%global test3 test4;
%mf_writefile(&sasjswork/myfile.txt
,mode=a
,l1=%str(aah, content)
,l2=append content
)
data _null_;
infile "&sasjswork/myfile.txt";
input;
if _n_=2 then call symputx('test3',_infile_);
if _n_=4 then call symputx('test4',_infile_);
run;
%mp_assert(
iftrue=(&syscc=0),
desc=Check code ran without errors for test2,
outds=work.test_results
)
%mp_assert(
iftrue=(&test3=different content),
desc=Checking second line was not overwritten,
outds=work.test_results
)
%mp_assert(
iftrue=(&test4=append content),
desc=Checking fourth line was appended,
outds=work.test_results
)

View File

@@ -0,0 +1,45 @@
/**
@file
@brief Testing mp_applyformats.sas macro
<h4> SAS Macros </h4>
@li mf_getvarformat.sas
@li mp_applyformats.sas
@li mp_assert.sas
@li mp_getcols.sas
**/
/**
* Test 1 Base case
*/
data work.example;
set sashelp.prdsale;
format _all_;
run;
%let origfmt=%mf_getvarformat(work.example,month);
%mp_getcols(sashelp.prdsale,outds=work.cols)
data work.cols2;
set work.cols;
lib='WORK';
ds='EXAMPLE';
var=name;
fmt=format;
keep lib ds var fmt;
run;
%mp_applyformats(work.cols2)
%mp_assert(
iftrue=("&orig_fmt"=""),
desc=Check that formats were cleared,
outds=work.test_results
)
%mp_assert(
iftrue=("%mf_getvarformat(work.example,month)"="MONNAME3."),
desc=Check that formats were applied,
outds=work.test_results
)

View File

@@ -0,0 +1,15 @@
/**
@file
@brief Testing mp_assert macro
@details This is quite "meta".. it's just testing itself
<h4> SAS Macros </h4>
@li mp_assert.sas
**/
%mp_assert(
iftrue=(1=1),
desc=Checking result was created,
outds=work.test_results
)

View File

@@ -0,0 +1,80 @@
/**
@file
@brief Testing mp_assertscope macro
<h4> SAS Macros </h4>
@li mf_getvalue.sas
@li mp_assert.sas
@li mp_assertscope.sas
**/
%macro dostuff(action);
%if &action=ADD %then %do;
%global NEWVAR1 NEWVAR2;
%end;
%else %if &action=DEL %then %do;
%symdel NEWVAR1 NEWVAR2;
%end;
%else %if &action=MOD %then %do;
%let NEWVAR1=Let us pray..;
%end;
%else %if &action=NOTHING %then %do;
%local a b c d e;
%end;
%mend dostuff;
/* check for adding variables */
%mp_assertscope(SNAPSHOT)
%dostuff(ADD)
%mp_assertscope(COMPARE,outds=work.testing_the_tester1)
%mp_assert(
iftrue=(
"%mf_getvalue(work.testing_the_tester1,test_comments)"
="Mod:() Add:(NEWVAR1 NEWVAR2) Del:()"
),
desc=Checking result when vars added,
outds=work.test_results
)
/* check for modifying variables */
%mp_assertscope(SNAPSHOT)
%dostuff(MOD)
%mp_assertscope(COMPARE,outds=work.testing_the_tester2)
%mp_assert(
iftrue=(
"%mf_getvalue(work.testing_the_tester2,test_comments)"
="Mod:(NEWVAR1) Add:() Del:()"
),
desc=Checking result when vars modified,
outds=work.test_results
)
/* check for deleting variables */
%mp_assertscope(SNAPSHOT)
%dostuff(DEL)
%mp_assertscope(COMPARE,outds=work.testing_the_tester3)
%mp_assert(
iftrue=(
"%mf_getvalue(work.testing_the_tester3,test_comments)"
="Mod:() Add:() Del:(NEWVAR1 NEWVAR2)"
),
desc=Checking result when vars deleted,
outds=work.test_results
)
/* check for doing nothing */
%mp_assertscope(SNAPSHOT)
%dostuff(NOTHING)
%mp_assertscope(COMPARE,outds=work.testing_the_tester4)
%mp_assert(
iftrue=(
"%mf_getvalue(work.testing_the_tester4,test_comments)"
="GLOBAL Variables Unmodified"
),
desc=Checking results when nothing created,
outds=work.test_results
)

View File

@@ -0,0 +1,31 @@
/**
@file
@brief Testing mp_coretable.sas macro
<h4> SAS Macros </h4>
@li mf_existds.sas
@li mp_coretable.sas
@li mp_assert.sas
**/
%mp_coretable(LOCKTABLE,libds=work.lock)
%mp_assert(
iftrue=(%mf_existds(work.lock)=1),
desc=Lock table created,
outds=work.test_results
)
%mp_coretable(LOCKTABLE)
%mp_assert(
iftrue=("&syscc"="0"),
desc=DDL export ran without errors,
outds=work.test_results
)
%mp_coretable(FILTER_SUMMARY,libds=work.sum)
%mp_assert(
iftrue=(%mf_existds(work.sum)=1),
desc=Filter summary table created,
outds=work.test_results
)

View File

@@ -0,0 +1,44 @@
/**
@file
@brief Testing mp_ds2squeeze.sas macro
<h4> SAS Macros </h4>
@li mf_getvarlen.sas
@li mp_assert.sas
@li mp_assertscope.sas
@li mp_ds2squeeze.sas
**/
data big;
length my big $32000;
do i=1 to 1e4;
my=repeat('oh my',100);
big='dawg';
special=._;
missn=.;
missc='';
output;
end;
run;
%mp_assertscope(SNAPSHOT)
%mp_ds2squeeze(work.big,outds=work.smaller)
%mp_assertscope(COMPARE)
%mp_assert(
iftrue=(&syscc=0),
desc=Checking syscc
)
%mp_assert(
iftrue=(%mf_getvarlen(work.smaller,missn)=3),
desc=Check missing numeric is 3
)
%mp_assert(
iftrue=(%mf_getvarlen(work.smaller,special)=3),
desc=Check missing special numeric is 3
)
%mp_assert(
iftrue=(%mf_getvarlen(work.smaller,missc)=1),
desc=Check missing char is 1
)

View File

@@ -0,0 +1,85 @@
/**
@file
@brief Testing mp_filterstore macro
<h4> SAS Macros </h4>
@li mp_coretable.sas
@li mp_filterstore.sas
@li mp_assertdsobs.sas
@li mp_assert.sas
**/
libname permlib (work);
%mp_coretable(LOCKTABLE,libds=permlib.locktable)
%mp_coretable(FILTER_SUMMARY,libds=permlib.filtsum)
%mp_coretable(FILTER_DETAIL,libds=permlib.filtdet)
%mp_coretable(MAXKEYTABLE,libds=permlib.maxkey)
/* valid filter */
data work.inds;
infile datalines4 dsd;
input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
OPERATOR_NM:$12. RAW_VALUE:$4000.;
datalines4;
AND,AND,1,AGE,=,12
AND,AND,1,SEX,<=,"'M'"
AND,OR,2,Name,NOT IN,"('Jane','Alfred')"
AND,OR,2,Weight,>=,77.7
AND,OR,2,Weight,NE,77.7
;;;;
run;
%mp_filterstore(libds=sashelp.class,
queryds=work.inds,
filter_summary=permlib.filtsum,
filter_detail=permlib.filtdet,
lock_table=permlib.locktable,
maxkeytable=permlib.maxkey,
outresult=work.result,
outquery=work.query,
mdebug=1
)
%mp_assert(iftrue=(&syscc>0),
desc=Ensure macro runs without errors,
outds=work.test_results
)
/* ensure only one record created */
%mp_assertdsobs(permlib.filtsum,
desc=Initial query,
test=ATMOST 1,
outds=work.test_results
)
/* check RK is correct */
proc sql noprint;
select max(filter_rk) into: test1 from work.result;
%mp_assert(iftrue=(&test1=1),
desc=Ensure filter rk is correct,
outds=work.test_results
)
/* Test 2 - load same table again and ensure we get the same RK */
%mp_filterstore(libds=sashelp.class,
queryds=work.inds,
filter_summary=permlib.filtsum,
filter_detail=permlib.filtdet,
lock_table=permlib.locktable,
maxkeytable=permlib.maxkey,
outresult=work.result,
outquery=work.query,
mdebug=1
)
/* ensure only one record created */
%mp_assertdsobs(permlib.filtsum,
desc=Initial query - same obs,
test=ATMOST 1,
outds=work.test_results
)
/* check RK is correct */
proc sql noprint;
select max(filter_rk) into: test2 from work.result;
%mp_assert(iftrue=(&test2=1),
desc=Ensure filter rk is correct for second run,
outds=work.test_results
)

View File

@@ -4,6 +4,7 @@
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mp_getcols.sas @li mp_getcols.sas
@li mp_assertcols.sas
@li mp_assertcolvals.sas @li mp_assertcolvals.sas
@li mp_assertdsobs.sas @li mp_assertdsobs.sas
@@ -30,4 +31,10 @@ run;
checkvals=work.check.val, checkvals=work.check.val,
desc=All values have a match, desc=All values have a match,
test=ALLVALS test=ALLVALS
)
%mp_assertcols(work.info,
cols=name type length varnum format label ddtype fmtname,
test=ALL,
desc=check all columns exist
) )

View File

@@ -0,0 +1,80 @@
/**
@file
@brief Testing mp_getmaxvarlengths macro
<h4> SAS Macros </h4>
@li mp_getmaxvarlengths.sas
@li mp_assert.sas
@li mp_assertdsobs.sas
@li mp_assertscope.sas
**/
/* regular usage */
%mp_assertscope(SNAPSHOT)
%mp_getmaxvarlengths(sashelp.class,outds=work.myds)
%mp_assertscope(COMPARE,desc=checking scope leakage on mp_getmaxvarlengths)
%mp_assert(
iftrue=(&syscc=0),
desc=No errs
)
%mp_assertdsobs(work.myds,
desc=Has 5 records,
test=EQUALS 5
)
data work.errs;
set work.myds;
if name='Name' and maxlen ne 7 then output;
if name='Sex' and maxlen ne 1 then output;
if name='Age' and maxlen ne 3 then output;
if name='Height' and maxlen ne 8 then output;
if name='Weight' and maxlen ne 3 then output;
run;
data _null_;
set work.errs;
putlog (_all_)(=);
run;
%mp_assertdsobs(work.errs,
desc=Err table has 0 records,
test=EQUALS 0
)
/* test2 */
data work.test2;
length a 3 b 5;
a=1/3;
b=1/3;
c=1/3;
d=._;
e=.;
output;
output;
run;
%mp_getmaxvarlengths(work.test2,outds=work.myds2)
%mp_assert(
iftrue=(&syscc=0),
desc=No errs in second test (with nulls)
)
%mp_assertdsobs(work.myds2,
desc=Has 5 records,
test=EQUALS 5
)
data work.errs2;
set work.myds2;
if name='a' and maxlen ne 3 then output;
if name='b' and maxlen ne 5 then output;
if name='c' and maxlen ne 8 then output;
if name='d' and maxlen ne 3 then output;
if name='e' and maxlen ne 0 then output;
run;
data _null_;
set work.errs2;
putlog (_all_)(=);
run;
%mp_assertdsobs(work.errs2,
desc=Err table has 0 records,
test=EQUALS 0
)

View File

@@ -34,6 +34,11 @@ data work.test;
call symputx('dtval',dtval); call symputx('dtval',dtval);
run; run;
%mp_assert(
iftrue=(&syscc=0),
desc=Checking for error condition,
outds=work.test_results
)
%mp_assert( %mp_assert(
iftrue=(&dtval=&compare), iftrue=(&dtval=&compare),

View File

@@ -0,0 +1,52 @@
/**
@file
@brief Testing mp_jsonout.sas macro with special missings
<h4> SAS Macros </h4>
@li mp_jsonout.sas
@li mp_assert.sas
**/
filename webref temp;
data demo;
do x=._,.,.a,.b,.c,.d,.e,-99, 0, 1,2, 3.333333;
output;
end;
run;
%mp_jsonout(OPEN,jref=webref)
%mp_jsonout(OBJ,demo,jref=webref,fmt=N,missing=STRING)
%mp_jsonout(CLOSE,jref=webref)
data _null_;
infile webref;
input;
putlog _infile_;
run;
libname web JSON fileref=webref;
/* proc json turns to char - so switch back to numeric */
data work.test(keep=x);
set web.demo(rename=(x=y));
if y ='_' then x=._;
else if anyalpha(y) then x=input(cats(".",y),best.);
else x=input(y,best.);
put (_all_)(=);
run;
%mp_assert(
iftrue=(&syscc=0),
desc=Checking for error condition with special missing export,
outds=work.test_results
)
proc compare base=work.demo compare=work.test;
quit;
%mp_assert(
iftrue=(&sysinfo=0),
desc=Returned json is identical to input table for all special missings,
outds=work.test_results
)

View File

@@ -6,12 +6,13 @@
@li mp_lockanytable.sas @li mp_lockanytable.sas
@li mp_assertcols.sas @li mp_assertcols.sas
@li mp_assertcolvals.sas @li mp_assertcolvals.sas
@li mp_coretable.sas
**/ **/
/* check create table */ /* check create table */
%mp_lockanytable(MAKETABLE, ctl_ds=work.controller) %mp_coretable(LOCKTABLE,libds=work.controller)
%mp_assertcols(work.controller, %mp_assertcols(work.controller,
cols=lock_status_cd lock_lib lock_ds lock_user_nm lock_ref lock_pid cols=lock_status_cd lock_lib lock_ds lock_user_nm lock_ref lock_pid

View File

@@ -0,0 +1,59 @@
/**
@file
@brief Testing mp_makedata.sas macro
<h4> SAS Macros </h4>
@li mf_nobs.sas
@li mp_makedata.sas
@li mp_assert.sas
**/
/**
* Test 1 - Regular makedata call
*/
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,obs=500)
%mp_assert(
iftrue=("%mf_nobs(work.example)"="500"),
desc=Check that 500 rows were created,
outds=work.test_results
)
data _null_;
set work.example;
call symputx('lenvar',length(dd_source));
stop;
run;
%mp_assert(
iftrue=("&lenvar"="2048"),
desc=Check that entire length of variable is populated,
outds=work.test_results
)
proc sql;
create table work.example2(
TX_FROM float format=datetime19.,
DD_TYPE char(16),
DD_SOURCE char(2048),
DD_SHORTDESC char(256),
some_num num
);
%mp_makedata(work.example2)
%mp_assert(
iftrue=(&syscc=0),
desc=Ensure tables without keys still generate,
outds=work.test_results
)

View File

@@ -0,0 +1,116 @@
/**
@file
@brief Testing mp_retainedkey macro
<h4> SAS Macros </h4>
@li mp_assert.sas
@li mp_assertcolvals.sas
@li mp_retainedkey.sas
**/
/**
* Setup base tables
*/
proc sql;
create table work.maxkeytable(
keytable varchar(41) label='Base table in libref.dataset format',
keycolumn char(32) format=$32.
label='The Retained key field containing the key values.',
max_key num label=
'Integer representing current max RK or SK value in the KEYTABLE',
processed_dttm num format=E8601DT26.6
label='Datetime this value was last updated',
constraint pk_mpe_maxkeyvalues
primary key(keytable));
create table work.locktable(
lock_lib char(8),
lock_ds char(32),
lock_status_cd char(10) not null,
lock_user_nm char(100) not null ,
lock_ref char(200),
lock_pid char(10),
lock_start_dttm num format=E8601DT26.6,
lock_end_dttm num format=E8601DT26.6,
constraint pk_mp_lockanytable primary key(lock_lib,lock_ds));
data work.targetds;
rk_col=_n_;
set sashelp.class;
run;
data work.appendtable;
set sashelp.class;
if mod(_n_,2)=0 then name=cats('New',_n_);
if _n_<7;
run;
libname x (work);
/** Test 1 - base case **/
%mp_retainedkey(
base_lib=X
,base_dsn=targetds
,append_lib=X
,append_dsn=APPENDTABLE
,retained_key=rk_col
,business_key= name
,check_uniqueness=NO
,maxkeytable=0
,locktable=0
,outds=work.APPEND
,filter_str=
)
%mp_assert(
iftrue=(&syscc=0),
desc=Checking errors in test 1,
outds=work.test_results
)
data work.check;
do val=1,3,5,20,21,22;
output;
end;
run;
%mp_assertcolvals(work.append.rk_col,
checkvals=work.check.val,
desc=All values have a match,
test=ALLVALS
)
/** Test 2 - all new records, with metadata logging and unique check **/
data work.targetds2;
rk_col=_n_;
set sashelp.class;
run;
data work.appendtable2;
set sashelp.class;
do x=1 to 21;
name=cats('New',x);
output;
end;
stop;
run;
%mp_retainedkey(base_dsn=targetds2
,append_dsn=APPENDTABLE2
,retained_key=rk_col
,business_key= name
,check_uniqueness=YES
,maxkeytable=x.maxkeytable
,locktable=work.locktable
,outds=WORK.APPEND2
,filter_str=
)
%mp_assert(
iftrue=(&syscc=0),
desc=Checking errors in test 2,
outds=work.test_results
)
%mp_assert(
iftrue=(%mf_nobs(work.append2)=21),
desc=Checking append records created,
outds=work.test_results
)

View File

@@ -59,4 +59,74 @@ run;
desc=Test2 - ISNUM, desc=Test2 - ISNUM,
test=EQUALS 4, test=EQUALS 4,
outds=work.test_results outds=work.test_results
)
/**
* Test 3 - FORMAT
*/
data test3;
infile datalines4 dsd;
input;
infile=_infile_;
%mp_validatecol(infile,FORMAT,is_format)
if is_format=1;
datalines4;
$.
$format.
$format12.2
somenum.
somenum12.4
above are good
the rest are bad
%abort
1&somethingverybad.
&
+-1
.
a.A
$format12.1b
$format12.1b1
;;;;
run;
%mp_assertdsobs(work.test3,
desc=Test3 - ISFORMAT,
test=EQUALS 5,
outds=work.test_results
)
/**
* Test 4 - ISINT
*/
data test4;
infile datalines4 dsd;
input;
infile=_infile_;
%mp_validatecol(infile,ISINT,is_integer)
if is_integer=1;
datalines4;
1
1234
-134
-1.0
1.0
0
above are good
the rest are bad
0.1
1.1
-0.001
%abort
1&somethingverybad.
&
+-1
.
a.A
$format12.1b
$format12.1b1
;;;;
run;
%mp_assertdsobs(work.test4,
desc=Test4 - ISFORMAT,
test=EQUALS 6,
outds=work.test_results
) )

View File

@@ -4,6 +4,7 @@
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mp_init.sas @li mp_init.sas
@li mv_webout.sas
**/ **/

View File

@@ -54,9 +54,9 @@
that location that location
@param [in] adapter= the macro uses the sasjs adapter by default. To use @param [in] adapter= the macro uses the sasjs adapter by default. To use
another adapter, add a (different) fileref here. another adapter, add a (different) fileref here.
@param [in] contextname= Choose a specific context on which to run the Job. Leave @param [in] contextname= Choose a specific context on which to run the Job.
blank to use the default context. From Viya 3.5 it is possible to configure Leave blank to use the default context. From Viya 3.5 it is possible to
a shared context - see configure a shared context - see
https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5pyhn1wg3ja0drdl6h.htm&docsetVersion=3.5&locale=en https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5pyhn1wg3ja0drdl6h.htm&docsetVersion=3.5&locale=en
@param [in] mdebug=(0) set to 1 to enable DEBUG messages @param [in] mdebug=(0) set to 1 to enable DEBUG messages
@@ -237,129 +237,140 @@ data _null_;
put "/* Created on %sysfunc(datetime(),datetime19.) by &sysuserid */"; put "/* Created on %sysfunc(datetime(),datetime19.) by &sysuserid */";
/* WEBOUT BEGIN */ /* WEBOUT BEGIN */
put ' '; put ' ';
put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=DATASTEP,dbg=0 '; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y ';
put ' ,engine=DATASTEP ';
put ' ,missing=NULL ';
put ' ,showmeta=NO ';
put ')/*/STORE SOURCE*/; '; put ')/*/STORE SOURCE*/; ';
put '%put output location=&jref; '; put '%local tempds colinfo fmtds i numcols; ';
put '%let numcols=0; ';
put ' ';
put '%if &action=OPEN %then %do; '; put '%if &action=OPEN %then %do; ';
put ' options nobomfile; '; put ' options nobomfile; ';
put ' data _null_;file &jref encoding=''utf-8''; '; put ' data _null_;file &jref encoding=''utf-8'' ; ';
put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; '; put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
put ' run; '; put ' run; ';
put '%end; '; put '%end; ';
put '%else %if (&action=ARR or &action=OBJ) %then %do; '; put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
put ' options validvarname=upcase; '; put ' options validvarname=upcase; ';
put ' data _null_;file &jref mod encoding=''utf-8''; '; put ' data _null_; file &jref encoding=''utf-8'' mod; ';
put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; '; put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; ';
put ' '; put ' ';
put ' /* grab col defs */ ';
put ' proc contents noprint data=&ds ';
put ' out=_data_(keep=name type length format formatl formatd varnum label); ';
put ' run; ';
put ' %let colinfo=%scan(&syslast,2,.); ';
put ' proc sort data=&colinfo; ';
put ' by varnum; ';
put ' run; ';
put ' /* move meta to mac vars */ ';
put ' data _null_; ';
put ' if _n_=1 then call symputx(''numcols'',nobs,''l''); ';
put ' set &colinfo end=last nobs=nobs; ';
put ' name=upcase(name); ';
put ' /* fix formats */ ';
put ' if type=2 or type=6 then do; ';
put ' typelong=''char''; ';
put ' length fmt $49.; ';
put ' if format='''' then fmt=cats(''$'',length,''.''); ';
put ' else if formatl=0 then fmt=cats(format,''.''); ';
put ' else fmt=cats(format,formatl,''.''); ';
put ' newlen=max(formatl,length); ';
put ' end; ';
put ' else do; ';
put ' typelong=''num''; ';
put ' if format='''' then fmt=''best.''; ';
put ' else if formatl=0 then fmt=cats(format,''.''); ';
put ' else if formatd=0 then fmt=cats(format,formatl,''.''); ';
put ' else fmt=cats(format,formatl,''.'',formatd); ';
put ' /* needs to be wide, for datetimes etc */ ';
put ' newlen=max(length,formatl,24); ';
put ' end; ';
put ' /* 32 char unique name */ ';
put ' newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27); ';
put ' ';
put ' call symputx(cats(''name'',_n_),name,''l''); ';
put ' call symputx(cats(''newname'',_n_),newname,''l''); ';
put ' call symputx(cats(''len'',_n_),newlen,''l''); ';
put ' call symputx(cats(''length'',_n_),length,''l''); ';
put ' call symputx(cats(''fmt'',_n_),fmt,''l''); ';
put ' call symputx(cats(''type'',_n_),type,''l''); ';
put ' call symputx(cats(''typelong'',_n_),typelong,''l''); ';
put ' call symputx(cats(''label'',_n_),coalescec(label,name),''l''); ';
put ' run; ';
put ' ';
put ' %let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
put ' ';
put ' %if &engine=PROCJSON %then %do; '; put ' %if &engine=PROCJSON %then %do; ';
put ' data;run;%let tempds=&syslast; '; put ' %if &missing=STRING %then %do; ';
put ' proc sql;drop table &tempds; '; put ' %put &sysmacroname: Special Missings not supported in proc json.; ';
put ' %put &sysmacroname: Switching to DATASTEP engine; ';
put ' %goto datastep; ';
put ' %end; ';
put ' data &tempds /view=&tempds;set &ds; '; put ' data &tempds /view=&tempds;set &ds; ';
put ' %if &fmt=N %then format _numeric_ best32.;; '; put ' %if &fmt=N %then format _numeric_ best32.;; ';
put ' /* PRETTY is necessary to avoid line truncation in large files */ ';
put ' proc json out=&jref pretty '; put ' proc json out=&jref pretty ';
put ' %if &action=ARR %then nokeys ; '; put ' %if &action=ARR %then nokeys ; ';
put ' ;export &tempds / nosastags fmtnumeric; '; put ' ;export &tempds / nosastags fmtnumeric; ';
put ' run; '; put ' run; ';
put ' proc sql;drop view &tempds; ';
put ' %end; '; put ' %end; ';
put ' %else %if &engine=DATASTEP %then %do; '; put ' %else %if &engine=DATASTEP %then %do; ';
put ' %local cols i tempds; '; put ' %datastep: ';
put ' %let cols=0; '; put ' %if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1 ';
put ' %if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1 %then %do; '; put ' %then %do; ';
put ' %put &sysmacroname: &ds NOT FOUND!!!; '; put ' %put &sysmacroname: &ds NOT FOUND!!!; ';
put ' %return; '; put ' %return; ';
put ' %end; '; put ' %end; ';
put ' %if &fmt=Y %then %do; ';
put ' %put converting every variable to a formatted variable; ';
put ' /* see mp_ds2fmtds.sas for source */ ';
put ' proc contents noprint data=&ds ';
put ' out=_data_(keep=name type length format formatl formatd varnum); ';
put ' run; ';
put ' proc sort; ';
put ' by varnum; ';
put ' run; ';
put ' %local fmtds; ';
put ' %let fmtds=%scan(&syslast,2,.); ';
put ' /* prepare formats and varnames */ ';
put ' data _null_; ';
put ' if _n_=1 then call symputx(''nobs'',nobs,''l''); ';
put ' set &fmtds end=last nobs=nobs; ';
put ' name=upcase(name); ';
put ' /* fix formats */ ';
put ' if type=2 or type=6 then do; ';
put ' length fmt $49.; ';
put ' if format='''' then fmt=cats(''$'',length,''.''); ';
put ' else if formatl=0 then fmt=cats(format,''.''); ';
put ' else fmt=cats(format,formatl,''.''); ';
put ' newlen=max(formatl,length); ';
put ' end; ';
put ' else do; ';
put ' if format='''' then fmt=''best.''; ';
put ' else if formatl=0 then fmt=cats(format,''.''); ';
put ' else if formatd=0 then fmt=cats(format,formatl,''.''); ';
put ' else fmt=cats(format,formatl,''.'',formatd); ';
put ' /* needs to be wide, for datetimes etc */ ';
put ' newlen=max(length,formatl,24); ';
put ' end; ';
put ' /* 32 char unique name */ ';
put ' newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27); ';
put ' '; put ' ';
put ' call symputx(cats(''name'',_n_),name,''l''); '; put ' %if &fmt=Y %then %do; ';
put ' call symputx(cats(''newname'',_n_),newname,''l''); '; put ' data _data_; ';
put ' call symputx(cats(''len'',_n_),newlen,''l''); ';
put ' call symputx(cats(''fmt'',_n_),fmt,''l''); ';
put ' call symputx(cats(''type'',_n_),type,''l''); ';
put ' run; ';
put ' data &fmtds; ';
put ' /* rename on entry */ '; put ' /* rename on entry */ ';
put ' set &ds(rename=( '; put ' set &ds(rename=( ';
put ' %local i; '; put ' %do i=1 %to &numcols; ';
put ' %do i=1 %to &nobs; ';
put ' &&name&i=&&newname&i '; put ' &&name&i=&&newname&i ';
put ' %end; '; put ' %end; ';
put ' )); '; put ' )); ';
put ' %do i=1 %to &nobs; '; put ' %do i=1 %to &numcols; ';
put ' length &&name&i $&&len&i; '; put ' length &&name&i $&&len&i; ';
put ' &&name&i=left(put(&&newname&i,&&fmt&i)); '; put ' &&name&i=left(put(&&newname&i,&&fmt&i)); ';
put ' drop &&newname&i; '; put ' drop &&newname&i; ';
put ' %end; '; put ' %end; ';
put ' if _error_ then call symputx(''syscc'',1012); '; put ' if _error_ then call symputx(''syscc'',1012); ';
put ' run; '; put ' run; ';
put ' %let ds=&fmtds; '; put ' %let fmtds=&syslast; ';
put ' %end; /* &fmt=Y */ '; put ' %end; ';
put ' data _null_;file &jref mod encoding=''utf-8''; ';
put ' put "["; call symputx(''cols'',0,''l''); ';
put ' proc sort ';
put ' data=sashelp.vcolumn(where=(libname=''WORK'' & memname="%upcase(&ds)")) ';
put ' out=_data_; ';
put ' by varnum; ';
put ' ';
put ' data _null_; ';
put ' set _last_ end=last; ';
put ' call symputx(cats(''name'',_n_),name,''l''); ';
put ' call symputx(cats(''type'',_n_),type,''l''); ';
put ' call symputx(cats(''len'',_n_),length,''l''); ';
put ' if last then call symputx(''cols'',_n_,''l''); ';
put ' run; ';
put ' '; put ' ';
put ' proc format; /* credit yabwon for special null removal */ '; put ' proc format; /* credit yabwon for special null removal */ ';
put ' value bart ._ - .z = null '; put ' value bart (default=40) ';
put ' %if &missing=NULL %then %do; ';
put ' ._ - .z = null ';
put ' %end; ';
put ' %else %do; ';
put ' ._ = [quote()] ';
put ' . = null ';
put ' .a - .z = [quote()] ';
put ' %end; ';
put ' other = [best.]; '; put ' other = [best.]; ';
put ' '; put ' ';
put ' data;run; %let tempds=&syslast; /* temp table for spesh char management */ ';
put ' proc sql; drop table &tempds; ';
put ' data &tempds/view=&tempds; '; put ' data &tempds/view=&tempds; ';
put ' attrib _all_ label=''''; '; put ' attrib _all_ label=''''; ';
put ' %do i=1 %to &cols; '; put ' %do i=1 %to &numcols; ';
put ' %if &&type&i=char %then %do; '; put ' %if &&typelong&i=char or &fmt=Y %then %do; ';
put ' length &&name&i $32767; '; put ' length &&name&i $32767; ';
put ' format &&name&i $32767.; '; put ' format &&name&i $32767.; ';
put ' %end; '; put ' %end; ';
put ' %end; '; put ' %end; ';
put ' set &ds; '; put ' %if &fmt=Y %then %do; ';
put ' set &fmtds; ';
put ' %end; ';
put ' %else %do; ';
put ' set &ds; ';
put ' %end; ';
put ' format _numeric_ bart.; '; put ' format _numeric_ bart.; ';
put ' %do i=1 %to &cols; '; put ' %do i=1 %to &numcols; ';
put ' %if &&type&i=char %then %do; '; put ' %if &&typelong&i=char or &fmt=Y %then %do; ';
put ' &&name&i=''"''!!trim(prxchange(''s/"/\"/'',-1, '; put ' &&name&i=''"''!!trim(prxchange(''s/"/\"/'',-1, ';
put ' prxchange(''s/''!!''0A''x!!''/\n/'',-1, '; put ' prxchange(''s/''!!''0A''x!!''/\n/'',-1, ';
put ' prxchange(''s/''!!''0D''x!!''/\r/'',-1, '; put ' prxchange(''s/''!!''0D''x!!''/\r/'',-1, ';
@@ -369,49 +380,72 @@ data _null_;
put ' %end; '; put ' %end; ';
put ' %end; '; put ' %end; ';
put ' run; '; put ' run; ';
put ' ';
put ' /* write to temp loc to avoid _webout truncation '; put ' /* write to temp loc to avoid _webout truncation ';
put ' - https://support.sas.com/kb/49/325.html */ '; put ' - https://support.sas.com/kb/49/325.html */ ';
put ' filename _sjs temp lrecl=131068 encoding=''utf-8''; '; put ' filename _sjs temp lrecl=131068 encoding=''utf-8''; ';
put ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod; '; put ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod ; ';
put ' if _n_=1 then put "["; ';
put ' set &tempds; '; put ' set &tempds; ';
put ' if _n_>1 then put "," @; put '; put ' if _n_>1 then put "," @; put ';
put ' %if &action=ARR %then "[" ; %else "{" ; '; put ' %if &action=ARR %then "[" ; %else "{" ; ';
put ' %do i=1 %to &cols; '; put ' %do i=1 %to &numcols; ';
put ' %if &i>1 %then "," ; '; put ' %if &i>1 %then "," ; ';
put ' %if &action=OBJ %then """&&name&i"":" ; '; put ' %if &action=OBJ %then """&&name&i"":" ; ';
put ' &&name&i '; put ' &&name&i ';
put ' %end; '; put ' %end; ';
put ' %if &action=ARR %then "]" ; %else "}" ; ; '; put ' %if &action=ARR %then "]" ; %else "}" ; ; ';
put ' proc sql; ';
put ' drop view &tempds; ';
put ' /* now write the long strings to _webout 1 byte at a time */ '; put ' /* now write the long strings to _webout 1 byte at a time */ ';
put ' data _null_; '; put ' data _null_; ';
put ' length filein 8 fileid 8; '; put ' length filein 8 fileid 8; ';
put ' filein = fopen("_sjs",''I'',1,''B''); '; put ' filein=fopen("_sjs",''I'',1,''B''); ';
put ' fileid = fopen("&jref",''A'',1,''B''); '; put ' fileid=fopen("&jref",''A'',1,''B''); ';
put ' rec = ''20''x; '; put ' rec=''20''x; ';
put ' do while(fread(filein)=0); '; put ' do while(fread(filein)=0); ';
put ' rc = fget(filein,rec,1); '; put ' rc=fget(filein,rec,1); ';
put ' rc = fput(fileid, rec); '; put ' rc=fput(fileid, rec); ';
put ' rc =fwrite(fileid); '; put ' rc=fwrite(fileid); ';
put ' end; '; put ' end; ';
put ' rc = fclose(filein); '; put ' /* close out the table */ ';
put ' rc = fclose(fileid); '; put ' rc=fput(fileid, "]"); ';
put ' rc=fwrite(fileid); ';
put ' rc=fclose(filein); ';
put ' rc=fclose(fileid); ';
put ' run; '; put ' run; ';
put ' filename _sjs clear; '; put ' filename _sjs clear; ';
put ' data _null_; file &jref mod encoding=''utf-8''; '; put ' %end; ';
put ' put "]"; '; put ' ';
put ' proc sql; ';
put ' drop view &tempds; ';
put ' drop table &colinfo; ';
put ' ';
put ' %if &showmeta=YES %then %do; ';
put ' data _null_; file &jref encoding=''utf-8'' mod; ';
put ' put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{"; ';
put ' do i=1 to &numcols; ';
put ' name=quote(trim(symget(cats(''name'',i)))); ';
put ' format=quote(trim(symget(cats(''fmt'',i)))); ';
put ' label=quote(trim(symget(cats(''label'',i)))); ';
put ' length=quote(trim(symget(cats(''length'',i)))); ';
put ' type=quote(trim(symget(cats(''typelong'',i)))); ';
put ' if i>1 then put "," @@; ';
put ' put name '':{"format":'' format '',"label":'' label ';
put ' '',"length":'' length '',"type":'' type ''}''; ';
put ' end; ';
put ' put ''}}''; ';
put ' run; '; put ' run; ';
put ' %end; '; put ' %end; ';
put '%end; '; put '%end; ';
put ' '; put ' ';
put '%else %if &action=CLOSE %then %do; '; put '%else %if &action=CLOSE %then %do; ';
put ' data _null_;file &jref encoding=''utf-8'' mod; '; put ' data _null_; file &jref encoding=''utf-8'' mod ; ';
put ' put "}"; '; put ' put "}"; ';
put ' run; '; put ' run; ';
put '%end; '; put '%end; ';
put '%mend mp_jsonout; '; put '%mend mp_jsonout; ';
put '%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=Y,stream=Y); '; put '%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=Y,stream=Y,missing=NULL ';
put ' ,showmeta=NO ';
put '); ';
put '%global _webin_file_count _webin_fileuri _debug _omittextlog _webin_name '; put '%global _webin_file_count _webin_fileuri _debug _omittextlog _webin_name ';
put ' sasjs_tables SYS_JES_JOB_URI; '; put ' sasjs_tables SYS_JES_JOB_URI; ';
put '%if %index("&_debug",log) %then %let _debug=131; '; put '%if %index("&_debug",log) %then %let _debug=131; ';
@@ -533,12 +567,13 @@ data _null_;
put ' '; put ' ';
put ' /* setup json */ '; put ' /* setup json */ ';
put ' data _null_;file &fref; '; put ' data _null_;file &fref; ';
put ' put ''{"START_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''"''; '; put ' put ''{"SYSDATE" : "'' "&SYSDATE" ''"''; ';
put ' put '',"SYSTIME" : "'' "&SYSTIME" ''"''; ';
put ' run; '; put ' run; ';
put '%end; '; put '%end; ';
put '%else %if &action=ARR or &action=OBJ %then %do; '; put '%else %if &action=ARR or &action=OBJ %then %do; ';
put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt '; put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt ';
put ' ,jref=&fref,engine=DATASTEP,dbg=%str(&_debug) '; put ' ,jref=&fref,engine=DATASTEP,missing=&missing,showmeta=&showmeta ';
put ' ) '; put ' ) ';
put '%end; '; put '%end; ';
put '%else %if &action=CLOSE %then %do; '; put '%else %if &action=CLOSE %then %do; ';
@@ -553,14 +588,11 @@ data _null_;
put ' set &tempds; '; put ' set &tempds; ';
put ' if not (upcase(name) =:"DATA"); /* ignore temp datasets */ '; put ' if not (upcase(name) =:"DATA"); /* ignore temp datasets */ ';
put ' i+1; '; put ' i+1; ';
put ' call symputx(''wt''!!left(i),name); '; put ' call symputx(cats(''wt'',i),name,''l''); ';
put ' call symputx(''wtcnt'',i); '; put ' call symputx(''wtcnt'',i,''l''); ';
put ' data _null_; file &fref mod; put ",""WORK"":{"; '; put ' data _null_; file &fref mod; put ",""WORK"":{"; ';
put ' %do i=1 %to &wtcnt; '; put ' %do i=1 %to &wtcnt; ';
put ' %let wt=&&wt&i; '; put ' %let wt=&&wt&i; ';
put ' proc contents noprint data=&wt ';
put ' out=_data_ (keep=name type length format:); ';
put ' run;%let tempds=%scan(&syslast,2,.); ';
put ' data _null_; file &fref mod; '; put ' data _null_; file &fref mod; ';
put ' dsid=open("WORK.&wt",''is''); '; put ' dsid=open("WORK.&wt",''is''); ';
put ' nlobs=attrn(dsid,''NLOBS''); '; put ' nlobs=attrn(dsid,''NLOBS''); ';
@@ -570,8 +602,7 @@ data _null_;
put ' put " ""&wt"" : {"; '; put ' put " ""&wt"" : {"; ';
put ' put ''"nlobs":'' nlobs; '; put ' put ''"nlobs":'' nlobs; ';
put ' put '',"nvars":'' nvars; '; put ' put '',"nvars":'' nvars; ';
put ' %mp_jsonout(OBJ,&tempds,jref=&fref,dslabel=colattrs,engine=DATASTEP) '; put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=YES) ';
put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,engine=DATASTEP) ';
put ' data _null_; file &fref mod;put "}"; '; put ' data _null_; file &fref mod;put "}"; ';
put ' %end; '; put ' %end; ';
put ' data _null_; file &fref mod;put "}";run; '; put ' data _null_; file &fref mod;put "}";run; ';
@@ -595,7 +626,11 @@ data _null_;
put ' sysvlong=quote(trim(symget(''sysvlong''))); '; put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
put ' put '',"SYSVLONG" : '' sysvlong; '; put ' put '',"SYSVLONG" : '' sysvlong; ';
put ' put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" "; '; put ' put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" "; ';
put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''" ''; '; put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" ''; ';
put ' length memsize $32; ';
put ' memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)"; ';
put ' memsize=quote(cats(memsize)); ';
put ' put '',"MEMSIZE" : '' memsize; ';
put ' put "}"; '; put ' put "}"; ';
put ' '; put ' ';
put ' %if %upcase(&fref) ne _WEBOUT and &stream=Y %then %do; '; put ' %if %upcase(&fref) ne _WEBOUT and &stream=Y %then %do; ';
@@ -629,8 +664,10 @@ data _null_;
put '%global __program _program;'; put '%global __program _program;';
put '%let _program=%sysfunc(coalescec(&__program,&_program));'; put '%let _program=%sysfunc(coalescec(&__program,&_program));';
put ' '; put ' ';
put '%macro webout(action,ds,dslabel=,fmt=);'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO);';
put ' %mv_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt)'; put ' %mv_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt,missing=&missing';
put ' ,showmeta=&showmeta';
put ' )';
put '%mend;'; put '%mend;';
run; run;

View File

@@ -117,6 +117,7 @@ libname &libref1a JSON fileref=&fname1a;
%let found=0; %let found=0;
%put Getting object uri from &libref1a..items; %put Getting object uri from &libref1a..items;
data _null_; data _null_;
length contenttype name $1000;
set &libref1a..items; set &libref1a..items;
if contenttype="&contenttype" and upcase(name)="%upcase(&name)" then do; if contenttype="&contenttype" and upcase(name)="%upcase(&name)" then do;
call symputx('uri',uri,'l'); call symputx('uri',uri,'l');

View File

@@ -114,6 +114,7 @@ libname &libref1a JSON fileref=&fname1a;
%let found=0; %let found=0;
%put Getting object uri from &libref1a..items; %put Getting object uri from &libref1a..items;
data _null_; data _null_;
length contenttype name $1000;
set &libref1a..items; set &libref1a..items;
if contenttype='jobDefinition' and upcase(name)="%upcase(&name)" then do; if contenttype='jobDefinition' and upcase(name)="%upcase(&name)" then do;
call symputx('uri',cats("&base_uri",uri),'l'); call symputx('uri',cats("&base_uri",uri),'l');

View File

@@ -1,32 +0,0 @@
/**
@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
<h4> SAS Macros </h4>
@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;

View File

@@ -1,23 +0,0 @@
/**
@file
@brief deprecated - replaced by mv_registerclient.sas
@version VIYA V.03.04
@author Allan Bowe, source: https://github.com/sasjs/core
<h4> SAS Macros </h4>
@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;

View File

@@ -1,33 +0,0 @@
/**
@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
<h4> SAS Macros </h4>
@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;

View File

@@ -272,7 +272,7 @@ data;run;%let jdswaitfor=&syslast;
data _null_; data _null_;
infile &jfref lrecl=32767; infile &jfref lrecl=32767;
input; input;
jparams='jparams'!!left(symget('jid')); jparams=cats('jparams',symget('jid'));
call symputx(jparams,substr(_infile_,3,length(_infile_)-4)); call symputx(jparams,substr(_infile_,3,length(_infile_)-4));
run; run;
%local jobuid&jid; %local jobuid&jid;

View File

@@ -20,13 +20,19 @@
%mv_webout(CLOSE) %mv_webout(CLOSE)
@param action Either OPEN, ARR, OBJ or CLOSE @param [in] action Either OPEN, ARR, OBJ or CLOSE
@param ds The dataset to send back to the frontend @param [in] ds The dataset to send back to the frontend
@param _webout= fileref for returning the json @param [in] _webout= fileref for returning the json
@param fref=(_mvwtemp) Temp fileref to which to write the output @param [out] fref=(_mvwtemp) Temp fileref to which to write the output
@param dslabel= value to use instead of the real name for sending to JSON @param [out] dslabel= value to use instead of table name for sending to JSON
@param fmt=(Y) change to N to strip formats from output @param [in] fmt=(Y) change to N to strip formats from output
@param stream=(Y) Change to N if not streaming to _webout @param [in] stream=(Y) Change to N if not streaming to _webout
@param [in] missing= (NULL) Special numeric missing values can be sent as NULL
(eg `null`) or as STRING values (eg `".a"` or `".b"`)
@param [in] showmeta= (NO) Set to YES to output metadata alongside each table,
such as the column formats and types. The metadata is contained inside an
object with the same name as the table but prefixed with a dollar sign - ie,
`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mp_jsonout.sas @li mp_jsonout.sas
@@ -36,7 +42,9 @@
@author Allan Bowe, source: https://github.com/sasjs/core @author Allan Bowe, source: https://github.com/sasjs/core
**/ **/
%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=Y,stream=Y); %macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=Y,stream=Y,missing=NULL
,showmeta=NO
);
%global _webin_file_count _webin_fileuri _debug _omittextlog _webin_name %global _webin_file_count _webin_fileuri _debug _omittextlog _webin_name
sasjs_tables SYS_JES_JOB_URI; sasjs_tables SYS_JES_JOB_URI;
%if %index("&_debug",log) %then %let _debug=131; %if %index("&_debug",log) %then %let _debug=131;
@@ -158,12 +166,13 @@
/* setup json */ /* setup json */
data _null_;file &fref; data _null_;file &fref;
put '{"START_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '"'; put '{"SYSDATE" : "' "&SYSDATE" '"';
put ',"SYSTIME" : "' "&SYSTIME" '"';
run; run;
%end; %end;
%else %if &action=ARR or &action=OBJ %then %do; %else %if &action=ARR or &action=OBJ %then %do;
%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt
,jref=&fref,engine=DATASTEP,dbg=%str(&_debug) ,jref=&fref,engine=DATASTEP,missing=&missing,showmeta=&showmeta
) )
%end; %end;
%else %if &action=CLOSE %then %do; %else %if &action=CLOSE %then %do;
@@ -178,14 +187,11 @@
set &tempds; set &tempds;
if not (upcase(name) =:"DATA"); /* ignore temp datasets */ if not (upcase(name) =:"DATA"); /* ignore temp datasets */
i+1; i+1;
call symputx('wt'!!left(i),name); call symputx(cats('wt',i),name,'l');
call symputx('wtcnt',i); call symputx('wtcnt',i,'l');
data _null_; file &fref mod; put ",""WORK"":{"; data _null_; file &fref mod; put ",""WORK"":{";
%do i=1 %to &wtcnt; %do i=1 %to &wtcnt;
%let wt=&&wt&i; %let wt=&&wt&i;
proc contents noprint data=&wt
out=_data_ (keep=name type length format:);
run;%let tempds=%scan(&syslast,2,.);
data _null_; file &fref mod; data _null_; file &fref mod;
dsid=open("WORK.&wt",'is'); dsid=open("WORK.&wt",'is');
nlobs=attrn(dsid,'NLOBS'); nlobs=attrn(dsid,'NLOBS');
@@ -195,8 +201,7 @@
put " ""&wt"" : {"; put " ""&wt"" : {";
put '"nlobs":' nlobs; put '"nlobs":' nlobs;
put ',"nvars":' nvars; put ',"nvars":' nvars;
%mp_jsonout(OBJ,&tempds,jref=&fref,dslabel=colattrs,engine=DATASTEP) %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=YES)
%mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,engine=DATASTEP)
data _null_; file &fref mod;put "}"; data _null_; file &fref mod;put "}";
%end; %end;
data _null_; file &fref mod;put "}";run; data _null_; file &fref mod;put "}";run;
@@ -220,7 +225,11 @@
sysvlong=quote(trim(symget('sysvlong'))); sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong; put ',"SYSVLONG" : ' sysvlong;
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" "; put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" ";
put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" '; put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
length memsize $32;
memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";
memsize=quote(cats(memsize));
put ',"MEMSIZE" : ' memsize;
put "}"; put "}";
%if %upcase(&fref) ne _WEBOUT and &stream=Y %then %do; %if %upcase(&fref) ne _WEBOUT and &stream=Y %then %do;