@sasjs/utils 2.48.2 → 2.49.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fs/generateCompileProgram.d.ts +1 -0
- package/fs/generateCompileProgram.js +154 -0
- package/fs/generateCompileProgram.js.map +1 -0
- package/fs/hash.d.ts +22 -0
- package/fs/hash.js +225 -0
- package/fs/hash.js.map +1 -0
- package/fs/index.d.ts +3 -0
- package/fs/index.js +13 -0
- package/fs/index.js.map +1 -0
- package/fs/internal/helper.d.ts +4 -0
- package/fs/internal/helper.js +125 -0
- package/fs/internal/helper.js.map +1 -0
- package/fs/sync.d.ts +6 -0
- package/fs/sync.js +208 -0
- package/fs/sync.js.map +1 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/macros/mf_abort.sas +27 -0
- package/macros/mf_dedup.sas +53 -0
- package/macros/mf_deletefile.sas +31 -0
- package/macros/mf_existds.sas +30 -0
- package/macros/mf_existfeature.sas +59 -0
- package/macros/mf_existfileref.sas +33 -0
- package/macros/mf_existfunction.sas +37 -0
- package/macros/mf_existvar.sas +39 -0
- package/macros/mf_existvarlist.sas +56 -0
- package/macros/mf_fmtdttm.sas +42 -0
- package/macros/mf_getapploc.sas +78 -0
- package/macros/mf_getattrc.sas +34 -0
- package/macros/mf_getattrn.sas +34 -0
- package/macros/mf_getengine.sas +54 -0
- package/macros/mf_getfilesize.sas +64 -0
- package/macros/mf_getfmtlist.sas +61 -0
- package/macros/mf_getfmtname.sas +44 -0
- package/macros/mf_getkeyvalue.sas +32 -0
- package/macros/mf_getplatform.sas +75 -0
- package/macros/mf_getquotedstr.sas +57 -0
- package/macros/mf_getschema.sas +43 -0
- package/macros/mf_getuniquefileref.sas +60 -0
- package/macros/mf_getuniquelibref.sas +57 -0
- package/macros/mf_getuniquename.sas +22 -0
- package/macros/mf_getuser.sas +44 -0
- package/macros/mf_getvalue.sas +36 -0
- package/macros/mf_getvarcount.sas +49 -0
- package/macros/mf_getvarformat.sas +72 -0
- package/macros/mf_getvarlen.sas +56 -0
- package/macros/mf_getvarlist.sas +74 -0
- package/macros/mf_getvarnum.sas +58 -0
- package/macros/mf_getvartype.sas +52 -0
- package/macros/mf_getxengine.sas +43 -0
- package/macros/mf_increment.sas +29 -0
- package/macros/mf_isblank.sas +27 -0
- package/macros/mf_isdir.sas +34 -0
- package/macros/mf_isint.sas +36 -0
- package/macros/mf_islibds.sas +40 -0
- package/macros/mf_loc.sas +29 -0
- package/macros/mf_mkdir.sas +67 -0
- package/macros/mf_mval.sas +19 -0
- package/macros/mf_nobs.sas +26 -0
- package/macros/mf_trimstr.sas +50 -0
- package/macros/mf_uid.sas +21 -0
- package/macros/mf_verifymacvars.sas +72 -0
- package/macros/mf_wordsinstr1andstr2.sas +53 -0
- package/macros/mf_wordsinstr1butnotstr2.sas +54 -0
- package/macros/mf_writefile.sas +67 -0
- package/macros/mp_abort.sas +301 -0
- package/macros/mp_appendfile.sas +57 -0
- package/macros/mp_applyformats.sas +181 -0
- package/macros/mp_assert.sas +56 -0
- package/macros/mp_assertcols.sas +145 -0
- package/macros/mp_assertcolvals.sas +168 -0
- package/macros/mp_assertdsobs.sas +121 -0
- package/macros/mp_assertscope.sas +147 -0
- package/macros/mp_base64copy.sas +117 -0
- package/macros/mp_binarycopy.sas +79 -0
- package/macros/mp_chop.sas +194 -0
- package/macros/mp_cleancsv.sas +78 -0
- package/macros/mp_cntlout.sas +85 -0
- package/macros/mp_copyfolder.sas +84 -0
- package/macros/mp_coretable.sas +73 -0
- package/macros/mp_createconstraints.sas +70 -0
- package/macros/mp_createwebservice.sas +32 -0
- package/macros/mp_csv2ds.sas +140 -0
- package/macros/mp_deleteconstraints.sas +52 -0
- package/macros/mp_deletefolder.sas +70 -0
- package/macros/mp_dictionary.sas +52 -0
- package/macros/mp_dirlist.sas +263 -0
- package/macros/mp_distinctfmtvalues.sas +50 -0
- package/macros/mp_dropmembers.sas +46 -0
- package/macros/mp_ds2cards.sas +291 -0
- package/macros/mp_ds2csv.sas +212 -0
- package/macros/mp_ds2ddl.sas +30 -0
- package/macros/mp_ds2fmtds.sas +105 -0
- package/macros/mp_ds2inserts.sas +180 -0
- package/macros/mp_ds2md.sas +103 -0
- package/macros/mp_ds2squeeze.sas +120 -0
- package/macros/mp_dsmeta.sas +110 -0
- package/macros/mp_filtercheck.sas +251 -0
- package/macros/mp_filtergenerate.sas +105 -0
- package/macros/mp_filterstore.sas +254 -0
- package/macros/mp_filtervalidate.sas +105 -0
- package/macros/mp_getcols.sas +67 -0
- package/macros/mp_getconstraints.sas +115 -0
- package/macros/mp_getdbml.sas +335 -0
- package/macros/mp_getddl.sas +400 -0
- package/macros/mp_getformats.sas +124 -0
- package/macros/mp_getmaxvarlengths.sas +130 -0
- package/macros/mp_getpk.sas +281 -0
- package/macros/mp_gsubfile.sas +58 -0
- package/macros/mp_guesspk.sas +332 -0
- package/macros/mp_hashdataset.sas +91 -0
- package/macros/mp_hashdirectory.sas +162 -0
- package/macros/mp_include.sas +106 -0
- package/macros/mp_init.sas +76 -0
- package/macros/mp_jsonout.sas +406 -0
- package/macros/mp_lib2cards.sas +78 -0
- package/macros/mp_lib2inserts.sas +77 -0
- package/macros/mp_loadformat.sas +303 -0
- package/macros/mp_lockanytable.sas +243 -0
- package/macros/mp_lockfilecheck.sas +103 -0
- package/macros/mp_makedata.sas +107 -0
- package/macros/mp_md5.sas +58 -0
- package/macros/mp_perflog.sas +43 -0
- package/macros/mp_prevobs.sas +88 -0
- package/macros/mp_recursivejoin.sas +90 -0
- package/macros/mp_replace.sas +151 -0
- package/macros/mp_reseterror.sas +27 -0
- package/macros/mp_resetoption.sas +39 -0
- package/macros/mp_retainedkey.sas +247 -0
- package/macros/mp_runddl.sas +49 -0
- package/macros/mp_searchcols.sas +103 -0
- package/macros/mp_searchdata.sas +131 -0
- package/macros/mp_setkeyvalue.sas +55 -0
- package/macros/mp_sortinplace.sas +122 -0
- package/macros/mp_stackdiffs.sas +592 -0
- package/macros/mp_storediffs.sas +230 -0
- package/macros/mp_stprequests.sas +74 -0
- package/macros/mp_streamfile.sas +212 -0
- package/macros/mp_testjob.sas +92 -0
- package/macros/mp_testservice.sas +39 -0
- package/macros/mp_testwritespeedlibrary.sas +59 -0
- package/macros/mp_tree.sas +71 -0
- package/macros/mp_unzip.sas +90 -0
- package/macros/mp_updatevarlength.sas +93 -0
- package/macros/mp_validatecol.sas +97 -0
- package/macros/mp_wait4file.sas +35 -0
- package/macros/mp_webin.sas +59 -0
- package/macros/mp_zip.sas +85 -0
- package/package.json +5 -2
- package/scripts/copySASjsCore.ts +36 -0
- package/types/hash.d.ts +9 -0
- package/types/hash.js +3 -0
- package/types/hash.js.map +1 -0
- package/types/index.d.ts +1 -0
- package/types/index.js +1 -0
- package/types/index.js.map +1 -1
- package/utils/index.d.ts +1 -1
- package/utils/index.js +2 -1
- package/utils/index.js.map +1 -1
- package/utils/utils.d.ts +1 -0
- package/utils/utils.js +7 -1
- package/utils/utils.js.map +1 -1
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@file
|
|
3
|
+
@brief Checks an input filter table for validity
|
|
4
|
+
@details Performs checks on the input table to ensure it arrives in the
|
|
5
|
+
correct format. This is necessary to prevent code injection. Will update
|
|
6
|
+
SYSCC to 1008 if bad records are found, and call mp_abort.sas for a
|
|
7
|
+
graceful service exit (configurable).
|
|
8
|
+
|
|
9
|
+
Used for dynamic filtering in [Data Controller for SAS®](
|
|
10
|
+
https://datacontroller.io).
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
|
|
14
|
+
%mp_filtercheck(work.filter,targetds=sashelp.class,outds=work.badrecords)
|
|
15
|
+
|
|
16
|
+
The input table should have the following format:
|
|
17
|
+
|
|
18
|
+
|GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$4000|
|
|
19
|
+
|---|---|---|---|---|---|
|
|
20
|
+
|AND|AND|1|AGE|=|12|
|
|
21
|
+
|AND|AND|1|SEX|<=|'M'|
|
|
22
|
+
|AND|OR|2|Name|NOT IN|('Jane','Alfred')|
|
|
23
|
+
|AND|OR|2|Weight|>=|7|
|
|
24
|
+
|
|
25
|
+
Rules applied:
|
|
26
|
+
|
|
27
|
+
@li GROUP_LOGIC - only AND/OR
|
|
28
|
+
@li SUBGROUP_LOGIC - only AND/OR
|
|
29
|
+
@li SUBGROUP_ID - only integers
|
|
30
|
+
@li VARIABLE_NM - must be in the target table
|
|
31
|
+
@li OPERATOR_NM - only =/>/</<=/>=/BETWEEN/IN/NOT IN/NE/CONTAINS
|
|
32
|
+
@li RAW_VALUE - no unquoted values except integers, commas and spaces.
|
|
33
|
+
|
|
34
|
+
@returns The &outds table containing any bad rows, plus a REASON_CD column.
|
|
35
|
+
|
|
36
|
+
@param [in] inds The table to be checked, with the format above
|
|
37
|
+
@param [in] targetds= The target dataset against which to verify VARIABLE_NM.
|
|
38
|
+
This must be available (ie, the library must be assigned).
|
|
39
|
+
@param [out] abort= (YES) If YES will call mp_abort.sas on any exceptions
|
|
40
|
+
@param [out] outds= The output table, which is a copy of the &inds. table
|
|
41
|
+
plus a REASON_CD column, containing only bad records. If bad records found,
|
|
42
|
+
the SYSCC value will be set to 1008 (general data problem). Downstream
|
|
43
|
+
processes should check this table (and return code) before continuing.
|
|
44
|
+
|
|
45
|
+
<h4> SAS Macros </h4>
|
|
46
|
+
@li mp_abort.sas
|
|
47
|
+
@li mf_getuniquefileref.sas
|
|
48
|
+
@li mf_getvarlist.sas
|
|
49
|
+
@li mf_getvartype.sas
|
|
50
|
+
@li mp_filtergenerate.sas
|
|
51
|
+
@li mp_filtervalidate.sas
|
|
52
|
+
|
|
53
|
+
<h4> Related Macros </h4>
|
|
54
|
+
@li mp_filtergenerate.sas
|
|
55
|
+
@li mp_filtervalidate.sas
|
|
56
|
+
|
|
57
|
+
@version 9.3
|
|
58
|
+
@author Allan Bowe
|
|
59
|
+
|
|
60
|
+
@todo Support date / hex / name literals and exponents in RAW_VALUE field
|
|
61
|
+
**/
|
|
62
|
+
|
|
63
|
+
%macro mp_filtercheck(inds,targetds=,outds=work.badrecords,abort=YES);
|
|
64
|
+
|
|
65
|
+
%mp_abort(iftrue= (&syscc ne 0)
|
|
66
|
+
,mac=&sysmacroname
|
|
67
|
+
,msg=%str(syscc=&syscc - on macro entry)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
/* Validate input column */
|
|
71
|
+
%local vtype;
|
|
72
|
+
%let vtype=%mf_getvartype(&inds,RAW_VALUE);
|
|
73
|
+
%mp_abort(iftrue=(&abort=YES and &vtype ne C),
|
|
74
|
+
mac=&sysmacroname,
|
|
75
|
+
msg=%str(%str(ERR)OR: RAW_VALUE must be character)
|
|
76
|
+
)
|
|
77
|
+
%if &vtype ne C %then %do;
|
|
78
|
+
%put &sysmacroname: RAW_VALUE must be character;
|
|
79
|
+
%let syscc=42;
|
|
80
|
+
%return;
|
|
81
|
+
%end;
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Sanitise the values based on valid value lists, then strip out
|
|
86
|
+
* quotes, commas, periods and spaces.
|
|
87
|
+
* Only numeric values should remain
|
|
88
|
+
*/
|
|
89
|
+
%local reason_cd nobs;
|
|
90
|
+
%let nobs=0;
|
|
91
|
+
data &outds;
|
|
92
|
+
/*length GROUP_LOGIC SUBGROUP_LOGIC $3 SUBGROUP_ID 8 VARIABLE_NM $32
|
|
93
|
+
OPERATOR_NM $10 RAW_VALUE $4000;*/
|
|
94
|
+
set &inds;
|
|
95
|
+
length reason_cd $4032 vtype $1 vnum dsid 8 tmp $4000;
|
|
96
|
+
drop tmp;
|
|
97
|
+
|
|
98
|
+
/* quick check to ensure column exists */
|
|
99
|
+
if upcase(VARIABLE_NM) not in
|
|
100
|
+
(%upcase(%mf_getvarlist(&targetds,dlm=%str(,),quote=SINGLE)))
|
|
101
|
+
then do;
|
|
102
|
+
REASON_CD="Variable "!!cats(variable_nm)!!" not in &targetds";
|
|
103
|
+
putlog REASON_CD= VARIABLE_NM=;
|
|
104
|
+
call symputx('reason_cd',reason_cd,'l');
|
|
105
|
+
call symputx('nobs',_n_,'l');
|
|
106
|
+
output;
|
|
107
|
+
return;
|
|
108
|
+
end;
|
|
109
|
+
|
|
110
|
+
/* need to open the dataset to get the column type */
|
|
111
|
+
dsid=open("&targetds","i");
|
|
112
|
+
if dsid>0 then do;
|
|
113
|
+
vnum=varnum(dsid,VARIABLE_NM);
|
|
114
|
+
if vnum<1 then do;
|
|
115
|
+
/* should not happen as was also tested for above */
|
|
116
|
+
REASON_CD=cats("Variable (",VARIABLE_NM,") not found in &targetds");
|
|
117
|
+
putlog REASON_CD= dsid=;
|
|
118
|
+
call symputx('reason_cd',reason_cd,'l');
|
|
119
|
+
call symputx('nobs',_n_,'l');
|
|
120
|
+
output;
|
|
121
|
+
return;
|
|
122
|
+
end;
|
|
123
|
+
/* now we can get the type */
|
|
124
|
+
else vtype=vartype(dsid,vnum);
|
|
125
|
+
end;
|
|
126
|
+
|
|
127
|
+
/* closed list checks */
|
|
128
|
+
if GROUP_LOGIC not in ('AND','OR') then do;
|
|
129
|
+
REASON_CD='GROUP_LOGIC should be AND/OR, not:'!!cats(GROUP_LOGIC);
|
|
130
|
+
putlog REASON_CD= GROUP_LOGIC=;
|
|
131
|
+
call symputx('reason_cd',reason_cd,'l');
|
|
132
|
+
call symputx('nobs',_n_,'l');
|
|
133
|
+
output;
|
|
134
|
+
end;
|
|
135
|
+
if SUBGROUP_LOGIC not in ('AND','OR') then do;
|
|
136
|
+
REASON_CD='SUBGROUP_LOGIC should be AND/OR, not:'!!cats(SUBGROUP_LOGIC);
|
|
137
|
+
putlog REASON_CD= SUBGROUP_LOGIC=;
|
|
138
|
+
call symputx('reason_cd',reason_cd,'l');
|
|
139
|
+
call symputx('nobs',_n_,'l');
|
|
140
|
+
output;
|
|
141
|
+
end;
|
|
142
|
+
if mod(SUBGROUP_ID,1) ne 0 then do;
|
|
143
|
+
REASON_CD='SUBGROUP_ID should be integer, not '!!cats(subgroup_id);
|
|
144
|
+
putlog REASON_CD= SUBGROUP_ID=;
|
|
145
|
+
call symputx('reason_cd',reason_cd,'l');
|
|
146
|
+
call symputx('nobs',_n_,'l');
|
|
147
|
+
output;
|
|
148
|
+
end;
|
|
149
|
+
if OPERATOR_NM not in
|
|
150
|
+
('=','>','<','<=','>=','NE','GE','LE','BETWEEN','IN','NOT IN','CONTAINS')
|
|
151
|
+
then do;
|
|
152
|
+
REASON_CD='Invalid OPERATOR_NM: '!!cats(OPERATOR_NM);
|
|
153
|
+
putlog REASON_CD= OPERATOR_NM=;
|
|
154
|
+
call symputx('reason_cd',reason_cd,'l');
|
|
155
|
+
call symputx('nobs',_n_,'l');
|
|
156
|
+
output;
|
|
157
|
+
end;
|
|
158
|
+
|
|
159
|
+
/* special missing logic */
|
|
160
|
+
if vtype='N'
|
|
161
|
+
and OPERATOR_NM in ('=','>','<','<=','>=','NE','GE','LE')
|
|
162
|
+
and cats(upcase(raw_value)) in (
|
|
163
|
+
'.','.A','.B','.C','.D','.E','.F','.G','.H','.I','.J','.K','.L','.M','.N'
|
|
164
|
+
'.N','.O','.P','.Q','.R','.S','.T','.U','.V','.W','.X','.Y','.Z','._'
|
|
165
|
+
)
|
|
166
|
+
then do;
|
|
167
|
+
/* valid numeric - exit data step loop */
|
|
168
|
+
return;
|
|
169
|
+
end;
|
|
170
|
+
|
|
171
|
+
/* special logic */
|
|
172
|
+
if OPERATOR_NM in ('IN','NOT IN','BETWEEN') then do;
|
|
173
|
+
if OPERATOR_NM='BETWEEN' then raw_value1=tranwrd(raw_value,' AND ',',');
|
|
174
|
+
else do;
|
|
175
|
+
if substr(raw_value,1,1) ne '('
|
|
176
|
+
or substr(cats(reverse(raw_value)),1,1) ne ')'
|
|
177
|
+
then do;
|
|
178
|
+
REASON_CD='Missing start/end bracket in RAW_VALUE';
|
|
179
|
+
putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;
|
|
180
|
+
call symputx('reason_cd',reason_cd,'l');
|
|
181
|
+
call symputx('nobs',_n_,'l');
|
|
182
|
+
output;
|
|
183
|
+
end;
|
|
184
|
+
else raw_value1=substr(raw_value,2,max(length(raw_value)-2,0));
|
|
185
|
+
end;
|
|
186
|
+
/* we now have a comma seperated list of values */
|
|
187
|
+
if vtype='N' then do i=1 to countc(raw_value1, ',')+1;
|
|
188
|
+
tmp=scan(raw_value1,i,',');
|
|
189
|
+
if cats(tmp) ne '.' and input(tmp, ?? 8.) eq . then do;
|
|
190
|
+
REASON_CD='Non Numeric value provided';
|
|
191
|
+
putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;
|
|
192
|
+
call symputx('reason_cd',reason_cd,'l');
|
|
193
|
+
call symputx('nobs',_n_,'l');
|
|
194
|
+
output;
|
|
195
|
+
end;
|
|
196
|
+
return;
|
|
197
|
+
end;
|
|
198
|
+
end;
|
|
199
|
+
else raw_value1=raw_value;
|
|
200
|
+
|
|
201
|
+
/* remove nested literals eg '' */
|
|
202
|
+
raw_value1=tranwrd(raw_value1,"''",'');
|
|
203
|
+
|
|
204
|
+
/* now match string literals (always single quotes) */
|
|
205
|
+
raw_value2=raw_value1;
|
|
206
|
+
regex = prxparse("s/(\').*?(\')//");
|
|
207
|
+
call prxchange(regex,-1,raw_value2);
|
|
208
|
+
|
|
209
|
+
/* remove commas and periods*/
|
|
210
|
+
raw_value3=compress(raw_value2,',.');
|
|
211
|
+
|
|
212
|
+
/* output records that contain values other than digits and spaces */
|
|
213
|
+
if notdigit(compress(raw_value3,' '))>0 then do;
|
|
214
|
+
putlog raw_value3= $hex32.;
|
|
215
|
+
REASON_CD=cats('Invalid RAW_VALUE:',raw_value);
|
|
216
|
+
putlog REASON_CD= raw_value= raw_value1= raw_value2= raw_value3=;
|
|
217
|
+
call symputx('reason_cd',reason_cd,'l');
|
|
218
|
+
call symputx('nobs',_n_,'l');
|
|
219
|
+
output;
|
|
220
|
+
end;
|
|
221
|
+
|
|
222
|
+
run;
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
data _null_;
|
|
226
|
+
set &outds end=last;
|
|
227
|
+
putlog (_all_)(=);
|
|
228
|
+
run;
|
|
229
|
+
|
|
230
|
+
%mp_abort(iftrue=(&abort=YES and &nobs>0),
|
|
231
|
+
mac=&sysmacroname,
|
|
232
|
+
msg=%str(Data issue: %superq(reason_cd))
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
%if &nobs>0 %then %do;
|
|
236
|
+
%let syscc=1008;
|
|
237
|
+
%return;
|
|
238
|
+
%end;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* syntax checking passed but it does not mean the filter is valid
|
|
242
|
+
* for that we can run a proc sql validate query
|
|
243
|
+
*/
|
|
244
|
+
%local fref1;
|
|
245
|
+
%let fref1=%mf_getuniquefileref();
|
|
246
|
+
%mp_filtergenerate(&inds,outref=&fref1)
|
|
247
|
+
|
|
248
|
+
/* this macro will also set syscc to 1008 if any issues found */
|
|
249
|
+
%mp_filtervalidate(&fref1,&targetds,outds=&outds,abort=&abort)
|
|
250
|
+
|
|
251
|
+
%mend mp_filtercheck;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@file
|
|
3
|
+
@brief Generates a filter clause from an input table, to a fileref
|
|
4
|
+
@details Uses the input table to generate an output filter clause.
|
|
5
|
+
This feature is used to create dynamic dropdowns in [Data Controller for SAS®](
|
|
6
|
+
https://datacontroller.io). The input table should be in the format below:
|
|
7
|
+
|
|
8
|
+
|GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$4000|
|
|
9
|
+
|---|---|---|---|---|---|
|
|
10
|
+
|AND|AND|1|AGE|=|12|
|
|
11
|
+
|AND|AND|1|SEX|<=|'M'|
|
|
12
|
+
|AND|OR|2|Name|NOT IN|('Jane','Alfred')|
|
|
13
|
+
|AND|OR|2|Weight|>=|7|
|
|
14
|
+
|
|
15
|
+
Note - if the above table is received from an external client, the values
|
|
16
|
+
should first be validated using the mp_filtercheck.sas macro to avoid risk
|
|
17
|
+
of SQL injection.
|
|
18
|
+
|
|
19
|
+
To generate the filter, run the following code:
|
|
20
|
+
|
|
21
|
+
data work.filtertable;
|
|
22
|
+
infile datalines4 dsd;
|
|
23
|
+
input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
|
|
24
|
+
OPERATOR_NM:$10. RAW_VALUE:$4000.;
|
|
25
|
+
datalines4;
|
|
26
|
+
AND,AND,1,AGE,=,12
|
|
27
|
+
AND,AND,1,SEX,<=,"'M'"
|
|
28
|
+
AND,OR,2,Name,NOT IN,"('Jane','Alfred')"
|
|
29
|
+
AND,OR,2,Weight,>=,7
|
|
30
|
+
;;;;
|
|
31
|
+
run;
|
|
32
|
+
|
|
33
|
+
%mp_filtergenerate(work.filtertable,outref=myfilter)
|
|
34
|
+
|
|
35
|
+
data _null_;
|
|
36
|
+
infile myfilter;
|
|
37
|
+
input;
|
|
38
|
+
put _infile_;
|
|
39
|
+
run;
|
|
40
|
+
|
|
41
|
+
Will write the following query to the log:
|
|
42
|
+
|
|
43
|
+
> (
|
|
44
|
+
> AGE = 12
|
|
45
|
+
> AND
|
|
46
|
+
> SEX <= 'M'
|
|
47
|
+
> ) AND (
|
|
48
|
+
> Name NOT IN ('Jane','Alfred')
|
|
49
|
+
> OR
|
|
50
|
+
> Weight >= 7
|
|
51
|
+
> )
|
|
52
|
+
|
|
53
|
+
@param [in] inds The input table with query values
|
|
54
|
+
@param [out] outref= The output fileref to contain the filter clause. Will
|
|
55
|
+
be created (or replaced).
|
|
56
|
+
|
|
57
|
+
<h4> Related Macros </h4>
|
|
58
|
+
@li mp_filtercheck.sas
|
|
59
|
+
@li mp_filtervalidate.sas
|
|
60
|
+
|
|
61
|
+
<h4> SAS Macros </h4>
|
|
62
|
+
@li mp_abort.sas
|
|
63
|
+
@li mf_nobs.sas
|
|
64
|
+
|
|
65
|
+
@version 9.3
|
|
66
|
+
@author Allan Bowe
|
|
67
|
+
|
|
68
|
+
**/
|
|
69
|
+
|
|
70
|
+
%macro mp_filtergenerate(inds,outref=filter);
|
|
71
|
+
|
|
72
|
+
%mp_abort(iftrue= (&syscc ne 0)
|
|
73
|
+
,mac=&sysmacroname
|
|
74
|
+
,msg=%str(syscc=&syscc - on macro entry)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
filename &outref temp;
|
|
78
|
+
|
|
79
|
+
%if %mf_nobs(&inds)=0 %then %do;
|
|
80
|
+
/* ensure we have a default filter */
|
|
81
|
+
data _null_;
|
|
82
|
+
file &outref;
|
|
83
|
+
put '1=1';
|
|
84
|
+
run;
|
|
85
|
+
%end;
|
|
86
|
+
%else %do;
|
|
87
|
+
proc sort data=&inds;
|
|
88
|
+
by SUBGROUP_ID;
|
|
89
|
+
run;
|
|
90
|
+
data _null_;
|
|
91
|
+
file &outref lrecl=32800;
|
|
92
|
+
set &inds end=last;
|
|
93
|
+
by SUBGROUP_ID;
|
|
94
|
+
if _n_=1 then put '((';
|
|
95
|
+
else if first.SUBGROUP_ID then put +1 GROUP_LOGIC '(';
|
|
96
|
+
else put +2 SUBGROUP_LOGIC;
|
|
97
|
+
|
|
98
|
+
put +4 VARIABLE_NM OPERATOR_NM RAW_VALUE;
|
|
99
|
+
|
|
100
|
+
if last.SUBGROUP_ID then put ')'@;
|
|
101
|
+
if last then put ')';
|
|
102
|
+
run;
|
|
103
|
+
%end;
|
|
104
|
+
|
|
105
|
+
%mend mp_filtergenerate;
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@file
|
|
3
|
+
@brief Checks & Stores an input filter table and returns the Filter Key
|
|
4
|
+
@details Used to generate a FILTER_RK from an input query dataset. This
|
|
5
|
+
process requires several permanent tables (names are configurable). The
|
|
6
|
+
benefit of storing query values at backend is to enable stored 'views' of
|
|
7
|
+
filtered tables at frontend (ie, when building [SAS-Powered Apps](
|
|
8
|
+
https://sasapps.io)). This macro is also used in [Data Controller for SAS](
|
|
9
|
+
https://datacontroller.io).
|
|
10
|
+
|
|
11
|
+
A more recent feature of this macro is the ability to support filter queries
|
|
12
|
+
on Format Catalogs. This is achieved by adding a `-FC` suffix to the `libds`
|
|
13
|
+
parameter - where the "ds" in this case is the catalog name.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@param [in] libds= The target dataset to be filtered (lib should be assigned).
|
|
17
|
+
If filtering a format catalog, add the following suffix: `-FC`.
|
|
18
|
+
@param [in] queryds= (WORK.FILTERQUERY) The temporary input query dataset to
|
|
19
|
+
be validated. Has the following format:
|
|
20
|
+
|GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767|
|
|
21
|
+
|---|---|---|---|---|---|
|
|
22
|
+
|AND|AND|1|SOME_BESTNUM|>|1|
|
|
23
|
+
|AND|AND|1|SOME_TIME|=|77333|
|
|
24
|
+
@param [in] filter_summary= (PERM.FILTER_SUMMARY) Permanent table containing
|
|
25
|
+
summary filter values. The definition is available by running
|
|
26
|
+
mp_coretable.sas as follows: `mp_coretable(FILTER_SUMMARY)`. Example
|
|
27
|
+
values:
|
|
28
|
+
|FILTER_RK:best.|FILTER_HASH:$32.|FILTER_TABLE:$41.|PROCESSED_DTTM:datetime19.|
|
|
29
|
+
|---|---|---|---|
|
|
30
|
+
|`1 `|`540E96F566D194AB58DD4C413C99C9DB `|`VIYA6014.MPE_TABLES `|`1956084246 `|
|
|
31
|
+
|`2 `|`87737DB9EEE2650F5C89956CEAD0A14F `|`VIYA6014.MPE_X_TEST `|`1956084452.1`|
|
|
32
|
+
|`3 `|`8048BD908DBBD83D013560734E90D394 `|`VIYA6014.MPE_TABLES `|`1956093620.6`|
|
|
33
|
+
@param [in] filter_detail= (PERM.FILTER_DETAIL) Permanent table containing
|
|
34
|
+
detailed (raw) filter values. The definition is available by running
|
|
35
|
+
mp_coretable.sas as follows: `mp_coretable(FILTER_DETAIL)`. Example
|
|
36
|
+
values:
|
|
37
|
+
|FILTER_HASH:$32.|FILTER_LINE:best.|GROUP_LOGIC:$3.|SUBGROUP_LOGIC:$3.|SUBGROUP_ID:best.|VARIABLE_NM:$32.|OPERATOR_NM:$12.|RAW_VALUE:$4000.|PROCESSED_DTTM:datetime19.|
|
|
38
|
+
|---|---|---|---|---|---|---|---|---|
|
|
39
|
+
|`540E96F566D194AB58DD4C413C99C9DB `|`1 `|`AND `|`AND `|`1 `|`LIBREF `|`CONTAINS `|`DC`|`1956084245.8 `|
|
|
40
|
+
|`540E96F566D194AB58DD4C413C99C9DB `|`2 `|`AND `|`OR `|`2 `|`DSN `|`= `|` MPE_LOCK_ANYTABLE `|`1956084245.8 `|
|
|
41
|
+
|`87737DB9EEE2650F5C89956CEAD0A14F `|`1 `|`AND `|`AND `|`1 `|`PRIMARY_KEY_FIELD `|`IN `|`(1,2,3) `|`1956084451.9 `|
|
|
42
|
+
@param [in] lock_table= (PERM.LOCK_TABLE) Permanent locking table. Used to
|
|
43
|
+
manage concurrent access. The definition is available by running
|
|
44
|
+
mp_coretable.sas as follows: `mp_coretable(LOCKTABLE)`.
|
|
45
|
+
@param [in] maxkeytable= (0) Optional permanent reference table used for
|
|
46
|
+
retained key tracking. Described in mp_retainedkey.sas.
|
|
47
|
+
@param [in] mdebug= set to 1 to enable DEBUG messages
|
|
48
|
+
@param [out] outresult= The result table with the FILTER_RK
|
|
49
|
+
@param [out] outquery= The original query, taken as extract after table load
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
<h4> SAS Macros </h4>
|
|
53
|
+
@li mddl_sas_cntlout.sas
|
|
54
|
+
@li mf_getuniquename.sas
|
|
55
|
+
@li mf_getvalue.sas
|
|
56
|
+
@li mf_islibds.sas
|
|
57
|
+
@li mf_nobs.sas
|
|
58
|
+
@li mp_abort.sas
|
|
59
|
+
@li mp_filtercheck.sas
|
|
60
|
+
@li mp_hashdataset.sas
|
|
61
|
+
@li mp_retainedkey.sas
|
|
62
|
+
|
|
63
|
+
<h4> Related Macros </h4>
|
|
64
|
+
@li mp_filtercheck.sas
|
|
65
|
+
@li mp_filtergenerate.sas
|
|
66
|
+
@li mp_filtervalidate.sas
|
|
67
|
+
@li mp_filterstore.test.sas
|
|
68
|
+
|
|
69
|
+
@version 9.2
|
|
70
|
+
@author [Allan Bowe](https://www.linkedin.com/in/allanbowe)
|
|
71
|
+
|
|
72
|
+
**/
|
|
73
|
+
|
|
74
|
+
%macro mp_filterstore(libds=,
|
|
75
|
+
queryds=work.filterquery,
|
|
76
|
+
filter_summary=PERM.FILTER_SUMMARY,
|
|
77
|
+
filter_detail=PERM.FILTER_DETAIL,
|
|
78
|
+
lock_table=PERM.LOCK_TABLE,
|
|
79
|
+
maxkeytable=PERM.MAXKEYTABLE,
|
|
80
|
+
outresult=work.result,
|
|
81
|
+
outquery=work.query,
|
|
82
|
+
mdebug=1
|
|
83
|
+
);
|
|
84
|
+
%put &sysmacroname entry vars:;
|
|
85
|
+
%put _local_;
|
|
86
|
+
|
|
87
|
+
%local ds0 ds1 ds2 ds3 ds4 filter_hash orig_libds;
|
|
88
|
+
%let libds=%upcase(&libds);
|
|
89
|
+
%let orig_libds=&libds;
|
|
90
|
+
|
|
91
|
+
%mp_abort(iftrue= (&syscc ne 0)
|
|
92
|
+
,mac=mp_filterstore
|
|
93
|
+
,msg=%str(syscc=&syscc on macro entry)
|
|
94
|
+
)
|
|
95
|
+
%mp_abort(iftrue= (%mf_islibds(&filter_summary)=0)
|
|
96
|
+
,mac=mp_filterstore
|
|
97
|
+
,msg=%str(Invalid filter_summary value: &filter_summary)
|
|
98
|
+
)
|
|
99
|
+
%mp_abort(iftrue= (%mf_islibds(&filter_detail)=0)
|
|
100
|
+
,mac=mp_filterstore
|
|
101
|
+
,msg=%str(Invalid filter_detail value: &filter_detail)
|
|
102
|
+
)
|
|
103
|
+
%mp_abort(iftrue= (%mf_islibds(&lock_table)=0)
|
|
104
|
+
,mac=mp_filterstore
|
|
105
|
+
,msg=%str(Invalid lock_table value: &lock_table)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* validate query
|
|
110
|
+
* use format catalog export, if a format
|
|
111
|
+
*/
|
|
112
|
+
%if "%substr(&libds,%length(&libds)-2,3)"="-FC" %then %do;
|
|
113
|
+
%let libds=%scan(&libds,1,-); /* chop off -FC extension */
|
|
114
|
+
%let ds0=%mf_getuniquename(prefix=fmtds_);
|
|
115
|
+
%let libds=&ds0;
|
|
116
|
+
/*
|
|
117
|
+
There is no need to export the entire format catalog here - the validations
|
|
118
|
+
are done against the data model, not the data values. So we can simply
|
|
119
|
+
hardcode the structure based on the cntlout dataset.
|
|
120
|
+
*/
|
|
121
|
+
%mddl_sas_cntlout(libds=&ds0)
|
|
122
|
+
|
|
123
|
+
%end;
|
|
124
|
+
%mp_filtercheck(&queryds,targetds=&libds,abort=YES)
|
|
125
|
+
|
|
126
|
+
/* hash the result */
|
|
127
|
+
%let ds1=%mf_getuniquename(prefix=hashds);
|
|
128
|
+
%mp_hashdataset(&queryds,outds=&ds1,salt=&orig_libds)
|
|
129
|
+
%let filter_hash=%upcase(%mf_getvalue(&ds1,hashkey));
|
|
130
|
+
%if &mdebug=1 %then %do;
|
|
131
|
+
data _null_;
|
|
132
|
+
putlog "filter_hash=&filter_hash";
|
|
133
|
+
set &ds1;
|
|
134
|
+
putlog (_all_)(=);
|
|
135
|
+
run;
|
|
136
|
+
%end;
|
|
137
|
+
|
|
138
|
+
/* check if data already exists for this hash */
|
|
139
|
+
data &outresult;
|
|
140
|
+
set &filter_summary;
|
|
141
|
+
where filter_hash="&filter_hash";
|
|
142
|
+
run;
|
|
143
|
+
|
|
144
|
+
%mp_abort(iftrue= (&syscc ne 0)
|
|
145
|
+
,mac=mp_filterstore
|
|
146
|
+
,msg=%str(syscc=&syscc after hash check)
|
|
147
|
+
)
|
|
148
|
+
%mp_abort(iftrue= ("&filter_hash "=" ")
|
|
149
|
+
,mac=mp_filterstore
|
|
150
|
+
,msg=%str(problem with filter_hash generation)
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
%if %mf_nobs(&outresult)=0 %then %do;
|
|
154
|
+
|
|
155
|
+
/* first update summary table */
|
|
156
|
+
%let ds3=%mf_getuniquename(prefix=filtersum);
|
|
157
|
+
data work.&ds3;
|
|
158
|
+
if 0 then set &filter_summary;
|
|
159
|
+
filter_table="&orig_libds";
|
|
160
|
+
filter_hash="&filter_hash";
|
|
161
|
+
PROCESSED_DTTM=%sysfunc(datetime());
|
|
162
|
+
output;
|
|
163
|
+
stop;
|
|
164
|
+
run;
|
|
165
|
+
|
|
166
|
+
%mp_lockanytable(LOCK,
|
|
167
|
+
lib=%scan(&filter_summary,1,.)
|
|
168
|
+
,ds=%scan(&filter_summary,2,.)
|
|
169
|
+
,ref=MP_FILTERSTORE summary update - &filter_hash
|
|
170
|
+
,ctl_ds=&lock_table
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
%let ds4=%mf_getuniquename(prefix=filtersumappend);
|
|
174
|
+
%mp_retainedkey(
|
|
175
|
+
base_lib=%scan(&filter_summary,1,.)
|
|
176
|
+
,base_dsn=%scan(&filter_summary,2,.)
|
|
177
|
+
,append_lib=work
|
|
178
|
+
,append_dsn=&ds3
|
|
179
|
+
,retained_key=filter_rk
|
|
180
|
+
,business_key=filter_hash
|
|
181
|
+
,maxkeytable=&maxkeytable
|
|
182
|
+
,locktable=&lock_table
|
|
183
|
+
,outds=work.&ds4
|
|
184
|
+
)
|
|
185
|
+
proc append base=&filter_summary data=&ds4;
|
|
186
|
+
run;
|
|
187
|
+
|
|
188
|
+
%mp_lockanytable(UNLOCK,
|
|
189
|
+
lib=%scan(&filter_summary,1,.)
|
|
190
|
+
,ds=%scan(&filter_summary,2,.)
|
|
191
|
+
,ref=MP_FILTERSTORE summary update - &filter_hash
|
|
192
|
+
,ctl_ds=&lock_table
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
%if &syscc ne 0 %then %do;
|
|
196
|
+
data _null_;
|
|
197
|
+
set &ds4;
|
|
198
|
+
putlog (_all_)(=);
|
|
199
|
+
run;
|
|
200
|
+
%goto err;
|
|
201
|
+
%end;
|
|
202
|
+
|
|
203
|
+
data &outresult;
|
|
204
|
+
set &filter_summary;
|
|
205
|
+
where filter_hash="&filter_hash";
|
|
206
|
+
run;
|
|
207
|
+
|
|
208
|
+
/* Next, update detail table */
|
|
209
|
+
%let ds2=%mf_getuniquename(prefix=filterdetail);
|
|
210
|
+
data &ds2;
|
|
211
|
+
if 0 then set &filter_detail;
|
|
212
|
+
set &queryds;
|
|
213
|
+
format filter_hash $hex32. filter_line 8.;
|
|
214
|
+
filter_hash="&filter_hash";
|
|
215
|
+
filter_line=_n_;
|
|
216
|
+
PROCESSED_DTTM=%sysfunc(datetime());
|
|
217
|
+
run;
|
|
218
|
+
%mp_lockanytable(LOCK,
|
|
219
|
+
lib=%scan(&filter_detail,1,.)
|
|
220
|
+
,ds=%scan(&filter_detail,2,.)
|
|
221
|
+
,ref=MP_FILTERSTORE update - &filter_hash
|
|
222
|
+
,ctl_ds=&lock_table
|
|
223
|
+
)
|
|
224
|
+
proc append base=&filter_detail data=&ds2;
|
|
225
|
+
run;
|
|
226
|
+
|
|
227
|
+
%mp_lockanytable(UNLOCK,
|
|
228
|
+
lib=%scan(&filter_detail,1,.)
|
|
229
|
+
,ds=%scan(&filter_detail,2,.)
|
|
230
|
+
,ref=MP_FILTERSTORE detail update &filter_hash
|
|
231
|
+
,ctl_ds=&lock_table
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
%if &syscc ne 0 %then %do;
|
|
235
|
+
data _null_;
|
|
236
|
+
set &ds2;
|
|
237
|
+
putlog (_all_)(=);
|
|
238
|
+
run;
|
|
239
|
+
%goto err;
|
|
240
|
+
%end;
|
|
241
|
+
|
|
242
|
+
%end;
|
|
243
|
+
|
|
244
|
+
proc sort data=&filter_detail(where=(filter_hash="&filter_hash")) out=&outquery;
|
|
245
|
+
by filter_line;
|
|
246
|
+
run;
|
|
247
|
+
|
|
248
|
+
%err:
|
|
249
|
+
%mp_abort(iftrue= (&syscc ne 0)
|
|
250
|
+
,mac=mp_filterstore
|
|
251
|
+
,msg=%str(syscc=&syscc on macro exit)
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
%mend mp_filterstore;
|