Macros for SAS Application Developers
https://github.com/sasjs/core
Loading...
Searching...
No Matches
mp_filtercheck.test.sas
Go to the documentation of this file.
1/**
2 @file
3 @brief Testing mp_filtercheck macro
4
5 <h4> SAS Macros </h4>
6 @li mp_filtercheck.sas
7 @li mp_assertdsobs.sas
8 @li mp_assert.sas
9 @li mp_assertscope.sas
10
11 @sasjslint {"maxLineLength": 93}
12**/
13
14/* set up test data */
15data work.class ;
16length name $8 sex $1 age height weight 8;
17infile cards dsd;
18input Name:$char. Sex :$char. Age Height Weight;
19datalines4;
20Alfred,M,14,69,112.5
21Alice,F,13,56.5,84
22Barbara,F,13,65.3,98
23Carol,F,14,62.8,102.5
24Henry,M,14,63.5,102.5
25James,M,12,57.3,83
26Jane,F,12,59.8,84.5
27Janet,F,15,62.5,112.5
28Jeffrey,M,13,62.5,84
29John,M,12,59,99.5
30Joyce,F,11,51.3,50.5
31Judy,F,14,64.3,90
32Louise,F,12,56.3,77
33Mary,F,15,66.5,112
34Philip,M,16,72,150
35Robert,M,12,64.8,128
36Ronald,M,15,67,133
37Thomas,M,11,57.5,85
38William,M,15,66.5,112
39;;;;
40run;
41
42/* VALID filter conditions */
43data work.inds;
44 infile datalines4 dsd;
45 input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
46 OPERATOR_NM:$10. RAW_VALUE:$4000.;
47datalines4;
48AND,AND,1,AGE,=,12
49AND,AND,1,SEX,<=,"'M'"
50AND,OR,2,Name,NOT IN,"('Jane','Alfred')"
51AND,OR,2,Weight,>=,77.7
52AND,OR,2,Weight,NE,77.7
53AND,AND,1,age,=,.A
54AND,AND,1,height,<,.B
55AND,AND,1,age,IN,"(.a,.b,.)"
56AND,AND,1,age,IN,"(.A)"
57AND,AND,1,AGE,=,AGE
58AND,AND,1,AGE,<,Weight
59AND,AND,1,AGE,BETWEEN,"HEIGHT AND WEIGHT"
60AND,OR,2,Name,=,name
61;;;;
62run;
63
64%mp_assertscope(SNAPSHOT)
65%mp_filtercheck(work.inds,
66 targetds=work.class,
67 outds=work.badrecords,
68 abort=NO
69)
70%mp_assertscope(COMPARE)
71
72%let syscc=0;
73%mp_assertdsobs(work.badrecords,
74 desc=Valid filter query,
75 test=EMPTY,
76 outds=work.test_results
77)
78
79/* invalid column */
80data work.inds;
81 infile datalines4 dsd;
82 input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
83 OPERATOR_NM:$10. RAW_VALUE:$4000.;
84datalines4;
85AND,AND,1,invalid,=,12
86AND,AND,1,SEX,<=,"'M'"
87AND,OR,2,Name,NOT IN,"('Jane','Alfred')"
88AND,OR,2,Weight,>=,7
89;;;;
90run;
91%mp_filtercheck(work.inds,
92 targetds=work.class,
93 outds=work.badrecords,
94 abort=NO
95)
96%let syscc=0;
97%mp_assertdsobs(work.badrecords,
98 desc=Invalid column name,
99 test=HASOBS,
100 outds=work.test_results
101)
102
103/* invalid raw value */
104data work.inds;
105 infile datalines4 dsd;
106 input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
107 OPERATOR_NM:$10. RAW_VALUE:$4000.;
108datalines4;
109AND,OR,2,Name,NOT IN,"(''''Jane','Alfred')"
110;;;;
111run;
112
113%mp_filtercheck(work.inds,
114 targetds=work.class,
115 outds=work.badrecords,
116 abort=NO
117)
118%let syscc=0;
119%mp_assertdsobs(work.badrecords,
120 desc=Invalid raw value,
121 test=HASOBS,
122 outds=work.test_results
123)
124
125/* invalid IN value */
126data work.inds;
127 infile datalines4 dsd;
128 input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
129 OPERATOR_NM:$10. RAW_VALUE:$4000.;
130datalines4;
131AND,OR,2,age,IN,"(.,.a,X)"
132;;;;
133run;
134
135%mp_filtercheck(work.inds,
136 targetds=work.class,
137 outds=work.badrecords,
138 abort=NO
139)
140%let syscc=0;
141%mp_assertdsobs(work.badrecords,
142 desc=Invalid IN value,
143 test=HASOBS,
144 outds=work.test_results
145)
146
147/* Code injection - column name */
148data work.inds;
149 infile datalines4 dsd;
150 input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
151 OPERATOR_NM:$10. RAW_VALUE:$4000.;
152datalines4;
153AND,AND,1,%abort,=,12
154AND,OR,2,Weight,>=,7
155;;;;
156run;
157
158%mp_filtercheck(work.inds,
159 targetds=work.class,
160 outds=work.badrecords,
161 abort=NO
162)
163%let syscc=0;
164%mp_assertdsobs(work.badrecords,
165 desc=Code injection - column name,
166 test=HASOBS,
167 outds=work.test_results
168)
169
170/* Code injection - raw values*/
171data work.inds;
172 infile datalines4 dsd;
173 input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
174 OPERATOR_NM:$10. RAW_VALUE:$4000.;
175datalines4;
176AND,AND,1,age,=,;;%abort
177;;;;
178run;
179%mp_filtercheck(work.inds,
180 targetds=work.class,
181 outds=work.badrecords,
182 abort=NO
183)
184%let syscc=0;
185%mp_assertdsobs(work.badrecords,
186 desc=Code injection - raw value abort,
187 test=HASOBS,
188 outds=work.test_results
189)
190
191/* Supply variables with incorrect types */
192data work.inds;
193 infile datalines4 dsd;
194 input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
195 OPERATOR_NM:$10. RAW_VALUE:8.;
196datalines4;
197AND,AND,1,age,=,0
198;;;;
199run;
200%let syscc=0;
201%mp_filtercheck(work.inds,
202 targetds=work.class,
203 outds=work.badrecords,
204 abort=NO
205)
206%mp_assert(iftrue=(&syscc=42),
207 desc=Throw a failure if RAW_VALUE is incorrect,
208 outds=work.test_results
209)
210%let syscc=0;
211
212/* invalid IN value (cannot use var names) */
213data work.inds;
214 infile datalines4 dsd;
215 input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
216 OPERATOR_NM:$10. RAW_VALUE:$4000.;
217datalines4;
218AND,AND,1,AGE,NOT IN,"(height, age)"
219;;;;
220run;
221
222%mp_filtercheck(work.inds,
223 targetds=work.class,
224 outds=work.badrecords,
225 abort=NO
226)
227%let syscc=0;
228%mp_assertdsobs(work.badrecords,
229 desc=Invalid IN syntax,
230 test=HASOBS,
231 outds=work.test_results
232)
233
234/* temporal literals are valid values for numeric columns */
235data work.dates;
236 length dt dtm tm 8;
237 format dt date9. dtm datetime20. tm time8.;
238 dt='20APR2042'd;
239 dtm='01JAN1960:00:00:42'dt;
240 tm='00:00:42't;
241run;
242
243data work.inds;
244 infile datalines4 dsd;
245 input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
246 OPERATOR_NM:$10. RAW_VALUE:$4000.;
247datalines4;
248AND,AND,1,dt,=,"'20APR2042'd"
249AND,AND,1,dt,NE,"'20APR2042'd"
250AND,AND,1,dtm,>=,"'01JAN1960:00:00:42'dt"
251AND,AND,1,tm,<,"'00:00:42't"
252AND,AND,1,dt,IN,"('20APR2042'd,'21APR2042'd)"
253AND,AND,1,dt,BETWEEN,"'20APR2042'd AND '21APR2042'd"
254;;;;
255run;
256
257%mp_filtercheck(work.inds,
258 targetds=work.dates,
259 outds=work.badrecords,
260 abort=NO
261)
262%let syscc=0;
263%mp_assertdsobs(work.badrecords,
264 desc=Temporal literals in RAW_VALUE,
265 test=EMPTY,
266 outds=work.test_results
267)
268
269/* a temporal literal is not valid for a character column */
270data work.inds;
271 infile datalines4 dsd;
272 input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
273 OPERATOR_NM:$10. RAW_VALUE:$4000.;
274datalines4;
275AND,AND,1,name,=,"'20APR2042'd"
276;;;;
277run;
278
279%mp_filtercheck(work.inds,
280 targetds=work.class,
281 outds=work.badrecords,
282 abort=NO
283)
284%let syscc=0;
285%mp_assertdsobs(work.badrecords,
286 desc=Temporal literal against a character column,
287 test=HASOBS,
288 outds=work.test_results
289)
290
291/* every value of an IN list is checked, not just the first */
292data work.inds;
293 infile datalines4 dsd;
294 input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
295 OPERATOR_NM:$10. RAW_VALUE:$4000.;
296datalines4;
297AND,AND,1,dt,IN,"('20APR2042'd,notanumber)"
298;;;;
299run;
300
301%mp_filtercheck(work.inds,
302 targetds=work.dates,
303 outds=work.badrecords,
304 abort=NO
305)
306%let syscc=0;
307%mp_assertdsobs(work.badrecords,
308 desc=Invalid value after the first in an IN list,
309 test=HASOBS,
310 outds=work.test_results
311)
312
313/* a suffix that is not temporal is still rejected */
314data work.inds;
315 infile datalines4 dsd;
316 input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
317 OPERATOR_NM:$10. RAW_VALUE:$4000.;
318datalines4;
319AND,AND,1,dt,=,"'20APR2042'x"
320;;;;
321run;
322
323%mp_filtercheck(work.inds,
324 targetds=work.dates,
325 outds=work.badrecords,
326 abort=NO
327)
328%let syscc=0;
329%mp_assertdsobs(work.badrecords,
330 desc=Non temporal literal suffix,
331 test=HASOBS,
332 outds=work.test_results
333)
334
335/* a character IN list is still validated - only a numeric list takes the
336 value-by-value path and skips the generic check */
337data work.inds;
338 infile datalines4 dsd;
339 input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
340 OPERATOR_NM:$10. RAW_VALUE:$4000.;
341datalines4;
342AND,AND,1,name,IN,"(Alfred,Alice)"
343;;;;;
344run;
345
346%mp_filtercheck(work.inds,
347 targetds=work.class,
348 outds=work.badrecords,
349 abort=NO
350)
351%let syscc=0;
352%mp_assertdsobs(work.badrecords,
353 desc=Character IN list is still validated,
354 test=HASOBS,
355 outds=work.test_results
356)
357
358/* the regex id and the qualified reference are working variables - they must
359 not leak into the output */
360%let re_temporal_col=1;
361%let vref_col=1;
362data _null_;
363 dsid=open('work.badrecords','i');
364 if dsid>0 then do;
365 call symputx('re_temporal_col',(varnum(dsid,'RE_TEMPORAL')>0));
366 call symputx('vref_col',(varnum(dsid,'VREF')>0));
367 rc=close(dsid);
368 end;
369run;
370%mp_assert(iftrue=(&re_temporal_col=0),
371 desc=re_temporal is not in the bad records table,
372 outds=work.test_results
373)
374%mp_assert(iftrue=(&vref_col=0),
375 desc=vref is not in the bad records table,
376 outds=work.test_results
377)
378
379/* CONTAINS on a character column is valid */
380data work.inds;
381 infile datalines4 dsd;
382 input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
383 OPERATOR_NM:$10. RAW_VALUE:$4000.;
384datalines4;
385AND,AND,1,Name,CONTAINS,"'ar'"
386AND,AND,1,SEX,CONTAINS,"'F'"
387;;;;;
388run;
389
390%mp_filtercheck(work.inds,
391 targetds=work.class,
392 outds=work.badrecords,
393 abort=NO
394)
395%let syscc=0;
396%mp_assertdsobs(work.badrecords,
397 desc=CONTAINS on a character column,
398 test=EMPTY,
399 outds=work.test_results
400)
401
402/* CONTAINS on a numeric column is rejected before any SQL is generated */
403data work.inds;
404 infile datalines4 dsd;
405 input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
406 OPERATOR_NM:$10. RAW_VALUE:$4000.;
407datalines4;
408AND,AND,1,Age,CONTAINS,"'12'"
409;;;;;
410run;
411
412%mp_filtercheck(work.inds,
413 targetds=work.class,
414 outds=work.badrecords,
415 abort=NO
416)
417%let syscc=0;
418%mp_assertdsobs(work.badrecords,
419 desc=CONTAINS on a numeric column,
420 test=HASOBS,
421 outds=work.test_results
422)
423
424/* the rejection names the operator and the offending variable */
425%let contains_reason=0;
426data _null_;
427 set work.badrecords;
428 if index(REASON_CD,
429 'CONTAINS is only valid for character variables - work.class.Age is numeric')>0
430 then call symputx('contains_reason',1);
431run;
432%mp_assert(iftrue=(&contains_reason=1),
433 desc=CONTAINS rejection names the variable,
434 outds=work.test_results
435)
436
437/* a non numeric value on a numeric column is rejected once, with the
438 CONTAINS reason - the generic RAW_VALUE check must not overwrite it */
439data work.inds;
440 infile datalines4 dsd;
441 input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
442 OPERATOR_NM:$10. RAW_VALUE:$4000.;
443datalines4;
444AND,AND,1,Age,CONTAINS,abc
445;;;;;
446run;
447
448%mp_filtercheck(work.inds,
449 targetds=work.class,
450 outds=work.badrecords,
451 abort=NO
452)
453%let syscc=0;
454%mp_assertdsobs(work.badrecords,
455 desc=CONTAINS numeric rejection emits a single row,
456 test=EQUALS 1,
457 outds=work.test_results
458)
459
460%let contains_single=0;
461data _null_;
462 set work.badrecords;
463 if index(REASON_CD,
464 'CONTAINS is only valid for character variables - work.class.Age is numeric')>0
465 then call symputx('contains_single',1);
466run;
467%mp_assert(iftrue=(&contains_single=1),
468 desc=CONTAINS numeric rejection keeps its reason,
469 outds=work.test_results
470)
471
472/* a character column with an unquoted numeric value is also rejected - the
473 engine refuses a numeric operand however the column is typed */
474data work.inds;
475 infile datalines4 dsd;
476 input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
477 OPERATOR_NM:$10. RAW_VALUE:$4000.;
478datalines4;
479AND,AND,1,Name,CONTAINS,12
480;;;;;
481run;
482
483%mp_filtercheck(work.inds,
484 targetds=work.class,
485 outds=work.badrecords,
486 abort=NO
487)
488%let syscc=0;
489%mp_assertdsobs(work.badrecords,
490 desc=CONTAINS with an unquoted numeric value,
491 test=HASOBS,
492 outds=work.test_results
493)
494
495%let contains_operand=0;
496data _null_;
497 set work.badrecords;
498 if index(REASON_CD,
499 'CONTAINS requires a quoted value or a character variable - work.class.Name CONTAINS 12')
500 >0 then call symputx('contains_operand',1);
501run;
502%mp_assert(iftrue=(&contains_operand=1),
503 desc=CONTAINS operand rejection names the value,
504 outds=work.test_results
505)
506
507/* a character variable is a valid CONTAINS operand */
508data work.inds;
509 infile datalines4 dsd;
510 input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
511 OPERATOR_NM:$10. RAW_VALUE:$4000.;
512datalines4;
513AND,AND,1,Name,CONTAINS,SEX
514;;;;;
515run;
516
517%mp_filtercheck(work.inds,
518 targetds=work.class,
519 outds=work.badrecords,
520 abort=NO
521)
522%let syscc=0;
523%mp_assertdsobs(work.badrecords,
524 desc=CONTAINS with a character variable operand,
525 test=EMPTY,
526 outds=work.test_results
527)
528
529/* a quoted value with leading blanks is still a quoted value */
530data work.inds;
531 length GROUP_LOGIC $3 SUBGROUP_LOGIC $3 SUBGROUP_ID 8 VARIABLE_NM $32
532 OPERATOR_NM $10 RAW_VALUE $4000;
533 GROUP_LOGIC='AND';
534 SUBGROUP_LOGIC='AND';
535 SUBGROUP_ID=1;
536 VARIABLE_NM='Name';
537 OPERATOR_NM='CONTAINS';
538 RAW_VALUE=" 'ar'";
539run;
540
541%mp_filtercheck(work.inds,
542 targetds=work.class,
543 outds=work.badrecords,
544 abort=NO
545)
546%let syscc=0;
547%mp_assertdsobs(work.badrecords,
548 desc=CONTAINS with a leading blank before the quote,
549 test=EMPTY,
550 outds=work.test_results
551)
552
553/* a numeric variable operand is a type mismatch, not a missing variable */
554data work.inds;
555 infile datalines4 dsd;
556 input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
557 OPERATOR_NM:$10. RAW_VALUE:$4000.;
558datalines4;
559AND,AND,1,Name,CONTAINS,Age
560;;;;;
561run;
562
563%mp_filtercheck(work.inds,
564 targetds=work.class,
565 outds=work.badrecords,
566 abort=NO
567)
568%let syscc=0;
569%let operand_type_reason=0;
570data _null_;
571 set work.badrecords;
572 if index(REASON_CD,
573 'Compared Type (N) is not (C) - work.class.Name CONTAINS Age')>0
574 then call symputx('operand_type_reason',1);
575run;
576%mp_assert(iftrue=(&operand_type_reason=1),
577 desc=CONTAINS with a numeric variable operand,
578 outds=work.test_results
579)
580