1
0
mirror of https://github.com/sasjs/core.git synced 2025-12-11 06:24:35 +00:00

Compare commits

...

8 Commits

18 changed files with 763 additions and 128 deletions

249
all.sas
View File

@@ -442,6 +442,9 @@ options noquotelenmax;
@version 9.2
@author Allan Bowe
<h4> Related Macros </h4>
@li mf_getxengine.sas
**/
/** @cond */
@@ -1037,14 +1040,17 @@ options noquotelenmax;
returns:
> List of Variables=Name Sex Age Height Weight
For a seperated list of column values:
%put %mf_getvarlist(sashelp.class,dlm=%str(,),quote=double);
returns:
> "Name","Sex","Age","Height","Weight"
@param libds Two part dataset (or view) reference.
@param dlm= provide a delimiter (eg comma or space) to separate the vars
@param quote= use either DOUBLE or SINGLE to quote the results
@param [in] libds Two part dataset (or view) reference.
@param [in] dlm= ( ) Provide a delimiter (eg comma or space) to separate the
variables
@param [in] quote= (none) use either DOUBLE or SINGLE to quote the results
@version 9.2
@author Allan Bowe
@@ -1189,6 +1195,49 @@ Usage:
/* Return variable type */
&vtype
%mend;/**
@file
@brief Returns the engine type of a SAS fileref
@details Queries sashelp.vextfl to get the xengine value.
Usage:
filename feng temp;
%put %mf_getxengine(feng);
returns:
> TEMP
@param fref The fileref to check
@returns The XENGINE value in sashelp.vextfl or 0 if not found.
@version 9.2
@author Allan Bowe
<h4> Related Macros </h4>
@li mf_getengine.sas
**/
%macro mf_getxengine(fref
)/*/STORE SOURCE*/;
%local dsid engnum rc engine;
%let dsid=%sysfunc(
open(sashelp.vextfl(where=(fileref="%upcase(&fref)")),i)
);
%if (&dsid ^= 0) %then %do;
%let engnum=%sysfunc(varnum(&dsid,XENGINE));
%let rc=%sysfunc(fetch(&dsid));
%let engine=%sysfunc(getvarc(&dsid,&engnum));
%* put &fref. ENGINE is &engine.;
%let rc= %sysfunc(close(&dsid));
%end;
%else %let engine=0;
&engine
%mend;
/**
@file mf_isblank.sas
@brief Checks whether a macro variable is empty (blank)
@details Simply performs:
@@ -3913,6 +3962,80 @@ create table &outds (rename=(
%return;
%end;
%mend;/**
@file
@brief Returns a unique hash for a dataset
@details Ignores metadata attributes, used only to hash values. Compared
datasets must be in the same order.
%mp_hashdataset(sashelp.class,outds=myhash)
data _null_;
set work.myhash;
put hashkey=;
run;
![sas md5 hash dataset log results](https://i.imgur.com/MqF98vk.png)
<h4> SAS Macros </h4>
@li mf_getattrn.sas
@li mf_getuniquename.sas
@li mf_getvarlist.sas
@li mf_getvartype.sas
@param [in] libds dataset to hash
@param [out] outds= (work.mf_hashdataset) The output dataset to create. This
will contain one column (hashkey) with one observation (a hex32.
representation of the input hash)
|hashkey:$32.|
|---|
|28ABC74ABFC45F50794237BA5566E6CA|
@version 9.2
@author Allan Bowe
**/
%macro mp_hashdataset(
libds,
outds=
)/*/STORE SOURCE*/;
%if %mf_getattrn(&libds,NLOBS)=0 %then %do;
%put %str(WARN)ING: Dataset &libds is empty;, or is not a dataset;
%end;
%else %if %mf_getattrn(&libds,NLOBS)<0 %then %do;
%put %str(ERR)OR: Dataset &libds is not a dataset;
%end;
%else %do;
%local keyvar /* roll up the md5 */
prevkeyvar /* retain prev record md5 */
lastvar /* last var in input ds */
varlist var i;
/* avoid naming conflict for hash key vars */
%let keyvar=%mf_getuniquename();
%let prevkeyvar=%mf_getuniquename();
%let lastvar=%mf_getuniquename();
%let varlist=%mf_getvarlist(&libds);
data &outds(rename=(&keyvar=hashkey) keep=&keyvar);
length &prevkeyvar &keyvar $32;
retain &prevkeyvar;
set &libds end=&lastvar;
/* hash should include previous row */
if _n_>1 then &keyvar=put(md5(&prevkeyvar
/* loop every column, hashing every individual value */
%do i=1 %to %sysfunc(countw(&varlist));
%let var=%scan(&varlist,&i,%str( ));
%if %mf_getvartype(&libds,&var)=C %then %do;
!!put(md5(trim(&var)),$hex32.)
%end;
%else %do;
!!put(md5(trim(put(&var*1,binary64.))),$hex32.)
%end;
%end;
),$hex32.);
&prevkeyvar=&keyvar;
if &lastvar then output;
run;
%end;
%mend;/**
@file mp_jsonout.sas
@brief Writes JSON in SASjs format to a fileref
@@ -5374,60 +5497,6 @@ alter table &libds modify &var char(&len);
%mend;
/**
@file
@brief Validates a filter clause
@details Validates a filter to avoid SQL injection. Works by removing string
literals, then ensuring that none of the following characters remain: &,%,;
%mp_setkeyvalue(someindex,22,type=N)
%mp_setkeyvalue(somenewindex,somevalue)
<h4> SAS Macros </h4>
@li mf_existds.sas
@param key Provide a key on which to perform the lookup
@param value Provide a value
@param type= either C or N will populate valc and valn respectively. C is
default.
@param libds= define the target table to hold the parameters
@version 9.2
@author Allan Bowe
@source https://github.com/sasjs/core
**/
%macro mp_setkeyvalue(key,value,type=C,libds=work.mp_setkeyvalue
)/*/STORE SOURCE*/;
%if not (%mf_existds(&libds)) %then %do;
data &libds (index=(key/unique));
length key $64 valc $2048 valn 8 type $1;
call missing(of _all_);
stop;
run;
%end;
proc sql;
delete from &libds
where key=symget('key');
insert into &libds
set key=symget('key')
%if &type=C %then %do;
,valc=symget('value')
,type='C'
%end;
%else %do;
,valn=symgetn('value')
,type='N'
%end;
;
quit;
%mend;/**
@file
@brief Creates a zip file
@details For DIRECTORY usage, will ignore subfolders. For DATASET usage,
@@ -7609,8 +7678,16 @@ data _null_;
put '%else %if &action=OPEN %then %do; ';
put ' /* fix encoding */ ';
put ' OPTIONS NOBOMFILE; ';
put ' ';
put ' /** ';
put ' * check engine type to avoid the below err message: ';
put ' * > Function is only valid for filerefs using the CACHE access method. ';
put ' */ ';
put ' data _null_; ';
put ' rc = stpsrv_header(''Content-type'',"text/html; encoding=utf-8"); ';
put ' set sashelp.vextfl(where=(fileref="_WEBOUT")); ';
put ' if xengine=''STREAM'' then do; ';
put ' rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8"); ';
put ' end; ';
put ' run; ';
put ' ';
put ' /* setup json */ ';
@@ -7624,16 +7701,9 @@ data _null_;
put '%end; ';
put ' ';
put '%else %if &action=ARR or &action=OBJ %then %do; ';
put ' %if &sysver=9.4 %then %do; ';
put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt ';
put ' ,engine=PROCJSON,dbg=%str(&_debug) ';
put ' ) ';
put ' %end; ';
put ' %else %do; ';
put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt ';
put ' ,engine=DATASTEP,dbg=%str(&_debug) ';
put ' ) ';
put ' %end; ';
put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt ';
put ' ,engine=DATASTEP,dbg=%str(&_debug) ';
put ' ) ';
put '%end; ';
put '%else %if &action=CLOSE %then %do; ';
put ' %if %str(&_debug) ge 131 %then %do; ';
@@ -11078,8 +11148,16 @@ run;
%else %if &action=OPEN %then %do;
/* fix encoding */
OPTIONS NOBOMFILE;
/**
* check engine type to avoid the below err message:
* > Function is only valid for filerefs using the CACHE access method.
*/
data _null_;
rc = stpsrv_header('Content-type',"text/html; encoding=utf-8");
set sashelp.vextfl(where=(fileref="_WEBOUT"));
if xengine='STREAM' then do;
rc=stpsrv_header('Content-type',"text/html; encoding=utf-8");
end;
run;
/* setup json */
@@ -11093,16 +11171,9 @@ run;
%end;
%else %if &action=ARR or &action=OBJ %then %do;
%if &sysver=9.4 %then %do;
%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt
,engine=PROCJSON,dbg=%str(&_debug)
)
%end;
%else %do;
%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt
,engine=DATASTEP,dbg=%str(&_debug)
)
%end;
%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt
,engine=DATASTEP,dbg=%str(&_debug)
)
%end;
%else %if &action=CLOSE %then %do;
%if %str(&_debug) ge 131 %then %do;
@@ -11311,8 +11382,8 @@ run;
@param path= The full path of the folder to be created
@param access_token_var= The global macro variable to contain the access token
@param grant_type= valid values are "password" or "authorization_code" (unquoted).
The default is authorization_code.
@param grant_type= (authorization_code) Valid values are "password" or
"authorization_code" (unquoted).
@version VIYA V.03.04
@@ -11341,7 +11412,6 @@ run;
%let &access_token_var=;
%end;
%put &sysmacroname: grant_type=&grant_type;
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
and &grant_type ne sas_services
)
@@ -11387,12 +11457,15 @@ options noquotelenmax;
%local libref1;
%let libref1=%mf_getuniquelibref();
libname &libref1 JSON fileref=&fname1;
%mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 404)
%mp_abort(
iftrue=(
&SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 404
)
,mac=&sysmacroname
,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
)
%if &SYS_PROCHTTP_STATUS_CODE=200 %then %do;
%put &sysmacroname &newpath exists so grab the follow on link ;
%*put &sysmacroname &newpath exists so grab the follow on link ;
data _null_;
set &libref1..links;
if rel='createChild' then
@@ -11524,7 +11597,6 @@ https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5p
%let oauth_bearer=oauth_bearer=sas_services;
%let &access_token_var=;
%end;
%put &sysmacroname: grant_type=&grant_type;
/* initial validation checking */
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
@@ -11843,7 +11915,6 @@ https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5p
%let oauth_bearer=oauth_bearer=sas_services;
%let &access_token_var=;
%end;
%put &sysmacroname: grant_type=&grant_type;
/* initial validation checking */
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
@@ -12627,7 +12698,7 @@ libname &libref1a clear;
%let oauth_bearer=oauth_bearer=sas_services;
%let &access_token_var=;
%end;
%put &sysmacroname: grant_type=&grant_type;
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
and &grant_type ne sas_services
)
@@ -12769,7 +12840,7 @@ libname &libref1a clear;
%let oauth_bearer=oauth_bearer=sas_services;
%let &access_token_var=;
%end;
%put &sysmacroname: grant_type=&grant_type;
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
and &grant_type ne sas_services
)
@@ -13195,7 +13266,7 @@ libname &libref1 clear;
%let oauth_bearer=oauth_bearer=sas_services;
%let &access_token_var=;
%end;
%put &sysmacroname: grant_type=&grant_type;
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
and &grant_type ne sas_services
)

View File

@@ -22,6 +22,9 @@
@version 9.2
@author Allan Bowe
<h4> Related Macros </h4>
@li mf_getxengine.sas
**/
/** @cond */

View File

@@ -10,14 +10,17 @@
returns:
> List of Variables=Name Sex Age Height Weight
For a seperated list of column values:
%put %mf_getvarlist(sashelp.class,dlm=%str(,),quote=double);
returns:
> "Name","Sex","Age","Height","Weight"
@param libds Two part dataset (or view) reference.
@param dlm= provide a delimiter (eg comma or space) to separate the vars
@param quote= use either DOUBLE or SINGLE to quote the results
@param [in] libds Two part dataset (or view) reference.
@param [in] dlm= ( ) Provide a delimiter (eg comma or space) to separate the
variables
@param [in] quote= (none) use either DOUBLE or SINGLE to quote the results
@version 9.2
@author Allan Bowe

43
base/mf_getxengine.sas Normal file
View File

@@ -0,0 +1,43 @@
/**
@file
@brief Returns the engine type of a SAS fileref
@details Queries sashelp.vextfl to get the xengine value.
Usage:
filename feng temp;
%put %mf_getxengine(feng);
returns:
> TEMP
@param fref The fileref to check
@returns The XENGINE value in sashelp.vextfl or 0 if not found.
@version 9.2
@author Allan Bowe
<h4> Related Macros </h4>
@li mf_getengine.sas
**/
%macro mf_getxengine(fref
)/*/STORE SOURCE*/;
%local dsid engnum rc engine;
%let dsid=%sysfunc(
open(sashelp.vextfl(where=(fileref="%upcase(&fref)")),i)
);
%if (&dsid ^= 0) %then %do;
%let engnum=%sysfunc(varnum(&dsid,XENGINE));
%let rc=%sysfunc(fetch(&dsid));
%let engine=%sysfunc(getvarc(&dsid,&engnum));
%* put &fref. ENGINE is &engine.;
%let rc= %sysfunc(close(&dsid));
%end;
%else %let engine=0;
&engine
%mend;

67
base/mp_assertdsobs.sas Normal file
View File

@@ -0,0 +1,67 @@
/**
@file
@brief Asserts the number of observations in a dataset
@details Useful in the context of writing sasjs tests. The results of the
test are _appended_ to the &outds. table.
Example usage:
%mp_assertdsobs(sashelp.class) %* tests if any observations are present;
<h4> SAS Macros </h4>
@li mf_nobs.sas
@param [in] inds input dataset to test for presence of observations
@param [in] desc= (Testing observations) The user provided test description
@param [in] test= (HASOBS) The test to apply. Valid values are:
@li HASOBS Test is a PASS if the input dataset has any observations
@li EMPTY Test is a PASS if input dataset is empty
@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|Dataset &inds has XX obs|
@version 9.2
@author Allan Bowe
**/
%macro mp_assertdsobs(inds,
test=HASOBS,
desc=Testing observations,
outds=work.test_results
)/*/STORE SOURCE*/;
%local nobs;
%let nobs=%mf_nobs(&inds);
%let test=%upcase(&test);
data;
length test_description $256 test_result $4 test_comments $256;
test_description=symget('desc');
test_result='FAIL';
test_comments="&sysmacroname: Dataset &inds has &nobs observations";
%if &test=HASOBS %then %do;
if &nobs>0 then test_result='PASS';
%end;
%else %if &test=EMPTY %then %do;
if &nobs=0 then test_result='PASS';
%end;
%else %do;
test_comments="&sysmacroname: Unsatisfied test condition - &test";
%end;
run;
%local ds;
%let ds=&syslast;
proc append base=&outds data=&ds;
run;
proc sql;
drop table &ds;
%mend;

143
base/mp_filtercheck.sas Normal file
View File

@@ -0,0 +1,143 @@
/**
@file
@brief Checks an input filter table for validity
@details Performs checks on the input table to ensure it arrives in the
correct format. This is necessary to prevent code injection. Will update
SYSCC to 1008 if bad records are found.
Used for dynamic filtering in [Data Controller for SAS&reg;](https://datacontroller.io).
Usage:
%mp_filtercheck(work.filter,targetds=sashelp.class,outds=work.badrecords)
The input table should have the following format:
|GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767|
|---|---|---|---|---|---|
|AND|AND|1|AGE|=|12|
|AND|AND|1|SEX|<=|'M'|
|AND|OR|2|Name|NOT IN|('Jane','Alfred')|
|AND|OR|2|Weight|>=|7|
Rules applied:
@li GROUP_LOGIC - only AND/OR
@li SUBGROUP_LOGIC - only AND/OR
@li SUBGROUP_ID - only integers
@li VARIABLE_NM - must be in the target table
@li OPERATOR_NM - only =/>/</<=/>=/BETWEEN/IN/NOT IN/NOT EQUAL/CONTAINS
@li RAW_VALUE - no unquoted values except integers, commas and spaces.
@returns The &outds table containing any bad rows, plus a REASON_CD column.
@param [in] inds The table to be checked, with the format above
@param [in] targetds= The target dataset against which to verify VARIABLE_NM
@param [out] outds= The output table, which is a copy of the &inds. table
plus a REASON_CD column, containing only bad records. If bad records found,
the SYSCC value will be set to 1008 (general data problem). Downstream
processes should check this table (and return code) before continuing.
<h4> SAS Macros </h4>
@li mp_abort.sas
@li mf_getvarlist.sas
@li mf_nobs.sas
<h4> Related Macros </h4>
@li mp_filtergenerate.sas
@version 9.3
@author Allan Bowe
@todo Support date / hex / name literals and exponents in RAW_VALUE field
**/
%macro mp_filtercheck(inds,targetds=,outds=work.badrecords);
%mp_abort(iftrue= (&syscc ne 0)
,mac=&sysmacroname
,msg=%str(syscc=&syscc - on macro entry)
)
/**
* Sanitise the values based on valid value lists, then strip out
* quotes, commas, periods and spaces.
* Only numeric values should remain
*/
data &outds;
set &inds;
length reason_cd $32;
/* closed list checks */
if GROUP_LOGIC not in ('AND','OR') then do;
REASON_CD='GROUP_LOGIC should be either AND or OR';
putlog REASON_CD= GROUP_LOGIC=;
output;
end;
if SUBGROUP_LOGIC not in ('AND','OR') then do;
REASON_CD='SUBGROUP_LOGIC should be either AND or OR';
putlog REASON_CD= SUBGROUP_LOGIC=;
output;
end;
if mod(SUBGROUP_ID,1) ne 0 then do;
REASON_CD='SUBGROUP_ID should be integer';
putlog REASON_CD= SUBGROUP_ID=;
output;
end;
if upcase(VARIABLE_NM) not in
(%upcase(%mf_getvarlist(&targetds,dlm=%str(,),quote=SINGLE)))
then do;
REASON_CD="VARIABLE_NM not in &targetds";
putlog REASON_CD= VARIABLE_NM=;
output;
end;
if OPERATOR_NM not in
('=','>','<','<=','>=','BETWEEN','IN','NOT IN','NOT EQUAL','CONTAINS')
then do;
REASON_CD='Invalid OPERATOR_NM';
putlog REASON_CD= OPERATOR_NM=;
output;
end;
/* special logic */
if OPERATOR_NM='BETWEEN' then raw_value1=tranwrd(raw_value,' AND ','');
else if OPERATOR_NM in ('IN','NOT IN') then do;
if substr(raw_value,1,1) ne '('
or substr(cats(reverse(raw_value)),1,1) ne ')'
then do;
REASON_CD='Missing brackets in RAW_VALUE';
putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;
output;
end;
else raw_value1=substr(raw_value,2,max(length(raw_value)-2,0));
end;
else raw_value1=raw_value;
/* remove nested literals eg '' */
raw_value1=tranwrd(raw_value1,"''",'');
/* now match string literals (always single quotes) */
raw_value2=raw_value1;
regex = prxparse("s/(\').*?(\')//");
call prxchange(regex,-1,raw_value2);
/* remove commas */
raw_value3=compress(raw_value2,',');
/* output records that contain values other than digits and spaces */
if notdigit(compress(raw_value3,' '))>0 then do;
putlog raw_value3= $hex32.;
REASON_CD='Invalid RAW_VALUE';
putlog REASON_CD= raw_value= raw_value1= raw_value2= raw_value3=;
output;
end;
run;
%if %mf_nobs(&outds)>0 %then %let syscc=1008;
%mend;

View File

@@ -0,0 +1,90 @@
/**
@file
@brief Generates a filter clause from an input table, to a fileref
@details Uses the input table to generate an output filter clause.
This feature is used to create dynamic dropdowns in [Data Controller for SAS&reg](
https://datacontroller.io). The input table should be in the format below:
|GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767|
|---|---|---|---|---|---|
|AND|AND|1|AGE|=|12|
|AND|AND|1|SEX|<=|'M'|
|AND|OR|2|Name|NOT IN|('Jane','Alfred')|
|AND|OR|2|Weight|>=|7|
Note - if the above table is received from an external client, the values
should first be validated using the mp_filtercheck.sas macro to avoid risk
of SQL injection.
To generate the filter, run the following code:
data work.filtertable;
infile datalines4 dsd;
input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
OPERATOR_NM:$10. RAW_VALUE:$32767.;
datalines4;
AND,AND,1,AGE,=,12
AND,AND,1,SEX,<=,"'M'"
AND,OR,2,Name,NOT IN,"('Jane','Alfred')"
AND,OR,2,Weight,>=,7
;;;;
run;
%mp_filtergenerate(work.filtertable,outref=myfilter)
data _null_;
infile myfilter;
input;
put _infile_;
run;
Will write the following query to the log:
> (
> AGE = 12
> AND
> SEX <= 'M'
> ) AND (
> Name NOT IN ('Jane','Alfred')
> OR
> Weight >= 7
> )
@param [in] inds The input table with query values
@param [out] outref= The output fileref to contain the filter clause. Will
be created (or replaced).
<h4> Related Macros </h4>
@li mp_filtercheck.sas
<h4> SAS Macros </h4>
@li mp_abort.sas
@version 9.3
@author Allan Bowe
**/
%macro mp_filtergenerate(inds,outref=filter);
%mp_abort(iftrue= (&syscc ne 0)
,mac=&sysmacroname
,msg=%str(syscc=&syscc - on macro entry)
)
filename &outref temp;
data _null_;
file &outref lrecl=32800;
set &inds end=last;
by SUBGROUP_ID;
if _n_=1 then put '(';
else if first.SUBGROUP_ID then put +1 GROUP_LOGIC '(';
else put +2 SUBGROUP_LOGIC;
put +4 VARIABLE_NM OPERATOR_NM RAW_VALUE;
if last.SUBGROUP_ID then put ')'@;
run;
%mend;

75
base/mp_hashdataset.sas Normal file
View File

@@ -0,0 +1,75 @@
/**
@file
@brief Returns a unique hash for a dataset
@details Ignores metadata attributes, used only to hash values. Compared
datasets must be in the same order.
%mp_hashdataset(sashelp.class,outds=myhash)
data _null_;
set work.myhash;
put hashkey=;
run;
![sas md5 hash dataset log results](https://i.imgur.com/MqF98vk.png)
<h4> SAS Macros </h4>
@li mf_getattrn.sas
@li mf_getuniquename.sas
@li mf_getvarlist.sas
@li mf_getvartype.sas
@param [in] libds dataset to hash
@param [out] outds= (work.mf_hashdataset) The output dataset to create. This
will contain one column (hashkey) with one observation (a hex32.
representation of the input hash)
|hashkey:$32.|
|---|
|28ABC74ABFC45F50794237BA5566E6CA|
@version 9.2
@author Allan Bowe
**/
%macro mp_hashdataset(
libds,
outds=
)/*/STORE SOURCE*/;
%if %mf_getattrn(&libds,NLOBS)=0 %then %do;
%put %str(WARN)ING: Dataset &libds is empty;, or is not a dataset;
%end;
%else %if %mf_getattrn(&libds,NLOBS)<0 %then %do;
%put %str(ERR)OR: Dataset &libds is not a dataset;
%end;
%else %do;
%local keyvar /* roll up the md5 */
prevkeyvar /* retain prev record md5 */
lastvar /* last var in input ds */
varlist var i;
/* avoid naming conflict for hash key vars */
%let keyvar=%mf_getuniquename();
%let prevkeyvar=%mf_getuniquename();
%let lastvar=%mf_getuniquename();
%let varlist=%mf_getvarlist(&libds);
data &outds(rename=(&keyvar=hashkey) keep=&keyvar);
length &prevkeyvar &keyvar $32;
retain &prevkeyvar;
set &libds end=&lastvar;
/* hash should include previous row */
if _n_>1 then &keyvar=put(md5(&prevkeyvar
/* loop every column, hashing every individual value */
%do i=1 %to %sysfunc(countw(&varlist));
%let var=%scan(&varlist,&i,%str( ));
%if %mf_getvartype(&libds,&var)=C %then %do;
!!put(md5(trim(&var)),$hex32.)
%end;
%else %do;
!!put(md5(trim(put(&var*1,binary64.))),$hex32.)
%end;
%end;
),$hex32.);
&prevkeyvar=&keyvar;
if &lastvar then output;
run;
%end;
%mend;

View File

@@ -237,8 +237,16 @@ data _null_;
put '%else %if &action=OPEN %then %do; ';
put ' /* fix encoding */ ';
put ' OPTIONS NOBOMFILE; ';
put ' ';
put ' /** ';
put ' * check engine type to avoid the below err message: ';
put ' * > Function is only valid for filerefs using the CACHE access method. ';
put ' */ ';
put ' data _null_; ';
put ' rc = stpsrv_header(''Content-type'',"text/html; encoding=utf-8"); ';
put ' set sashelp.vextfl(where=(fileref="_WEBOUT")); ';
put ' if xengine=''STREAM'' then do; ';
put ' rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8"); ';
put ' end; ';
put ' run; ';
put ' ';
put ' /* setup json */ ';
@@ -252,16 +260,9 @@ data _null_;
put '%end; ';
put ' ';
put '%else %if &action=ARR or &action=OBJ %then %do; ';
put ' %if &sysver=9.4 %then %do; ';
put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt ';
put ' ,engine=PROCJSON,dbg=%str(&_debug) ';
put ' ) ';
put ' %end; ';
put ' %else %do; ';
put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt ';
put ' ,engine=DATASTEP,dbg=%str(&_debug) ';
put ' ) ';
put ' %end; ';
put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt ';
put ' ,engine=DATASTEP,dbg=%str(&_debug) ';
put ' ) ';
put '%end; ';
put '%else %if &action=CLOSE %then %do; ';
put ' %if %str(&_debug) ge 131 %then %do; ';

View File

@@ -68,8 +68,16 @@
%else %if &action=OPEN %then %do;
/* fix encoding */
OPTIONS NOBOMFILE;
/**
* check engine type to avoid the below err message:
* > Function is only valid for filerefs using the CACHE access method.
*/
data _null_;
rc = stpsrv_header('Content-type',"text/html; encoding=utf-8");
set sashelp.vextfl(where=(fileref="_WEBOUT"));
if xengine='STREAM' then do;
rc=stpsrv_header('Content-type',"text/html; encoding=utf-8");
end;
run;
/* setup json */
@@ -83,16 +91,9 @@
%end;
%else %if &action=ARR or &action=OBJ %then %do;
%if &sysver=9.4 %then %do;
%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt
,engine=PROCJSON,dbg=%str(&_debug)
)
%end;
%else %do;
%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt
,engine=DATASTEP,dbg=%str(&_debug)
)
%end;
%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt
,engine=DATASTEP,dbg=%str(&_debug)
)
%end;
%else %if &action=CLOSE %then %do;
%if %str(&_debug) ge 131 %then %do;

View File

@@ -1,6 +1,12 @@
{
"$schema": "https://cli.sasjs.io/sasjsconfig-schema.json",
"macroFolders": ["base", "meta", "metax", "viya", "lua"],
"macroFolders": [
"base",
"meta",
"metax",
"viya",
"lua"
],
"docConfig": {
"displayMacroCore": false,
"enableLineage": false,
@@ -21,14 +27,18 @@
"serverType": "SASVIYA",
"appLoc": "/Public/temp/macrocore",
"serviceConfig": {
"serviceFolders": ["tests/viya"],
"serviceFolders": [
"tests/base",
"tests/viya"
],
"macroVars": {
"mcTestAppLoc": "/Public/temp/macrocore"
}
},
"deployConfig": {
"deployServicePack": true
}
},
"contextName": "SAS Job Execution compute context"
}
]
}
}

View File

@@ -0,0 +1,128 @@
/**
@file
@brief Testing mp_filtercheck macro
<h4> SAS Macros </h4>
@li mp_filtercheck.sas
@li mp_assertdsobs.sas
**/
/* valid filter */
data work.inds;
infile datalines4 dsd;
input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
OPERATOR_NM:$10. RAW_VALUE:$32767.;
datalines4;
AND,AND,1,AGE,=,12
AND,AND,1,SEX,<=,"'M'"
AND,OR,2,Name,NOT IN,"('Jane','Alfred')"
AND,OR,2,Weight,>=,7
;;;;
run;
%mp_filtercheck(work.inds,
targetds=sashelp.class,
outds=work.badrecords
)
%let syscc=0;
%mp_assertdsobs(work.badrecords,
desc=Valid filter query,
test=EMPTY,
outds=work.test_results
)
/* invalid column */
data work.inds;
infile datalines4 dsd;
input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
OPERATOR_NM:$10. RAW_VALUE:$32767.;
datalines4;
AND,AND,1,invalid,=,12
AND,AND,1,SEX,<=,"'M'"
AND,OR,2,Name,NOT IN,"('Jane','Alfred')"
AND,OR,2,Weight,>=,7
;;;;
run;
%mp_filtercheck(work.inds,
targetds=sashelp.class,
outds=work.badrecords
)
%let syscc=0;
%mp_assertdsobs(work.badrecords,
desc=Invalid column name,
test=HASOBS,
outds=work.test_results
)
/* invalid raw value */
data work.inds;
infile datalines4 dsd;
input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
OPERATOR_NM:$10. RAW_VALUE:$32767.;
datalines4;
AND,OR,2,Name,NOT IN,"(''''Jane','Alfred')"
;;;;
run;
%mp_filtercheck(work.inds,
targetds=sashelp.class,
outds=work.badrecords
)
%let syscc=0;
%mp_assertdsobs(work.badrecords,
desc=Invalid raw value,
test=HASOBS,
outds=work.test_results
)
/* Code injection - column name */
data work.inds;
infile datalines4 dsd;
input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
OPERATOR_NM:$10. RAW_VALUE:$32767.;
datalines4;
AND,AND,1,%abort,=,12
AND,OR,2,Weight,>=,7
;;;;
run;
%mp_filtercheck(work.inds,
targetds=sashelp.class,
outds=work.badrecords
)
%let syscc=0;
%mp_assertdsobs(work.badrecords,
desc=Code injection - column name,
test=HASOBS,
outds=work.test_results
)
/* Code injection - raw values*/
data work.inds;
infile datalines4 dsd;
input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
OPERATOR_NM:$10. RAW_VALUE:$32767.;
datalines4;
AND,AND,1,age,=,;;%abort
;;;;
run;
%mp_filtercheck(work.inds,
targetds=sashelp.class,
outds=work.badrecords
)
%let syscc=0;
%mp_assertdsobs(work.badrecords,
desc=Code injection - raw value abort,
test=HASOBS,
outds=work.test_results
)
%webout(OPEN)
%webout(OBJ, TEST_RESULTS)
%webout(CLOSE)

View File

@@ -9,8 +9,8 @@
@param path= The full path of the folder to be created
@param access_token_var= The global macro variable to contain the access token
@param grant_type= valid values are "password" or "authorization_code" (unquoted).
The default is authorization_code.
@param grant_type= (authorization_code) Valid values are "password" or
"authorization_code" (unquoted).
@version VIYA V.03.04
@@ -39,7 +39,6 @@
%let &access_token_var=;
%end;
%put &sysmacroname: grant_type=&grant_type;
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
and &grant_type ne sas_services
)
@@ -85,12 +84,15 @@ options noquotelenmax;
%local libref1;
%let libref1=%mf_getuniquelibref();
libname &libref1 JSON fileref=&fname1;
%mp_abort(iftrue=(&SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 404)
%mp_abort(
iftrue=(
&SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 404
)
,mac=&sysmacroname
,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
)
%if &SYS_PROCHTTP_STATUS_CODE=200 %then %do;
%put &sysmacroname &newpath exists so grab the follow on link ;
%*put &sysmacroname &newpath exists so grab the follow on link ;
data _null_;
set &libref1..links;
if rel='createChild' then

View File

@@ -72,7 +72,6 @@ https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5p
%let oauth_bearer=oauth_bearer=sas_services;
%let &access_token_var=;
%end;
%put &sysmacroname: grant_type=&grant_type;
/* initial validation checking */
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password

View File

@@ -83,7 +83,6 @@ https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5p
%let oauth_bearer=oauth_bearer=sas_services;
%let &access_token_var=;
%end;
%put &sysmacroname: grant_type=&grant_type;
/* initial validation checking */
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password

View File

@@ -44,7 +44,7 @@
%let oauth_bearer=oauth_bearer=sas_services;
%let &access_token_var=;
%end;
%put &sysmacroname: grant_type=&grant_type;
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
and &grant_type ne sas_services
)

View File

@@ -39,7 +39,7 @@
%let oauth_bearer=oauth_bearer=sas_services;
%let &access_token_var=;
%end;
%put &sysmacroname: grant_type=&grant_type;
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
and &grant_type ne sas_services
)

View File

@@ -54,7 +54,7 @@
%let oauth_bearer=oauth_bearer=sas_services;
%let &access_token_var=;
%end;
%put &sysmacroname: grant_type=&grant_type;
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
and &grant_type ne sas_services
)