1
0
mirror of https://github.com/sasjs/core.git synced 2026-01-07 17:40:05 +00:00

Compare commits

..

25 Commits

Author SHA1 Message Date
Allan Bowe
2536e299ad Merge pull request #112 from sasjs/mf_isint
feat: new macro to determine if a macro variable value is an integer …
2021-12-13 18:55:41 +00:00
munja
8b5238230b feat: new macro to determine if a macro variable value is an integer - mf_isint (and associated test) 2021-12-13 18:36:22 +00:00
munja
0ce7efee3e fix: declaring msg variable prior to set statement in mp_copyfolder.sas 2021-12-13 11:26:17 +00:00
munja
357677e45c chore: switching pre-commit hook to bash from shell 2021-12-13 09:14:29 +00:00
Allan Bowe
a4a332926e Merge pull request #111 from sasjs/issue110
feat: adding varinitchk=ERROR to mp_init.  Closes #110.
2021-12-13 08:44:12 +00:00
munja
0a29006914 chore: running all.sas 2021-12-13 01:08:37 +00:00
munja
0885bad859 fix: updating the tests following varinitchk=error enablement. Removing the word 'error' from documenttion. 2021-12-13 01:07:54 +00:00
munja
42bd1750bd feat: adding varinitchk=ERROR to mp_init. Closes #110. Also updated the comments / documentation 2021-12-12 22:57:25 +00:00
Allan Bowe
58784b2f28 Merge pull request #109 from sasjs/issue108
fix: rebuilding mp_searchdata into data step to avoid SQL warning.
2021-12-11 14:34:08 +00:00
munja
e125aace9b fix: rebuilding mp_searchdata into data step to avoid SQL warning. Added test and refreshed docs. Closes #108 2021-12-10 15:56:39 +00:00
Allan Bowe
b02a9e3478 Merge pull request #107 from sasjs/hashdclogic
fix: adding iftrue switch to mp_hashdataset
2021-12-08 20:59:35 +00:00
munja
3d3c76c836 fix: adding iftrue switch to mp_hashdataset 2021-12-08 20:58:59 +00:00
Allan Bowe
e039f1cd83 Merge pull request #106 from sasjs/hook
chore: adding hook script to prevent accidental commits to master
2021-12-07 17:44:00 +00:00
munja
6c8165601d chore: adding hook script to prevent accidental commits to master 2021-12-07 16:58:23 +00:00
munja
596624c1bf chore: remove the word ERROR from the code 2021-12-06 15:14:46 +00:00
munja
4aca34d4c2 fix: missing end comment in mp_init.sas 2021-12-06 14:58:13 +00:00
Allan Bowe
858b378658 Merge pull request #104 from sasjs/moremacros
feat: new macros
2021-12-06 14:37:35 +00:00
munja
8af41a8cc3 feat: v1 of the mp_makedata() macro. Will create random data from an empty table definition. Many more features to be added 2021-12-06 00:36:31 +00:00
munja
b13c33cbde fix: using byte codes to tidy up apostrophe logic, and new param for mf_getquotedstr macro 2021-12-06 00:12:36 +00:00
munja
6906f025d6 fix: updating tests that are failing due to changes / new options 2021-12-05 23:56:36 +00:00
munja
8b355b8056 feat: new macros (mp_reseterror and mp_init), new datetime format added to mp_getcols, and stub prepared for mp_makedata 2021-12-05 23:35:25 +00:00
munja
8938553588 chore: updating header in mp_getpk.sas 2021-12-05 19:08:06 +00:00
munja
14852f3647 chore: updating README headers and licence date 2021-12-05 17:24:15 +00:00
munja
b55e91784d chore: updating all.sas 2021-12-05 16:57:29 +00:00
munja
fc14aaa37f chore: updating docs 2021-12-05 16:57:16 +00:00
41 changed files with 792 additions and 156 deletions

View File

@@ -1,2 +1,11 @@
#!/bin/sh #!/bin/bash
sasjs lint sasjs lint
# Avoid commits to the master branch
BRANCH=`git rev-parse --abbrev-ref HEAD`
if [[ "$BRANCH" =~ ^(master|main|develop)$ ]]; then
echo "You are on branch $BRANCH. Are you sure you want to commit to this branch?"
echo "If so, commit with -n to bypass the pre-commit hook."
exit 1
fi

View File

@@ -1,4 +1,4 @@
Copyright 2020 (Allan Bowe) Copyright 2021 (Allan Bowe)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View File

@@ -1,7 +1,7 @@
# Macro Core # Macro Core
[![npm package][npm-image]][npm-url] [![npm package][npm-image]][npm-url]
[![Github Workflow][githubworkflow-image]][githubworkflow-url] [![Github Workflow][githubworkflow-image]][githubworkflow-url]
[![npm](https://img.shields.io/npm/dt/@sasjs/core)]() ![npm](https://img.shields.io/npm/dt/@sasjs/core)
![Snyk Vulnerabilities for npm package](https://img.shields.io/snyk/vulnerabilities/npm/@sasjs/core) ![Snyk Vulnerabilities for npm package](https://img.shields.io/snyk/vulnerabilities/npm/@sasjs/core)
[![License](https://img.shields.io/apm/l/atomic-design-ui.svg)](/LICENSE) [![License](https://img.shields.io/apm/l/atomic-design-ui.svg)](/LICENSE)
![GitHub top language](https://img.shields.io/github/languages/top/sasjs/core) ![GitHub top language](https://img.shields.io/github/languages/top/sasjs/core)
@@ -31,21 +31,21 @@ Documentation: https://core.sasjs.io
## Components ## Components
### **base** library (SAS9/Viya) ### BASE library (SAS9/Viya)
- OS independent - OS independent
- Not metadata aware - Not metadata aware
- No X command - No X command
- Prefixes: _mf_, _mp_ - Prefixes: _mf_, _mp_
#### **fcmp** library (SAS9/Viya) #### FCMP library (SAS9/Viya)
- Function and macro names are identical, except for special cases - Function and macro names are identical, except for special cases
- Prefixes: _mcf_ - Prefixes: _mcf_
The fcmp macros are used to generate fcmp functions, and can be used with or The fcmp macros are used to generate fcmp functions, and can be used with or
without the `proc fcmp` wrapper. without the `proc fcmp` wrapper.
### **meta** library (SAS9 only) ### META library (SAS9 only)
Macros used in SAS EBI, which connect to the metadata server. Macros used in SAS EBI, which connect to the metadata server.
@@ -54,7 +54,7 @@ Macros used in SAS EBI, which connect to the metadata server.
- No X command - No X command
- Prefixes: _mm_ - Prefixes: _mm_
### **server** library (@sasjs/server only) ### SERVER library (@sasjs/server only)
These macros are used for building applications using [@sasjs/server](https://server.sasjs.io) - an open source REST API for Desktop SAS. These macros are used for building applications using [@sasjs/server](https://server.sasjs.io) - an open source REST API for Desktop SAS.
- OS independent - OS independent
@@ -62,7 +62,7 @@ These macros are used for building applications using [@sasjs/server](https://se
- No X command - No X command
- Prefixes: _ms_ - Prefixes: _ms_
### **viya** library (Viya only) ### VIYA library (Viya only)
Macros used for interfacing with SAS Viya. Macros used for interfacing with SAS Viya.
@@ -70,14 +70,14 @@ Macros used for interfacing with SAS Viya.
- No X command - No X command
- Prefixes: _mv_, _mvf_ - Prefixes: _mv_, _mvf_
### **metax** library (SAS9 only) ### METAX library (SAS9 only)
- OS specific - OS specific
- Metadata aware - Metadata aware
- X command enabled - X command enabled
- Prefixes: _mmw_,_mmu_,_mmx_ - Prefixes: _mmw_,_mmu_,_mmx_
### **lua** library ### LUA library
Wait - this is a macro library - what is LUA doing here? Well, it is a little known fact that you CAN run LUA within a SAS Macro. It has to be written to a text file with a `.lua` extension, from where you can `%include` it. So, without using the `proc lua` wrapper. Wait - this is a macro library - what is LUA doing here? Well, it is a little known fact that you CAN run LUA within a SAS Macro. It has to be written to a text file with a `.lua` extension, from where you can `%include` it. So, without using the `proc lua` wrapper.

388
all.sas
View File

@@ -79,7 +79,7 @@ options noquotelenmax;
Run without arguments to see a list of detectable features. Run without arguments to see a list of detectable features.
Note - this list is based on known versions of SAS rather than Note - this list is based on known versions of SAS rather than
actual feature detection, as that is tricky / impossible to do actual feature detection, as that is tricky / impossible to do
without generating errors in most cases. without generating errs in most cases.
%put %mf_existfeature(PROCLUA); %put %mf_existfeature(PROCLUA);
@@ -362,7 +362,7 @@ https://github.com/yabwon/SAS_PACKAGES/blob/main/packages/baseplus.md#functionex
@param attr full list in [documentation]( @param attr full list in [documentation](
https://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000147794.htm) https://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000147794.htm)
@return output returns result of the attrc value supplied, or -1 and log @return output returns result of the attrc value supplied, or -1 and log
message if error. message if err.
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe
@@ -395,7 +395,7 @@ https://github.com/yabwon/SAS_PACKAGES/blob/main/packages/baseplus.md#functionex
@param attr Common values are NLOBS and NVARS, full list in [documentation]( @param attr Common values are NLOBS and NVARS, full list in [documentation](
http://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000212040.htm) http://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000212040.htm)
@return output returns result of the attrn value supplied, or -1 and log @return output returns result of the attrn value supplied, or -1 and log
message if error. message if err.
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe
@@ -626,12 +626,12 @@ https://github.com/yabwon/SAS_PACKAGES/blob/main/packages/baseplus.md#functionex
for: for:
> "these","words","are","double","quoted" > "these","words","are","double","quoted"
@param in_str the unquoted, spaced delimited string to transform @param [in] in_str The unquoted, spaced delimited string to transform
@param dlm= the delimeter to be applied to the output (default comma) @param [in] dlm= The delimeter to be applied to the output (default comma)
@param indlm= the delimeter used for the input (default is space) @param [in] indlm= (,) The delimeter used for the input (default is space)
@param quote= the quote mark to apply (S=Single, D=Double). If any other value @param [in] quote= (S) The quote mark to apply (S=Single, D=Double, N=None).
than uppercase S or D is supplied, then that value will be used as the If any other value than uppercase S or D is supplied, then that value will
quoting character. be used as the quoting character.
@return output returns a string with the newly quoted / delimited output. @return output returns a string with the newly quoted / delimited output.
@version 9.2 @version 9.2
@@ -641,9 +641,10 @@ https://github.com/yabwon/SAS_PACKAGES/blob/main/packages/baseplus.md#functionex
%macro mf_getquotedstr(IN_STR,DLM=%str(,),QUOTE=S,indlm=%str( ) %macro mf_getquotedstr(IN_STR,DLM=%str(,),QUOTE=S,indlm=%str( )
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%if &quote=S %then %let quote=%str(%'); /* credit Rowland Hale - byte34 is double quote, 39 is single quote */
%else %if &quote=D %then %let quote=%str(%"); %if &quote=S %then %let quote=%qsysfunc(byte(39));
%else %let quote=%str(); %else %if &quote=D %then %let quote=%qsysfunc(byte(34));
%else %if &quote=N %then %let quote=;
%local i item buffer; %local i item buffer;
%let i=1; %let i=1;
%do %while (%qscan(&IN_STR,&i,%str(&indlm)) ne %str() ) ; %do %while (%qscan(&IN_STR,&i,%str(&indlm)) ne %str() ) ;
@@ -1339,6 +1340,38 @@ Usage:
&is_directory &is_directory
%mend mf_isdir;/** %mend mf_isdir;/**
@file
@brief Returns 1 if the variable contains only digits 0-9, else 0
@details Note that numerics containing any punctuation (including decimals
or exponents) will be flagged zero.
If you'd like support for this, then do raise an issue (or even better, a
pull request!)
Usage:
%put %mf_isint(1) returns 1;
%put %mf_isint(1.1) returns 0;
%put %mf_isint(%str(1,1)) returns 0;
@param [in] arg input value to check
@version 9.2
**/
%macro mf_isint(arg
)/*/STORE SOURCE*/;
/* remove minus sign if exists */
%local val;
%if "%substr(%str(&arg),1,1)"="-" %then %let val=%substr(%str(&arg),2);
%else %let val=&arg;
/* check remaining chars */
%if %sysfunc(findc(%str(&val),,kd)) %then %do;0%end;
%else %do;1%end;
%mend mf_isint;/**
@file @file
@brief Returns physical location of various SAS items @brief Returns physical location of various SAS items
@details Returns location of the PlatformObjectFramework tools @details Returns location of the PlatformObjectFramework tools
@@ -1420,7 +1453,7 @@ Usage:
%end; %end;
/* /*
Now create the directory. Complain loudly of any errors. Now create the directory. Complain loudly of any errs.
*/ */
%let dname = %sysfunc(dcreate(&child, &parent)); %let dname = %sysfunc(dcreate(&child, &parent));
@@ -1467,7 +1500,7 @@ Usage:
@param libds library.dataset @param libds library.dataset
@return output returns result of the attrn value supplied, or log message @return output returns result of the attrn value supplied, or log message
if error. if err.
@version 9.2 @version 9.2
@@ -2789,6 +2822,8 @@ run;
/* create folders and copy content */ /* create folders and copy content */
data _null_; data _null_;
length msg $200;
call missing(msg);
set work.&tempds; set work.&tempds;
if _n_ = 1 then dpos+sum(length(directory),2); if _n_ = 1 then dpos+sum(length(directory),2);
filepath2="&target/"!!substr(filepath,dpos); filepath2="&target/"!!substr(filepath,dpos);
@@ -2798,9 +2833,9 @@ run;
rc1=filename(fref1,filepath,'disk','recfm=n'); rc1=filename(fref1,filepath,'disk','recfm=n');
rc2=filename(fref2,filepath2,'disk','recfm=n'); rc2=filename(fref2,filepath2,'disk','recfm=n');
if fcopy(fref1,fref2) ne 0 then do; if fcopy(fref1,fref2) ne 0 then do;
sysmsg=sysmsg(); msg=sysmsg();
putlog "%str(ERR)OR: Unable to copy " filepath " to " filepath2; putlog "%str(ERR)OR: Unable to copy " filepath " to " filepath2;
putlog sysmg=; putlog msg=;
end; end;
end; end;
rc=filename(fref1); rc=filename(fref1);
@@ -3875,17 +3910,23 @@ run;
%mend mp_ds2csv;/** %mend mp_ds2csv;/**
@file @file
@brief Converts every value in a dataset to it's formatted value @brief Converts every value in a dataset to formatted value
@details Converts every value to it's formatted value. All variables will @details Converts every value to it's formatted value. All variables will
become character, and will be in the same order. become character, and will be in the same order as the original dataset.
Lengths will be adjusted according to the format lengths, where applicable.
Usage: Usage:
%mp_ds2fmtds(sashelp.cars,work.cars) %mp_ds2fmtds(sashelp.cars,work.cars)
%mp_ds2fmtds(sashelp.vallopt,vw_vallopt)
@param [in] libds The library.dataset to be converted @param [in] libds The library.dataset to be converted
@param [out] outds The dataset to create. @param [out] outds The dataset to create.
<h4> SAS Macros </h4>
@li mf_existds.sas
<h4> Related Macros <h4> <h4> Related Macros <h4>
@li mp_jsonout.sas @li mp_jsonout.sas
@@ -3897,8 +3938,9 @@ run;
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
/* validations */ /* validations */
%if not %sysfunc(exist(&libds)) %then %do;
%put %str(WARN)ING: &libds does not exist; %if not %mf_existds(libds=&libds) %then %do;
%put %str(WARN)ING: &libds does not exist as either a VIEW or DATASET;
%return; %return;
%end; %end;
%if %index(&libds,.)=0 %then %let libds=WORK.&libds; %if %index(&libds,.)=0 %then %let libds=WORK.&libds;
@@ -4088,6 +4130,7 @@ data _null_;
if _n_>&maxobs then stop; if _n_>&maxobs then stop;
%end; %end;
length _____str $32767; length _____str $32767;
call missing(_____str);
format _numeric_ best.; format _numeric_ best.;
format _character_ ; format _character_ ;
%local i comma var vtype vfmt; %local i comma var vtype vfmt;
@@ -4488,7 +4531,7 @@ filename &outref temp;
@param [in] targetds The target dataset against which to verify the query @param [in] targetds The target dataset against which to verify the query
@param [out] abort= (YES) If YES will call mp_abort.sas on any exceptions @param [out] abort= (YES) If YES will call mp_abort.sas on any exceptions
@param [out] outds= (work.mp_filtervalidate) Output dataset containing the @param [out] outds= (work.mp_filtervalidate) Output dataset containing the
error / warning message, if one exists. If this table contains any rows, err / warning message, if one exists. If this table contains any rows,
there are problems! there are problems!
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@@ -4612,7 +4655,7 @@ data &outds(keep=name type length varnum format label ddtype);
else if formatd=0 then format=cats(format2,formatl,'.'); else if formatd=0 then format=cats(format2,formatl,'.');
else format=cats(format2,formatl,'.',formatd); else format=cats(format2,formatl,'.',formatd);
type='N'; type='N';
if format=:'DATETIME' then ddtype='DATETIME'; if format=:'DATETIME' or format=:'E8601DT' then ddtype='DATETIME';
else if format=:'DATE' or format=:'DDMMYY' or format=:'MMDDYY' else if format=:'DATE' or format=:'DDMMYY' or format=:'MMDDYY'
or format=:'YYMMDD' or format=:'E8601DA' or format=:'B8601DA' or format=:'YYMMDD' or format=:'E8601DA' or format=:'B8601DA'
or format=:'MONYY' or format=:'MONYY'
@@ -4673,17 +4716,17 @@ run;
%let vw=%mf_getuniquename(prefix=mp_getconstraints_vw_); %let vw=%mf_getuniquename(prefix=mp_getconstraints_vw_);
data &vw /view=&vw; data &vw /view=&vw;
set sashelp.vcncolu; set sashelp.vcncolu;
where TABLE_CATALOG="&lib"; where table_catalog="&lib";
/* use retain approach to reset the constraint order with each constraint */ /* use retain approach to reset the constraint order with each constraint */
length tmp $1000; length tmp $1000;
retain tmp; retain tmp;
drop tmp; drop tmp;
if tmp ne catx('|',libref,table_name,constraint_type,constraint_name) then do; if tmp ne catx('|',table_catalog,table_name,constraint_name) then do;
constraint_order=1; constraint_order=1;
end; end;
else constraint_order+1; else constraint_order+1;
tmp=catx('|',libref, table_name, constraint_type,constraint_name); tmp=catx('|',table_catalog, table_name,constraint_name);
run; run;
/* must use SQL as proc datasets does not support length changes */ /* must use SQL as proc datasets does not support length changes */
@@ -5193,7 +5236,7 @@ run;
%let curds=%scan(&dsnlist,&x); %let curds=%scan(&dsnlist,&x);
data _null_; data _null_;
file &fref mod; file &fref mod;
length nm lab $1024 typ $20; length lab $1024 typ $20;
set &colinfo (where=(upcase(memname)="&curds")) end=last; set &colinfo (where=(upcase(memname)="&curds")) end=last;
if _n_=1 then do; if _n_=1 then do;
@@ -5548,6 +5591,10 @@ create table &outds (rename=(
Returns: Returns:
|libref:$8.|dsn:$32.|memtype:$8.|dbms_memtype:$32.|typemem:$8.|memlabel:$256.|nvar:best.|compress:$8.|pk_fields:$512.|
|---|---|---|---|---|---|---|---|---|
|WORK|EXAMPLE|DATA| |DATA| |4|NO|TX_FROM DD_TYPE DD_SOURCE|
@param [in] lib The libref to examine @param [in] lib The libref to examine
@param [in] ds= (0) Select the dataset to examine, else use 0 for all tables @param [in] ds= (0) Select the dataset to examine, else use 0 for all tables
@@ -5562,6 +5609,7 @@ create table &outds (rename=(
<h4> Related Macros </h4> <h4> Related Macros </h4>
@li mp_getpk.test.sas @li mp_getpk.test.sas
@li mp_guesspk.sas
@version 9.3 @version 9.3
@author Macro People Ltd @author Macro People Ltd
@@ -5830,34 +5878,39 @@ create table &outds as
%mend mp_gsubfile; %mend mp_gsubfile;
/** /**
@file mp_guesspk.sas @file
@brief Guess the primary key of a table @brief Guess the primary key of a table
@details Tries to guess the primary key of a table based on the following logic: @details Tries to guess the primary key of a table based on the following
logic:
* Columns with nulls are ignored * Columns with nulls are ignored
* Return only column combinations that provide unique results * Return only column combinations that provide unique results
* Start from one column, then move out to include composite keys of 2 to 6 columns * Start from one column, then move out to composite keys of 2 to 6 columns
The library of the target should be assigned before using this macro. The library of the target should be assigned before using this macro.
Usage: Usage:
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; filename mc url
"https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc; %inc mc;
%mp_guesspk(sashelp.class,outds=classpks) %mp_guesspk(sashelp.class,outds=classpks)
@param baseds The dataset to analyse @param baseds The dataset to analyse
@param outds= The output dataset to contain the possible PKs @param outds= The output dataset to contain the possible PKs
@param max_guesses= The total number of possible primary keys to generate. A @param max_guesses= (3) The total number of possible primary keys to generate.
table is likely to have multiple unlikely PKs, so no need to list them all. Default=3. A table may have multiple unlikely PKs, so no need to list them all.
@param min_rows= The minimum number of rows a table should have in order to try @param min_rows= (5) The minimum number of rows a table should have in order
and guess the PK. Default=5. to try and guess the PK.
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_getvarlist.sas @li mf_getvarlist.sas
@li mf_getuniquename.sas @li mf_getuniquename.sas
@li mf_nobs.sas @li mf_nobs.sas
<h4> Related Macros </h4>
@li mp_getpk.sas
@version 9.3 @version 9.3
@author Allan Bowe @author Allan Bowe
@@ -6027,7 +6080,8 @@ create table &outds as
%let lev4=%scan(&posspks,&l); %let lev4=%scan(&posspks,&l);
%if &lev1 ne &lev4 and &lev2 ne &lev4 and &lev3 ne &lev4 %then %do; %if &lev1 ne &lev4 and &lev2 ne &lev4 and &lev3 ne &lev4 %then %do;
/* check for four level uniqueness */ /* check for four level uniqueness */
proc sort data=&pkds(keep=&lev1 &lev2 &lev3 &lev4) out=&tmpds noduprec; proc sort data=&pkds(keep=&lev1 &lev2 &lev3 &lev4)
out=&tmpds noduprec;
by _all_; by _all_;
run; run;
%if %mf_nobs(&tmpds)=&rows %then %do; %if %mf_nobs(&tmpds)=&rows %then %do;
@@ -6064,7 +6118,8 @@ create table &outds as
%let lev5=%scan(&posspks,&m); %let lev5=%scan(&posspks,&m);
%if &lev1 ne &lev5 & &lev2 ne &lev5 & &lev3 ne &lev5 & &lev4 ne &lev5 %then %do; %if &lev1 ne &lev5 & &lev2 ne &lev5 & &lev3 ne &lev5 & &lev4 ne &lev5 %then %do;
/* check for four level uniqueness */ /* check for four level uniqueness */
proc sort data=&pkds(keep=&lev1 &lev2 &lev3 &lev4 &lev5) out=&tmpds noduprec; proc sort data=&pkds(keep=&lev1 &lev2 &lev3 &lev4 &lev5)
out=&tmpds noduprec;
by _all_; by _all_;
run; run;
%if %mf_nobs(&tmpds)=&rows %then %do; %if %mf_nobs(&tmpds)=&rows %then %do;
@@ -6113,7 +6168,8 @@ create table &outds as
run; run;
%if %mf_nobs(&tmpds)=&rows %then %do; %if %mf_nobs(&tmpds)=&rows %then %do;
proc sql; proc sql;
insert into &outds values("&lev1 &lev2 &lev3 &lev4 &lev5 &lev6"); insert into &outds
values("&lev1 &lev2 &lev3 &lev4 &lev5 &lev6");
%if %mf_nobs(&outds) ge &max_guesses %then %do; %if %mf_nobs(&outds) ge &max_guesses %then %do;
%put &sysmacroname: Max PKs reached at Level 6 for &baseds; %put &sysmacroname: Max PKs reached at Level 6 for &baseds;
%return; %return;
@@ -6155,6 +6211,7 @@ create table &outds as
@param [in] libds dataset to hash @param [in] libds dataset to hash
@param [in] salt= Provide a salt (could be, for instance, the dataset name) @param [in] salt= Provide a salt (could be, for instance, the dataset name)
@param [in] iftrue= A condition under which the macro should be executed.
@param [out] outds= (work.mf_hashdataset) The output dataset to create. This @param [out] outds= (work.mf_hashdataset) The output dataset to create. This
will contain one column (hashkey) with one observation (a hex32. will contain one column (hashkey) with one observation (a hex32.
representation of the input hash) representation of the input hash)
@@ -6169,10 +6226,14 @@ create table &outds as
%macro mp_hashdataset( %macro mp_hashdataset(
libds, libds,
outds=, outds=,
salt= salt=,
iftrue=%str(1=1)
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%if not(%eval(%unquote(&iftrue))) %then %return;
%if %mf_getattrn(&libds,NLOBS)=0 %then %do; %if %mf_getattrn(&libds,NLOBS)=0 %then %do;
%put %str(WARN)ING: Dataset &libds is empty;, or is not a dataset; %put %str(WARN)ING: Dataset &libds is empty, or is not a dataset;
%end; %end;
%else %if %mf_getattrn(&libds,NLOBS)<0 %then %do; %else %if %mf_getattrn(&libds,NLOBS)<0 %then %do;
%put %str(ERR)OR: Dataset &libds is not a dataset; %put %str(ERR)OR: Dataset &libds is not a dataset;
@@ -6312,6 +6373,70 @@ run;
filename &tempref clear; filename &tempref clear;
%mend mp_include;/** %mend mp_include;/**
@file
@brief Initialise session with useful settings and variables
@details Implements a "strict" set of SAS options for use in defensive
programming. Highly recommended, if you want your code to run on some
other machine.
This macro is recommended to be compiled and invoked in the `initProgram`
for SASjs [Jobs](https://cli.sasjs.io/sasjsconfig.html#jobConfig_initProgram
), [Services](
https://cli.sasjs.io/sasjsconfig.html#serviceConfig_initProgram) and [Tests]
(https://cli.sasjs.io/sasjsconfig.html#testConfig_initProgram).
For non SASjs projects, you could invoke in the autoexec, or in your own
solution initialisation macro.
If you have a good idea for another useful option, setting, or global
variable - feel free to [raise an issue](
https://github.com/sasjs/core/issues/new)!
All global variables are prefixed with "SASJS" (unless modified with the
prefix parameter).
@param [in] prefix= (SASJS) The prefix to apply to the global macro variables
@version 9.2
@author Allan Bowe
**/
%macro mp_init(prefix=
)/*/STORE SOURCE*/;
%global
&prefix._INIT_NUM /* initialisation time as numeric */
&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */
;
%if %eval(&&&prefix._INIT_NUM>0) %then %return; /* only run once */
data _null_;
dttm=datetime();
call symputx("&prefix._init_num",dttm);
call symputx("&prefix._init_dttm",put(dttm,E8601DT26.6));
run;
options
autocorrect /* disallow mis-spelled procedure names */
compress=CHAR /* default is none so ensure we have something! */
datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */
%str(err)orcheck=STRICT /* catch errs in libname/filename statements */
fmterr /* ensure err when a format cannot be found */
mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */
missing=. /* changing this can cause hard to detect errs */
noquotelenmax /* avoid warnings for long strings */
noreplace /* avoid overwriting permanent datasets */
ps=max /* reduce log size slightly */
validmemname=COMPATIBLE /* avoid special characters etc in table names */
validvarname=V7 /* avoid special characters etc in variable names */
varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */
varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */
;
%mend mp_init;/**
@file mp_jsonout.sas @file mp_jsonout.sas
@brief Writes JSON in SASjs format to a fileref @brief Writes JSON in SASjs format to a fileref
@details PROC JSON is faster but will produce errs like the ones below if @details PROC JSON is faster but will produce errs like the ones below if
@@ -6376,7 +6501,7 @@ filename &tempref clear;
%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 '{"START_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '"'; 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;
@@ -6938,7 +7063,7 @@ run;
%let abortme=1; %let abortme=1;
%end; %end;
/* catch errors - mp_abort must be called outside of a logic block */ /* catch errs - mp_abort must be called outside of a logic block */
%mp_abort(iftrue=(&abortme=1), %mp_abort(iftrue=(&abortme=1),
msg=%superq(msg), msg=%superq(msg),
mac=&sysmacroname mac=&sysmacroname
@@ -7049,6 +7174,92 @@ lock &libds clear;
) )
%mend mp_lockfilecheck;/** %mend mp_lockfilecheck;/**
@file
@brief Create sample data based on the structure of an empty table
@details Many SAS projects involve sensitive datasets. One way to _ensure_
the data is anonymised, is never to receive it in the first place! Often
consultants are provided with empty tables, and expected to create complex
ETL flows.
This macro can help by taking an empty table, and populating it with data
according to the variable types and formats.
TODO:
@li Respect PKs
@li Respect NOT NULLs
@li Consider dates, datetimes, times, integers etc
Usage:
proc sql;
create table work.example(
TX_FROM float format=datetime19.,
DD_TYPE char(16),
DD_SOURCE char(2048),
DD_SHORTDESC char(256),
constraint pk primary key(tx_from, dd_type,dd_source),
constraint nnn not null(DD_SHORTDESC)
);
%mp_makedata(work.example)
@param [in] libds The empty table in which to create data
@param [out] obs= (500) The number of records to create.
<h4> SAS Macros </h4>
@li mf_getuniquename.sas
@li mf_getvarlen.sas
@li mf_nobs.sas
@li mp_getcols.sas
@li mp_getpk.sas
@version 9.2
@author Allan Bowe
**/
%macro mp_makedata(libds
,obs=500
)/*/STORE SOURCE*/;
%local ds1 c1 n1 i col charvars numvars;
%if %mf_nobs(&libds)>0 %then %do;
%put &sysmacroname: &libds has data, it will not be recreated;
%return;
%end;
%local ds1 c1 n1;
%let ds1=%mf_getuniquename(prefix=mp_makedata);
%let c1=%mf_getuniquename(prefix=mp_makedatacol);
%let n1=%mf_getuniquename(prefix=mp_makedatacol);
data &ds1;
if 0 then set &libds;
do _n_=1 to &obs;
&c1=repeat(uuidgen(),10);
&n1=ranuni(1)*5000000;
drop &c1 &n1;
%let charvars=%mf_getvarlist(&libds,typefilter=C);
%do i=1 %to %sysfunc(countw(&charvars));
%let col=%scan(&charvars,&i);
&col=subpad(&c1,1,%mf_getvarlen(&libds,&col));
%end;
%let numvars=%mf_getvarlist(&libds,typefilter=N);
%do i=1 %to %sysfunc(countw(&numvars));
%let col=%scan(&numvars,&i);
&col=&n1;
%end;
output;
end;
run;
proc append base=&libds data=&ds1;
run;
proc sql;
drop table &ds1;
%mend mp_makedata;/**
@file @file
@brief Create a Markdown Table from a dataset @brief Create a Markdown Table from a dataset
@details A markdown table is a simple table representation for use in @details A markdown table is a simple table representation for use in
@@ -7364,6 +7575,32 @@ insert into &outds select distinct * from &append_ds;
%mend mp_recursivejoin; %mend mp_recursivejoin;
/** /**
@file
@brief Reset when an err condition occurs
@details When building apps, sometimes an operation must be attempted that
can cause an err condition. There is no try catch in SAS! So the err state
must be caught and reset.
This macro attempts to do that reset.
@version 9.2
@author Allan Bowe
**/
%macro mp_reseterror(
)/*/STORE SOURCE*/;
options obs=max replace nosyntaxcheck;
%let syscc=0;
%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;
data _null_;
rc=stpsrvset('program error', 0);
run;
%end;
%mend mp_reseterror;/**
@file @file
@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 -
@@ -7558,22 +7795,28 @@ drop table &tempds;
Usage: Usage:
%mp_searchdata(lib=sashelp, string=Jan) %mp_searchdata(lib=sashelp, string=Jan)
%mp_searchdata(lib=sashelp, numval=1) %mp_searchdata(lib=sashelp, ds=bird, numval=1)
%mp_searchdata(lib=sashelp, ds=class, string=l,outobs=5)
Outputs zero or more tables to an MPSEARCH library with specific records. Outputs zero or more tables to an MPSEARCH library with specific records.
@param lib= the libref to search (should be already assigned) @param [in] lib= The libref to search (should be already assigned)
@param ds= the dataset to search (leave blank to search entire library) @param [in] ds= The dataset to search (leave blank to search entire library)
@param string= the string value to search @param [in] string= String value to search (case sensitive, can be partial)
@param numval= the numeric value to search (must be exact) @param [in] numval= Numeric value to search (must be exact)
@param outloc= the directory in which to create the output datasets with @param [out] outloc= (0) Optionally specify the directory in which to
matching rows. Will default to a subfolder in the WORK library. create the the output datasets with matching rows. By default it will
@param outobs= set to a positive integer to restrict the number of write them to a temporary subdirectory within the WORK folder.
@param [out] outlib= (MPSEARCH) Assign a different libref to the output
library containing the matching datasets / records
@param [in] outobs= set to a positive integer to restrict the number of
observations observations
@param filter_text= add a (valid) filter clause to further filter the results @param [in] filter_text= (1=1) Add a (valid) filter clause to further filter
the results.
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_getuniquename.sas
@li mf_getvarlist.sas @li mf_getvarlist.sas
@li mf_getvartype.sas @li mf_getvartype.sas
@li mf_mkdir.sas @li mf_mkdir.sas
@@ -7583,11 +7826,12 @@ drop table &tempds;
@author Allan Bowe @author Allan Bowe
**/ **/
%macro mp_searchdata(lib=sashelp %macro mp_searchdata(lib=
,ds= ,ds=
,string= /* the query will use a contains (?) operator */ ,string= /* the query will use a contains (?) operator */
,numval= /* numeric must match exactly */ ,numval= /* numeric must match exactly */
,outloc=%sysfunc(pathname(work))/mpsearch ,outloc=0
,outlib=MPSEARCH
,outobs=-1 ,outobs=-1
,filter_text=%str(1=1) ,filter_text=%str(1=1)
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
@@ -7604,8 +7848,12 @@ drop table &tempds;
%if &string = %then %let type=N; %if &string = %then %let type=N;
%else %let type=C; %else %let type=C;
%if "&outloc"="0" %then %do;
%let outloc=%sysfunc(pathname(work))/%mf_getuniquename();
%end;
%mf_mkdir(&outloc) %mf_mkdir(&outloc)
libname mpsearch "&outloc"; libname &outlib "&outloc";
/* get the list of tables in the library */ /* get the list of tables in the library */
proc sql noprint; proc sql noprint;
@@ -7617,11 +7865,6 @@ select distinct memname into: table_list separated by ' '
%end; %end;
; ;
/* check that we have something to check */ /* check that we have something to check */
proc sql
%if &outobs>-1 %then %do;
outobs=&outobs
%end;
;
%if %length(&table_list)=0 %then %put library &lib contains no tables!; %if %length(&table_list)=0 %then %put library &lib contains no tables!;
/* loop through each table */ /* loop through each table */
%else %do table_num=1 %to %sysfunc(countw(&table_list,%str( ))); %else %do table_num=1 %to %sysfunc(countw(&table_list,%str( )));
@@ -7632,10 +7875,10 @@ proc sql
%end; %end;
%else %do; %else %do;
%let check_tm=%sysfunc(datetime()); %let check_tm=%sysfunc(datetime());
/* build sql statement */ /* prep input */
create table mpsearch.&table as select * from &lib..&table data &outlib..&table;
where %unquote(&filter_text) and set &lib..&table;
(0 where %unquote(&filter_text) and ( 0
/* loop through columns */ /* loop through columns */
%do colnum=1 %to %sysfunc(countw(&vars,%str( ))); %do colnum=1 %to %sysfunc(countw(&vars,%str( )));
%let col=%scan(&vars,&colnum,%str( )); %let col=%scan(&vars,&colnum,%str( ));
@@ -7650,14 +7893,19 @@ proc sql
%end; %end;
%end; %end;
); );
%if &outobs>-1 %then %do;
if _n_ > &outobs then stop;
%end;
run;
%put Search query for &table took %put Search query for &table took
%sysevalf(%sysfunc(datetime())-&check_tm) seconds; %sysevalf(%sysfunc(datetime())-&check_tm) seconds;
%if &sqlrc ne 0 %then %do; %if &syscc ne 0 %then %do;
%put %str(WAR)NING: SQLRC=&sqlrc when processing &table; %put %str(ERR)ROR: SYSCC=&syscc when processing &lib..&table;
%return; %return;
%end; %end;
%if %mf_nobs(mpsearch.&table)=0 %then %do; %if %mf_nobs(&outlib..&table)=0 %then %do;
drop table mpsearch.&table; proc sql;
drop table &outlib..&table;
%end; %end;
%end; %end;
%end; %end;
@@ -7810,7 +8058,7 @@ run;
%let tempvw=%mf_getuniquename(prefix=&sysmacroname); %let tempvw=%mf_getuniquename(prefix=&sysmacroname);
proc sql; proc sql;
create view work.&tempvw as select * from &lib..&ds create view work.&tempvw as select * from &lib..&ds
order by %mf_getquotedstr(&sortkey,quote=%str()); order by %mf_getquotedstr(&sortkey,quote=N);
/* append sorted data */ /* append sorted data */
proc append base=&lib..&tempds2 data=work.&tempvw; proc append base=&lib..&tempds2 data=work.&tempvw;
@@ -10917,7 +11165,7 @@ data _null_;
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 ''{"START_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''"''; '; 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; ';
@@ -15999,7 +16247,7 @@ data _null_;
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 ''{"START_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''"''; '; 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; ';
@@ -17246,6 +17494,7 @@ options noquotelenmax;
libname &libref2 JSON fileref=&fname2; libname &libref2 JSON fileref=&fname2;
data &outds; data &outds;
length id $36 name $128 uri $64 type $32 description $256; length id $36 name $128 uri $64 type $32 description $256;
if _n_=1 then call missing (of _all_);
set &libref2..items; set &libref2..items;
run; run;
filename &fname2 clear; filename &fname2 clear;
@@ -19330,6 +19579,7 @@ run;
data &outds; data &outds;
format _program uri $128. state $32. stateDetails $32. timestamp datetime19. format _program uri $128. state $32. stateDetails $32. timestamp datetime19.
jobparams $32767.; jobparams $32767.;
call missing (of _all_);
stop; stop;
run; run;

View File

@@ -5,7 +5,7 @@
Run without arguments to see a list of detectable features. Run without arguments to see a list of detectable features.
Note - this list is based on known versions of SAS rather than Note - this list is based on known versions of SAS rather than
actual feature detection, as that is tricky / impossible to do actual feature detection, as that is tricky / impossible to do
without generating errors in most cases. without generating errs in most cases.
%put %mf_existfeature(PROCLUA); %put %mf_existfeature(PROCLUA);

View File

@@ -10,7 +10,7 @@
@param attr full list in [documentation]( @param attr full list in [documentation](
https://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000147794.htm) https://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000147794.htm)
@return output returns result of the attrc value supplied, or -1 and log @return output returns result of the attrc value supplied, or -1 and log
message if error. message if err.
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe

View File

@@ -10,7 +10,7 @@
@param attr Common values are NLOBS and NVARS, full list in [documentation]( @param attr Common values are NLOBS and NVARS, full list in [documentation](
http://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000212040.htm) http://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000212040.htm)
@return output returns result of the attrn value supplied, or -1 and log @return output returns result of the attrn value supplied, or -1 and log
message if error. message if err.
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe

View File

@@ -15,12 +15,12 @@
for: for:
> "these","words","are","double","quoted" > "these","words","are","double","quoted"
@param in_str the unquoted, spaced delimited string to transform @param [in] in_str The unquoted, spaced delimited string to transform
@param dlm= the delimeter to be applied to the output (default comma) @param [in] dlm= The delimeter to be applied to the output (default comma)
@param indlm= the delimeter used for the input (default is space) @param [in] indlm= (,) The delimeter used for the input (default is space)
@param quote= the quote mark to apply (S=Single, D=Double). If any other value @param [in] quote= (S) The quote mark to apply (S=Single, D=Double, N=None).
than uppercase S or D is supplied, then that value will be used as the If any other value than uppercase S or D is supplied, then that value will
quoting character. be used as the quoting character.
@return output returns a string with the newly quoted / delimited output. @return output returns a string with the newly quoted / delimited output.
@version 9.2 @version 9.2
@@ -30,9 +30,10 @@
%macro mf_getquotedstr(IN_STR,DLM=%str(,),QUOTE=S,indlm=%str( ) %macro mf_getquotedstr(IN_STR,DLM=%str(,),QUOTE=S,indlm=%str( )
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%if &quote=S %then %let quote=%str(%'); /* credit Rowland Hale - byte34 is double quote, 39 is single quote */
%else %if &quote=D %then %let quote=%str(%"); %if &quote=S %then %let quote=%qsysfunc(byte(39));
%else %let quote=%str(); %else %if &quote=D %then %let quote=%qsysfunc(byte(34));
%else %if &quote=N %then %let quote=;
%local i item buffer; %local i item buffer;
%let i=1; %let i=1;
%do %while (%qscan(&IN_STR,&i,%str(&indlm)) ne %str() ) ; %do %while (%qscan(&IN_STR,&i,%str(&indlm)) ne %str() ) ;

33
base/mf_isint.sas Normal file
View File

@@ -0,0 +1,33 @@
/**
@file
@brief Returns 1 if the variable contains only digits 0-9, else 0
@details Note that numerics containing any punctuation (including decimals
or exponents) will be flagged zero.
If you'd like support for this, then do raise an issue (or even better, a
pull request!)
Usage:
%put %mf_isint(1) returns 1;
%put %mf_isint(1.1) returns 0;
%put %mf_isint(%str(1,1)) returns 0;
@param [in] arg input value to check
@version 9.2
**/
%macro mf_isint(arg
)/*/STORE SOURCE*/;
/* remove minus sign if exists */
%local val;
%if "%substr(%str(&arg),1,1)"="-" %then %let val=%substr(%str(&arg),2);
%else %let val=&arg;
/* check remaining chars */
%if %sysfunc(findc(%str(&val),,kd)) %then %do;0%end;
%else %do;1%end;
%mend mf_isint;

View File

@@ -51,7 +51,7 @@ Usage:
%end; %end;
/* /*
Now create the directory. Complain loudly of any errors. Now create the directory. Complain loudly of any errs.
*/ */
%let dname = %sysfunc(dcreate(&child, &parent)); %let dname = %sysfunc(dcreate(&child, &parent));

View File

@@ -12,7 +12,7 @@
@param libds library.dataset @param libds library.dataset
@return output returns result of the attrn value supplied, or log message @return output returns result of the attrn value supplied, or log message
if error. if err.
@version 9.2 @version 9.2

View File

@@ -54,6 +54,8 @@
/* create folders and copy content */ /* create folders and copy content */
data _null_; data _null_;
length msg $200;
call missing(msg);
set work.&tempds; set work.&tempds;
if _n_ = 1 then dpos+sum(length(directory),2); if _n_ = 1 then dpos+sum(length(directory),2);
filepath2="&target/"!!substr(filepath,dpos); filepath2="&target/"!!substr(filepath,dpos);
@@ -63,9 +65,9 @@
rc1=filename(fref1,filepath,'disk','recfm=n'); rc1=filename(fref1,filepath,'disk','recfm=n');
rc2=filename(fref2,filepath2,'disk','recfm=n'); rc2=filename(fref2,filepath2,'disk','recfm=n');
if fcopy(fref1,fref2) ne 0 then do; if fcopy(fref1,fref2) ne 0 then do;
sysmsg=sysmsg(); msg=sysmsg();
putlog "%str(ERR)OR: Unable to copy " filepath " to " filepath2; putlog "%str(ERR)OR: Unable to copy " filepath " to " filepath2;
putlog sysmg=; putlog msg=;
end; end;
end; end;
rc=filename(fref1); rc=filename(fref1);

View File

@@ -1,16 +1,22 @@
/** /**
@file @file
@brief Converts every value in a dataset to it's formatted value @brief Converts every value in a dataset to formatted value
@details Converts every value to it's formatted value. All variables will @details Converts every value to it's formatted value. All variables will
become character, and will be in the same order. become character, and will be in the same order as the original dataset.
Lengths will be adjusted according to the format lengths, where applicable.
Usage: Usage:
%mp_ds2fmtds(sashelp.cars,work.cars) %mp_ds2fmtds(sashelp.cars,work.cars)
%mp_ds2fmtds(sashelp.vallopt,vw_vallopt)
@param [in] libds The library.dataset to be converted @param [in] libds The library.dataset to be converted
@param [out] outds The dataset to create. @param [out] outds The dataset to create.
<h4> SAS Macros </h4>
@li mf_existds.sas
<h4> Related Macros <h4> <h4> Related Macros <h4>
@li mp_jsonout.sas @li mp_jsonout.sas
@@ -22,8 +28,9 @@
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
/* validations */ /* validations */
%if not %sysfunc(exist(&libds)) %then %do;
%put %str(WARN)ING: &libds does not exist; %if not %mf_existds(libds=&libds) %then %do;
%put %str(WARN)ING: &libds does not exist as either a VIEW or DATASET;
%return; %return;
%end; %end;
%if %index(&libds,.)=0 %then %let libds=WORK.&libds; %if %index(&libds,.)=0 %then %let libds=WORK.&libds;

View File

@@ -116,6 +116,7 @@ data _null_;
if _n_>&maxobs then stop; if _n_>&maxobs then stop;
%end; %end;
length _____str $32767; length _____str $32767;
call missing(_____str);
format _numeric_ best.; format _numeric_ best.;
format _character_ ; format _character_ ;
%local i comma var vtype vfmt; %local i comma var vtype vfmt;

View File

@@ -33,7 +33,7 @@
@param [in] targetds The target dataset against which to verify the query @param [in] targetds The target dataset against which to verify the query
@param [out] abort= (YES) If YES will call mp_abort.sas on any exceptions @param [out] abort= (YES) If YES will call mp_abort.sas on any exceptions
@param [out] outds= (work.mp_filtervalidate) Output dataset containing the @param [out] outds= (work.mp_filtervalidate) Output dataset containing the
error / warning message, if one exists. If this table contains any rows, err / warning message, if one exists. If this table contains any rows,
there are problems! there are problems!
<h4> SAS Macros </h4> <h4> SAS Macros </h4>

View File

@@ -51,7 +51,7 @@ data &outds(keep=name type length varnum format label ddtype);
else if formatd=0 then format=cats(format2,formatl,'.'); else if formatd=0 then format=cats(format2,formatl,'.');
else format=cats(format2,formatl,'.',formatd); else format=cats(format2,formatl,'.',formatd);
type='N'; type='N';
if format=:'DATETIME' then ddtype='DATETIME'; if format=:'DATETIME' or format=:'E8601DT' then ddtype='DATETIME';
else if format=:'DATE' or format=:'DDMMYY' or format=:'MMDDYY' else if format=:'DATE' or format=:'DDMMYY' or format=:'MMDDYY'
or format=:'YYMMDD' or format=:'E8601DA' or format=:'B8601DA' or format=:'YYMMDD' or format=:'E8601DA' or format=:'B8601DA'
or format=:'MONYY' or format=:'MONYY'

View File

@@ -48,17 +48,17 @@
%let vw=%mf_getuniquename(prefix=mp_getconstraints_vw_); %let vw=%mf_getuniquename(prefix=mp_getconstraints_vw_);
data &vw /view=&vw; data &vw /view=&vw;
set sashelp.vcncolu; set sashelp.vcncolu;
where TABLE_CATALOG="&lib"; where table_catalog="&lib";
/* use retain approach to reset the constraint order with each constraint */ /* use retain approach to reset the constraint order with each constraint */
length tmp $1000; length tmp $1000;
retain tmp; retain tmp;
drop tmp; drop tmp;
if tmp ne catx('|',libref,table_name,constraint_type,constraint_name) then do; if tmp ne catx('|',table_catalog,table_name,constraint_name) then do;
constraint_order=1; constraint_order=1;
end; end;
else constraint_order+1; else constraint_order+1;
tmp=catx('|',libref, table_name, constraint_type,constraint_name); tmp=catx('|',table_catalog, table_name,constraint_name);
run; run;
/* must use SQL as proc datasets does not support length changes */ /* must use SQL as proc datasets does not support length changes */

View File

@@ -139,7 +139,7 @@ run;
%let curds=%scan(&dsnlist,&x); %let curds=%scan(&dsnlist,&x);
data _null_; data _null_;
file &fref mod; file &fref mod;
length nm lab $1024 typ $20; length lab $1024 typ $20;
set &colinfo (where=(upcase(memname)="&curds")) end=last; set &colinfo (where=(upcase(memname)="&curds")) end=last;
if _n_=1 then do; if _n_=1 then do;

View File

@@ -23,6 +23,10 @@
Returns: Returns:
|libref:$8.|dsn:$32.|memtype:$8.|dbms_memtype:$32.|typemem:$8.|memlabel:$256.|nvar:best.|compress:$8.|pk_fields:$512.|
|---|---|---|---|---|---|---|---|---|
|WORK|EXAMPLE|DATA| |DATA| |4|NO|TX_FROM DD_TYPE DD_SOURCE|
@param [in] lib The libref to examine @param [in] lib The libref to examine
@param [in] ds= (0) Select the dataset to examine, else use 0 for all tables @param [in] ds= (0) Select the dataset to examine, else use 0 for all tables
@@ -37,6 +41,7 @@
<h4> Related Macros </h4> <h4> Related Macros </h4>
@li mp_getpk.test.sas @li mp_getpk.test.sas
@li mp_guesspk.sas
@version 9.3 @version 9.3
@author Macro People Ltd @author Macro People Ltd

View File

@@ -1,32 +1,37 @@
/** /**
@file mp_guesspk.sas @file
@brief Guess the primary key of a table @brief Guess the primary key of a table
@details Tries to guess the primary key of a table based on the following logic: @details Tries to guess the primary key of a table based on the following
logic:
* Columns with nulls are ignored * Columns with nulls are ignored
* Return only column combinations that provide unique results * Return only column combinations that provide unique results
* Start from one column, then move out to include composite keys of 2 to 6 columns * Start from one column, then move out to composite keys of 2 to 6 columns
The library of the target should be assigned before using this macro. The library of the target should be assigned before using this macro.
Usage: Usage:
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; filename mc url
"https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc; %inc mc;
%mp_guesspk(sashelp.class,outds=classpks) %mp_guesspk(sashelp.class,outds=classpks)
@param baseds The dataset to analyse @param baseds The dataset to analyse
@param outds= The output dataset to contain the possible PKs @param outds= The output dataset to contain the possible PKs
@param max_guesses= The total number of possible primary keys to generate. A @param max_guesses= (3) The total number of possible primary keys to generate.
table is likely to have multiple unlikely PKs, so no need to list them all. Default=3. A table may have multiple unlikely PKs, so no need to list them all.
@param min_rows= The minimum number of rows a table should have in order to try @param min_rows= (5) The minimum number of rows a table should have in order
and guess the PK. Default=5. to try and guess the PK.
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_getvarlist.sas @li mf_getvarlist.sas
@li mf_getuniquename.sas @li mf_getuniquename.sas
@li mf_nobs.sas @li mf_nobs.sas
<h4> Related Macros </h4>
@li mp_getpk.sas
@version 9.3 @version 9.3
@author Allan Bowe @author Allan Bowe
@@ -196,7 +201,8 @@
%let lev4=%scan(&posspks,&l); %let lev4=%scan(&posspks,&l);
%if &lev1 ne &lev4 and &lev2 ne &lev4 and &lev3 ne &lev4 %then %do; %if &lev1 ne &lev4 and &lev2 ne &lev4 and &lev3 ne &lev4 %then %do;
/* check for four level uniqueness */ /* check for four level uniqueness */
proc sort data=&pkds(keep=&lev1 &lev2 &lev3 &lev4) out=&tmpds noduprec; proc sort data=&pkds(keep=&lev1 &lev2 &lev3 &lev4)
out=&tmpds noduprec;
by _all_; by _all_;
run; run;
%if %mf_nobs(&tmpds)=&rows %then %do; %if %mf_nobs(&tmpds)=&rows %then %do;
@@ -233,7 +239,8 @@
%let lev5=%scan(&posspks,&m); %let lev5=%scan(&posspks,&m);
%if &lev1 ne &lev5 & &lev2 ne &lev5 & &lev3 ne &lev5 & &lev4 ne &lev5 %then %do; %if &lev1 ne &lev5 & &lev2 ne &lev5 & &lev3 ne &lev5 & &lev4 ne &lev5 %then %do;
/* check for four level uniqueness */ /* check for four level uniqueness */
proc sort data=&pkds(keep=&lev1 &lev2 &lev3 &lev4 &lev5) out=&tmpds noduprec; proc sort data=&pkds(keep=&lev1 &lev2 &lev3 &lev4 &lev5)
out=&tmpds noduprec;
by _all_; by _all_;
run; run;
%if %mf_nobs(&tmpds)=&rows %then %do; %if %mf_nobs(&tmpds)=&rows %then %do;
@@ -282,7 +289,8 @@
run; run;
%if %mf_nobs(&tmpds)=&rows %then %do; %if %mf_nobs(&tmpds)=&rows %then %do;
proc sql; proc sql;
insert into &outds values("&lev1 &lev2 &lev3 &lev4 &lev5 &lev6"); insert into &outds
values("&lev1 &lev2 &lev3 &lev4 &lev5 &lev6");
%if %mf_nobs(&outds) ge &max_guesses %then %do; %if %mf_nobs(&outds) ge &max_guesses %then %do;
%put &sysmacroname: Max PKs reached at Level 6 for &baseds; %put &sysmacroname: Max PKs reached at Level 6 for &baseds;
%return; %return;

View File

@@ -21,6 +21,7 @@
@param [in] libds dataset to hash @param [in] libds dataset to hash
@param [in] salt= Provide a salt (could be, for instance, the dataset name) @param [in] salt= Provide a salt (could be, for instance, the dataset name)
@param [in] iftrue= A condition under which the macro should be executed.
@param [out] outds= (work.mf_hashdataset) The output dataset to create. This @param [out] outds= (work.mf_hashdataset) The output dataset to create. This
will contain one column (hashkey) with one observation (a hex32. will contain one column (hashkey) with one observation (a hex32.
representation of the input hash) representation of the input hash)
@@ -35,10 +36,14 @@
%macro mp_hashdataset( %macro mp_hashdataset(
libds, libds,
outds=, outds=,
salt= salt=,
iftrue=%str(1=1)
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%if not(%eval(%unquote(&iftrue))) %then %return;
%if %mf_getattrn(&libds,NLOBS)=0 %then %do; %if %mf_getattrn(&libds,NLOBS)=0 %then %do;
%put %str(WARN)ING: Dataset &libds is empty;, or is not a dataset; %put %str(WARN)ING: Dataset &libds is empty, or is not a dataset;
%end; %end;
%else %if %mf_getattrn(&libds,NLOBS)<0 %then %do; %else %if %mf_getattrn(&libds,NLOBS)<0 %then %do;
%put %str(ERR)OR: Dataset &libds is not a dataset; %put %str(ERR)OR: Dataset &libds is not a dataset;

65
base/mp_init.sas Normal file
View File

@@ -0,0 +1,65 @@
/**
@file
@brief Initialise session with useful settings and variables
@details Implements a "strict" set of SAS options for use in defensive
programming. Highly recommended, if you want your code to run on some
other machine.
This macro is recommended to be compiled and invoked in the `initProgram`
for SASjs [Jobs](https://cli.sasjs.io/sasjsconfig.html#jobConfig_initProgram
), [Services](
https://cli.sasjs.io/sasjsconfig.html#serviceConfig_initProgram) and [Tests]
(https://cli.sasjs.io/sasjsconfig.html#testConfig_initProgram).
For non SASjs projects, you could invoke in the autoexec, or in your own
solution initialisation macro.
If you have a good idea for another useful option, setting, or global
variable - feel free to [raise an issue](
https://github.com/sasjs/core/issues/new)!
All global variables are prefixed with "SASJS" (unless modified with the
prefix parameter).
@param [in] prefix= (SASJS) The prefix to apply to the global macro variables
@version 9.2
@author Allan Bowe
**/
%macro mp_init(prefix=
)/*/STORE SOURCE*/;
%global
&prefix._INIT_NUM /* initialisation time as numeric */
&prefix._INIT_DTTM /* initialisation time in E8601DT26.6 format */
;
%if %eval(&&&prefix._INIT_NUM>0) %then %return; /* only run once */
data _null_;
dttm=datetime();
call symputx("&prefix._init_num",dttm);
call symputx("&prefix._init_dttm",put(dttm,E8601DT26.6));
run;
options
autocorrect /* disallow mis-spelled procedure names */
compress=CHAR /* default is none so ensure we have something! */
datastmtchk=ALLKEYWORDS /* protection from overwriting input datasets */
%str(err)orcheck=STRICT /* catch errs in libname/filename statements */
fmterr /* ensure err when a format cannot be found */
mergenoby=%str(ERR)OR /* throw err when a merge has no BY variables */
missing=. /* changing this can cause hard to detect errs */
noquotelenmax /* avoid warnings for long strings */
noreplace /* avoid overwriting permanent datasets */
ps=max /* reduce log size slightly */
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;

View File

@@ -63,7 +63,7 @@
%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 '{"START_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '"'; 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;

View File

@@ -239,7 +239,7 @@ run;
%let abortme=1; %let abortme=1;
%end; %end;
/* catch errors - mp_abort must be called outside of a logic block */ /* catch errs - mp_abort must be called outside of a logic block */
%mp_abort(iftrue=(&abortme=1), %mp_abort(iftrue=(&abortme=1),
msg=%superq(msg), msg=%superq(msg),
mac=&sysmacroname mac=&sysmacroname

87
base/mp_makedata.sas Normal file
View File

@@ -0,0 +1,87 @@
/**
@file
@brief Create sample data based on the structure of an empty table
@details Many SAS projects involve sensitive datasets. One way to _ensure_
the data is anonymised, is never to receive it in the first place! Often
consultants are provided with empty tables, and expected to create complex
ETL flows.
This macro can help by taking an empty table, and populating it with data
according to the variable types and formats.
TODO:
@li Respect PKs
@li Respect NOT NULLs
@li Consider dates, datetimes, times, integers etc
Usage:
proc sql;
create table work.example(
TX_FROM float format=datetime19.,
DD_TYPE char(16),
DD_SOURCE char(2048),
DD_SHORTDESC char(256),
constraint pk primary key(tx_from, dd_type,dd_source),
constraint nnn not null(DD_SHORTDESC)
);
%mp_makedata(work.example)
@param [in] libds The empty table in which to create data
@param [out] obs= (500) The number of records to create.
<h4> SAS Macros </h4>
@li mf_getuniquename.sas
@li mf_getvarlen.sas
@li mf_nobs.sas
@li mp_getcols.sas
@li mp_getpk.sas
@version 9.2
@author Allan Bowe
**/
%macro mp_makedata(libds
,obs=500
)/*/STORE SOURCE*/;
%local ds1 c1 n1 i col charvars numvars;
%if %mf_nobs(&libds)>0 %then %do;
%put &sysmacroname: &libds has data, it will not be recreated;
%return;
%end;
%local ds1 c1 n1;
%let ds1=%mf_getuniquename(prefix=mp_makedata);
%let c1=%mf_getuniquename(prefix=mp_makedatacol);
%let n1=%mf_getuniquename(prefix=mp_makedatacol);
data &ds1;
if 0 then set &libds;
do _n_=1 to &obs;
&c1=repeat(uuidgen(),10);
&n1=ranuni(1)*5000000;
drop &c1 &n1;
%let charvars=%mf_getvarlist(&libds,typefilter=C);
%do i=1 %to %sysfunc(countw(&charvars));
%let col=%scan(&charvars,&i);
&col=subpad(&c1,1,%mf_getvarlen(&libds,&col));
%end;
%let numvars=%mf_getvarlist(&libds,typefilter=N);
%do i=1 %to %sysfunc(countw(&numvars));
%let col=%scan(&numvars,&i);
&col=&n1;
%end;
output;
end;
run;
proc append base=&libds data=&ds1;
run;
proc sql;
drop table &ds1;
%mend mp_makedata;

27
base/mp_reseterror.sas Normal file
View File

@@ -0,0 +1,27 @@
/**
@file
@brief Reset when an err condition occurs
@details When building apps, sometimes an operation must be attempted that
can cause an err condition. There is no try catch in SAS! So the err state
must be caught and reset.
This macro attempts to do that reset.
@version 9.2
@author Allan Bowe
**/
%macro mp_reseterror(
)/*/STORE SOURCE*/;
options obs=max replace nosyntaxcheck;
%let syscc=0;
%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;
data _null_;
rc=stpsrvset('program error', 0);
run;
%end;
%mend mp_reseterror;

View File

@@ -11,22 +11,28 @@
Usage: Usage:
%mp_searchdata(lib=sashelp, string=Jan) %mp_searchdata(lib=sashelp, string=Jan)
%mp_searchdata(lib=sashelp, numval=1) %mp_searchdata(lib=sashelp, ds=bird, numval=1)
%mp_searchdata(lib=sashelp, ds=class, string=l,outobs=5)
Outputs zero or more tables to an MPSEARCH library with specific records. Outputs zero or more tables to an MPSEARCH library with specific records.
@param lib= the libref to search (should be already assigned) @param [in] lib= The libref to search (should be already assigned)
@param ds= the dataset to search (leave blank to search entire library) @param [in] ds= The dataset to search (leave blank to search entire library)
@param string= the string value to search @param [in] string= String value to search (case sensitive, can be partial)
@param numval= the numeric value to search (must be exact) @param [in] numval= Numeric value to search (must be exact)
@param outloc= the directory in which to create the output datasets with @param [out] outloc= (0) Optionally specify the directory in which to
matching rows. Will default to a subfolder in the WORK library. create the the output datasets with matching rows. By default it will
@param outobs= set to a positive integer to restrict the number of write them to a temporary subdirectory within the WORK folder.
@param [out] outlib= (MPSEARCH) Assign a different libref to the output
library containing the matching datasets / records
@param [in] outobs= set to a positive integer to restrict the number of
observations observations
@param filter_text= add a (valid) filter clause to further filter the results @param [in] filter_text= (1=1) Add a (valid) filter clause to further filter
the results.
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_getuniquename.sas
@li mf_getvarlist.sas @li mf_getvarlist.sas
@li mf_getvartype.sas @li mf_getvartype.sas
@li mf_mkdir.sas @li mf_mkdir.sas
@@ -36,11 +42,12 @@
@author Allan Bowe @author Allan Bowe
**/ **/
%macro mp_searchdata(lib=sashelp %macro mp_searchdata(lib=
,ds= ,ds=
,string= /* the query will use a contains (?) operator */ ,string= /* the query will use a contains (?) operator */
,numval= /* numeric must match exactly */ ,numval= /* numeric must match exactly */
,outloc=%sysfunc(pathname(work))/mpsearch ,outloc=0
,outlib=MPSEARCH
,outobs=-1 ,outobs=-1
,filter_text=%str(1=1) ,filter_text=%str(1=1)
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
@@ -57,8 +64,12 @@
%if &string = %then %let type=N; %if &string = %then %let type=N;
%else %let type=C; %else %let type=C;
%if "&outloc"="0" %then %do;
%let outloc=%sysfunc(pathname(work))/%mf_getuniquename();
%end;
%mf_mkdir(&outloc) %mf_mkdir(&outloc)
libname mpsearch "&outloc"; libname &outlib "&outloc";
/* get the list of tables in the library */ /* get the list of tables in the library */
proc sql noprint; proc sql noprint;
@@ -70,11 +81,6 @@ select distinct memname into: table_list separated by ' '
%end; %end;
; ;
/* check that we have something to check */ /* check that we have something to check */
proc sql
%if &outobs>-1 %then %do;
outobs=&outobs
%end;
;
%if %length(&table_list)=0 %then %put library &lib contains no tables!; %if %length(&table_list)=0 %then %put library &lib contains no tables!;
/* loop through each table */ /* loop through each table */
%else %do table_num=1 %to %sysfunc(countw(&table_list,%str( ))); %else %do table_num=1 %to %sysfunc(countw(&table_list,%str( )));
@@ -85,10 +91,10 @@ proc sql
%end; %end;
%else %do; %else %do;
%let check_tm=%sysfunc(datetime()); %let check_tm=%sysfunc(datetime());
/* build sql statement */ /* prep input */
create table mpsearch.&table as select * from &lib..&table data &outlib..&table;
where %unquote(&filter_text) and set &lib..&table;
(0 where %unquote(&filter_text) and ( 0
/* loop through columns */ /* loop through columns */
%do colnum=1 %to %sysfunc(countw(&vars,%str( ))); %do colnum=1 %to %sysfunc(countw(&vars,%str( )));
%let col=%scan(&vars,&colnum,%str( )); %let col=%scan(&vars,&colnum,%str( ));
@@ -103,14 +109,19 @@ proc sql
%end; %end;
%end; %end;
); );
%if &outobs>-1 %then %do;
if _n_ > &outobs then stop;
%end;
run;
%put Search query for &table took %put Search query for &table took
%sysevalf(%sysfunc(datetime())-&check_tm) seconds; %sysevalf(%sysfunc(datetime())-&check_tm) seconds;
%if &sqlrc ne 0 %then %do; %if &syscc ne 0 %then %do;
%put %str(WAR)NING: SQLRC=&sqlrc when processing &table; %put %str(ERR)ROR: SYSCC=&syscc when processing &lib..&table;
%return; %return;
%end; %end;
%if %mf_nobs(mpsearch.&table)=0 %then %do; %if %mf_nobs(&outlib..&table)=0 %then %do;
drop table mpsearch.&table; proc sql;
drop table &outlib..&table;
%end; %end;
%end; %end;
%end; %end;

View File

@@ -89,7 +89,7 @@ run;
%let tempvw=%mf_getuniquename(prefix=&sysmacroname); %let tempvw=%mf_getuniquename(prefix=&sysmacroname);
proc sql; proc sql;
create view work.&tempvw as select * from &lib..&ds create view work.&tempvw as select * from &lib..&ds
order by %mf_getquotedstr(&sortkey,quote=%str()); order by %mf_getquotedstr(&sortkey,quote=N);
/* append sorted data */ /* append sorted data */
proc append base=&lib..&tempds2 data=work.&tempvw; proc append base=&lib..&tempds2 data=work.&tempvw;

View File

@@ -95,7 +95,7 @@ data _null_;
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 ''{"START_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''"''; '; 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; ';

View File

@@ -0,0 +1,33 @@
/**
@file
@brief Testing mf_isint macro
<h4> SAS Macros </h4>
@li mf_isint.sas
@li mp_assert.sas
**/
%mp_assert(
iftrue=(
"%mf_isint(1)"="1"
),
desc=Checking basic mf_isint(1),
outds=work.test_results
)
%mp_assert(
iftrue=(
"%mf_isint(1.1)"="0"
),
desc=Checking basic mf_isint(1.1),
outds=work.test_results
)
%mp_assert(
iftrue=(
"%mf_isint(-1)"="1"
),
desc=Checking mf_isint(-1),
outds=work.test_results
)

View File

@@ -15,10 +15,12 @@ filename inc temp;
data _null_; data _null_;
set work.test; set work.test;
file inc; file inc;
line=cats('%mp_ds2fmtds(sashelp.',memname,',',memname,')'); libds=cats('sashelp.',memname);
if exist(libds) then line=cats('%mp_ds2fmtds(',libds,',',memname,')');
put line; put line;
run; run;
options obs=50;
%inc inc; %inc inc;
%mp_assert( %mp_assert(

View File

@@ -0,0 +1,24 @@
/**
@file
@brief Testing mp_gsubfile.sas macro
<h4> SAS Macros </h4>
@li mp_init.sas
@li mp_assert.sas
**/
/**
* Test 1 - mp_init.sas actually already ran as part of testinit
* So lets test to make sure it will not run again
*/
%let initial_value=&sasjs_init_num;
%mp_init();
%mp_assert(
iftrue=("&initial_value"="&sasjs_init_num"),
desc=Check that mp_init() did not run twice,
outds=work.test_results
)

View File

@@ -11,9 +11,10 @@
**/ **/
/* grab 20 datasets from SASHELP */ /* grab 20 datasets from SASHELP */
%let path=%sysfunc(pathname(work)); %let work=%sysfunc(pathname(work));
%let path=&work/new;
%mf_mkdir(&path) %mf_mkdir(&path)
libname sashlp "&path"; libname sashlp "&work";
proc sql noprint; proc sql noprint;
create table members as create table members as
select distinct lowcase(memname) as memname select distinct lowcase(memname) as memname
@@ -31,6 +32,7 @@ run;
%mp_lib2inserts(sashlp, schema=work, outref=tempref,maxobs=50) %mp_lib2inserts(sashlp, schema=work, outref=tempref,maxobs=50)
/* check if it actually runs */ /* check if it actually runs */
libname sashlp "&path";
options source2; options source2;
%inc tempref; %inc tempref;

View File

@@ -5,6 +5,7 @@
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mp_lockfilecheck.sas @li mp_lockfilecheck.sas
@li mp_assert.sas @li mp_assert.sas
@li mp_reseterror.sas
**/ **/
@@ -29,6 +30,8 @@ data work.test; a=1;run;
%mp_lockfilecheck(sashelp.class) %mp_lockfilecheck(sashelp.class)
%mp_reseterror()
%mp_assert( %mp_assert(
iftrue=(&success=1), iftrue=(&success=1),
desc=Checking sashelp table cannot be locked, desc=Checking sashelp table cannot be locked,

View File

@@ -0,0 +1,23 @@
/**
@file
@brief Testing mp_reseterror macro
<h4> SAS Macros </h4>
@li mp_assert.sas
@li mp_reseterror.sas
**/
/* cause an error */
lock sashelp.class;
/* recover ? */
%mp_reseterror()
%mp_assert(
iftrue=(&syscc=0),
desc=Checking error condition was fixed,
outds=work.test_results
)

View File

@@ -0,0 +1,29 @@
/**
@file
@brief Testing mp_searchdata.sas
<h4> SAS Macros </h4>
@li mp_searchdata.sas
@li mp_assert.sas
**/
/** Test 1 - generic useage */
%mp_searchdata(lib=sashelp, ds=class, string=a)
%mp_assert(
iftrue=(&syscc=0),
desc=No errors in regular usage,
outds=work.test_results
)
/** Test 2 - with obs issue */
%mp_searchdata(lib=sashelp, ds=class, string=l,outobs=5)
%mp_assert(
iftrue=("&SYSWARNINGTEXT" = ""),
desc=Ensuring WARN status is clean,
outds=work.test_results
)

View File

@@ -2,11 +2,17 @@
@file @file
@brief init file for tests @brief init file for tests
<h4> SAS Macros </h4>
@li mp_init.sas
**/ **/
/* location in metadata or SAS Drive for temporary files */ /* location in metadata or SAS Drive for temporary files */
%let mcTestAppLoc=/Public/temp/macrocore; %let mcTestAppLoc=/Public/temp/macrocore;
/* set defaults */
%mp_init()
%macro loglevel(); %macro loglevel();
%if &_debug=2477 %then %do; %if &_debug=2477 %then %do;
options mprint; options mprint;

View File

@@ -33,6 +33,7 @@ run;
%put TEST1: checking web service code; %put TEST1: checking web service code;
data work.test_results; data work.test_results;
length test_description $256 test_result $4 test_comments $256; length test_description $256 test_result $4 test_comments $256;
if _n_=1 then call missing (of _all_);
infile compare end=eof; infile compare end=eof;
input; input;
if eof then do; if eof then do;

View File

@@ -243,7 +243,7 @@ data _null_;
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 ''{"START_DTTM" : "'' "%sysfunc(datetime(),datetime20.3)" ''"''; '; 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; ';

View File

@@ -123,6 +123,7 @@ options noquotelenmax;
libname &libref2 JSON fileref=&fname2; libname &libref2 JSON fileref=&fname2;
data &outds; data &outds;
length id $36 name $128 uri $64 type $32 description $256; length id $36 name $128 uri $64 type $32 description $256;
if _n_=1 then call missing (of _all_);
set &libref2..items; set &libref2..items;
run; run;
filename &fname2 clear; filename &fname2 clear;

View File

@@ -168,6 +168,7 @@ run;
data &outds; data &outds;
format _program uri $128. state $32. stateDetails $32. timestamp datetime19. format _program uri $128. state $32. stateDetails $32. timestamp datetime19.
jobparams $32767.; jobparams $32767.;
call missing (of _all_);
stop; stop;
run; run;