@sasjs/utils 2.49.0 → 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.
Files changed (144) hide show
  1. package/fs/internal/helper.js +19 -24
  2. package/fs/internal/helper.js.map +1 -1
  3. package/macros/mf_abort.sas +27 -0
  4. package/macros/mf_dedup.sas +53 -0
  5. package/macros/mf_deletefile.sas +31 -0
  6. package/macros/mf_existds.sas +30 -0
  7. package/macros/mf_existfeature.sas +59 -0
  8. package/macros/mf_existfileref.sas +33 -0
  9. package/macros/mf_existfunction.sas +37 -0
  10. package/macros/mf_existvar.sas +39 -0
  11. package/macros/mf_existvarlist.sas +56 -0
  12. package/macros/mf_fmtdttm.sas +42 -0
  13. package/macros/mf_getapploc.sas +78 -0
  14. package/macros/mf_getattrc.sas +34 -0
  15. package/macros/mf_getattrn.sas +34 -0
  16. package/macros/mf_getengine.sas +54 -0
  17. package/macros/mf_getfilesize.sas +64 -0
  18. package/macros/mf_getfmtlist.sas +61 -0
  19. package/macros/mf_getfmtname.sas +44 -0
  20. package/macros/mf_getkeyvalue.sas +32 -0
  21. package/macros/mf_getplatform.sas +75 -0
  22. package/macros/mf_getquotedstr.sas +57 -0
  23. package/macros/mf_getschema.sas +43 -0
  24. package/macros/mf_getuniquefileref.sas +60 -0
  25. package/macros/mf_getuniquelibref.sas +57 -0
  26. package/macros/mf_getuniquename.sas +22 -0
  27. package/macros/mf_getuser.sas +44 -0
  28. package/macros/mf_getvalue.sas +36 -0
  29. package/macros/mf_getvarcount.sas +49 -0
  30. package/macros/mf_getvarformat.sas +72 -0
  31. package/macros/mf_getvarlen.sas +56 -0
  32. package/macros/mf_getvarlist.sas +74 -0
  33. package/macros/mf_getvarnum.sas +58 -0
  34. package/macros/mf_getvartype.sas +52 -0
  35. package/macros/mf_getxengine.sas +43 -0
  36. package/macros/mf_increment.sas +29 -0
  37. package/macros/mf_isblank.sas +27 -0
  38. package/macros/mf_isdir.sas +34 -0
  39. package/macros/mf_isint.sas +36 -0
  40. package/macros/mf_islibds.sas +40 -0
  41. package/macros/mf_loc.sas +29 -0
  42. package/macros/mf_mkdir.sas +67 -0
  43. package/macros/mf_mval.sas +19 -0
  44. package/macros/mf_nobs.sas +26 -0
  45. package/macros/mf_trimstr.sas +50 -0
  46. package/macros/mf_uid.sas +21 -0
  47. package/macros/mf_verifymacvars.sas +72 -0
  48. package/macros/mf_wordsinstr1andstr2.sas +53 -0
  49. package/macros/mf_wordsinstr1butnotstr2.sas +54 -0
  50. package/macros/mf_writefile.sas +67 -0
  51. package/macros/mp_abort.sas +301 -0
  52. package/macros/mp_appendfile.sas +57 -0
  53. package/macros/mp_applyformats.sas +181 -0
  54. package/macros/mp_assert.sas +56 -0
  55. package/macros/mp_assertcols.sas +145 -0
  56. package/macros/mp_assertcolvals.sas +168 -0
  57. package/macros/mp_assertdsobs.sas +121 -0
  58. package/macros/mp_assertscope.sas +147 -0
  59. package/macros/mp_base64copy.sas +117 -0
  60. package/macros/mp_binarycopy.sas +79 -0
  61. package/macros/mp_chop.sas +194 -0
  62. package/macros/mp_cleancsv.sas +78 -0
  63. package/macros/mp_cntlout.sas +85 -0
  64. package/macros/mp_copyfolder.sas +84 -0
  65. package/macros/mp_coretable.sas +73 -0
  66. package/macros/mp_createconstraints.sas +70 -0
  67. package/macros/mp_createwebservice.sas +32 -0
  68. package/macros/mp_csv2ds.sas +140 -0
  69. package/macros/mp_deleteconstraints.sas +52 -0
  70. package/macros/mp_deletefolder.sas +70 -0
  71. package/macros/mp_dictionary.sas +52 -0
  72. package/macros/mp_dirlist.sas +263 -0
  73. package/macros/mp_distinctfmtvalues.sas +50 -0
  74. package/macros/mp_dropmembers.sas +46 -0
  75. package/macros/mp_ds2cards.sas +291 -0
  76. package/macros/mp_ds2csv.sas +212 -0
  77. package/macros/mp_ds2ddl.sas +30 -0
  78. package/macros/mp_ds2fmtds.sas +105 -0
  79. package/macros/mp_ds2inserts.sas +180 -0
  80. package/macros/mp_ds2md.sas +103 -0
  81. package/macros/mp_ds2squeeze.sas +120 -0
  82. package/macros/mp_dsmeta.sas +110 -0
  83. package/macros/mp_filtercheck.sas +251 -0
  84. package/macros/mp_filtergenerate.sas +105 -0
  85. package/macros/mp_filterstore.sas +254 -0
  86. package/macros/mp_filtervalidate.sas +105 -0
  87. package/macros/mp_getcols.sas +67 -0
  88. package/macros/mp_getconstraints.sas +115 -0
  89. package/macros/mp_getdbml.sas +335 -0
  90. package/macros/mp_getddl.sas +400 -0
  91. package/macros/mp_getformats.sas +124 -0
  92. package/macros/mp_getmaxvarlengths.sas +130 -0
  93. package/macros/mp_getpk.sas +281 -0
  94. package/macros/mp_gsubfile.sas +58 -0
  95. package/macros/mp_guesspk.sas +332 -0
  96. package/macros/mp_hashdataset.sas +91 -0
  97. package/macros/mp_hashdirectory.sas +162 -0
  98. package/macros/mp_include.sas +106 -0
  99. package/macros/mp_init.sas +76 -0
  100. package/macros/mp_jsonout.sas +406 -0
  101. package/macros/mp_lib2cards.sas +78 -0
  102. package/macros/mp_lib2inserts.sas +77 -0
  103. package/macros/mp_loadformat.sas +303 -0
  104. package/macros/mp_lockanytable.sas +243 -0
  105. package/macros/mp_lockfilecheck.sas +103 -0
  106. package/macros/mp_makedata.sas +107 -0
  107. package/macros/mp_md5.sas +58 -0
  108. package/macros/mp_perflog.sas +43 -0
  109. package/macros/mp_prevobs.sas +88 -0
  110. package/macros/mp_recursivejoin.sas +90 -0
  111. package/macros/mp_replace.sas +151 -0
  112. package/macros/mp_reseterror.sas +27 -0
  113. package/macros/mp_resetoption.sas +39 -0
  114. package/macros/mp_retainedkey.sas +247 -0
  115. package/macros/mp_runddl.sas +49 -0
  116. package/macros/mp_searchcols.sas +103 -0
  117. package/macros/mp_searchdata.sas +131 -0
  118. package/macros/mp_setkeyvalue.sas +55 -0
  119. package/macros/mp_sortinplace.sas +122 -0
  120. package/macros/mp_stackdiffs.sas +592 -0
  121. package/macros/mp_storediffs.sas +230 -0
  122. package/macros/mp_stprequests.sas +74 -0
  123. package/macros/mp_streamfile.sas +212 -0
  124. package/macros/mp_testjob.sas +92 -0
  125. package/macros/mp_testservice.sas +39 -0
  126. package/macros/mp_testwritespeedlibrary.sas +59 -0
  127. package/macros/mp_tree.sas +71 -0
  128. package/macros/mp_unzip.sas +90 -0
  129. package/macros/mp_updatevarlength.sas +93 -0
  130. package/macros/mp_validatecol.sas +97 -0
  131. package/macros/mp_wait4file.sas +35 -0
  132. package/macros/mp_webin.sas +59 -0
  133. package/macros/mp_zip.sas +85 -0
  134. package/package.json +4 -1
  135. package/scripts/copySASjsCore.ts +36 -0
  136. package/utils/index.d.ts +1 -2
  137. package/utils/index.js +2 -3
  138. package/utils/index.js.map +1 -1
  139. package/utils/utils.d.ts +1 -0
  140. package/utils/utils.js +7 -1
  141. package/utils/utils.js.map +1 -1
  142. package/utils/getNodeModulePath.d.ts +0 -2
  143. package/utils/getNodeModulePath.js +0 -83
  144. package/utils/getNodeModulePath.js.map +0 -1
@@ -0,0 +1,120 @@
1
+ /**
2
+ @file
3
+ @brief Create a smaller version of a dataset, without data loss
4
+ @details This macro will scan the input dataset and create a new one, that
5
+ has the minimum variable lengths needed to store the data without data loss.
6
+
7
+ Inspiration was taken from [How to Reduce the Disk Space Required by a
8
+ SAS® Data Set](https://www.lexjansen.com/nesug/nesug06/io/io18.pdf) by
9
+ Selvaratnam Sridharma. The end of the referenced paper presents a macro named
10
+ "squeeze", hence the nomenclature.
11
+
12
+ Usage:
13
+
14
+ data big;
15
+ length my big $32000;
16
+ do i=1 to 1e4;
17
+ my=repeat('oh my',100);
18
+ big='dawg';
19
+ special=._;
20
+ output;
21
+ end;
22
+ run;
23
+
24
+ %mp_ds2squeeze(work.big,outds=work.smaller)
25
+
26
+ The following will also be printed to the log (exact values may differ
27
+ depending on your OS and COMPRESS settings):
28
+
29
+ > MP_DS2SQUEEZE: work.big was 625MB
30
+
31
+ > MP_DS2SQUEEZE: work.smaller is 5MB
32
+
33
+ @param [in] libds The library.dataset to be squeezed
34
+ @param [out] outds= (work.mp_ds2squeeze) The squeezed dataset to create
35
+ @param [in] mdebug= (0) Set to 1 to enable DEBUG messages
36
+
37
+ <h4> SAS Macros </h4>
38
+ @li mf_getfilesize.sas
39
+ @li mf_getuniquefileref.sas
40
+ @li mf_getuniquename.sas
41
+ @li mp_getmaxvarlengths.sas
42
+
43
+ <h4> Related Programs </h4>
44
+ @li mp_ds2squeeze.test.sas
45
+
46
+ @version 9.3
47
+ @author Allan Bowe
48
+ **/
49
+
50
+ %macro mp_ds2squeeze(
51
+ libds,
52
+ outds=work.mp_ds2squeeze,
53
+ mdebug=0
54
+ )/*/STORE SOURCE*/;
55
+ %local dbg source;
56
+ %if &mdebug=1 %then %do;
57
+ %put &sysmacroname entry vars:;
58
+ %put _local_;
59
+ %end;
60
+ %else %do;
61
+ %let dbg=*;
62
+ %let source=/source2;
63
+ %end;
64
+
65
+ %local optval ds fref startsize;
66
+ %let ds=%mf_getuniquename();
67
+ %let fref=%mf_getuniquefileref();
68
+ %let startsize=%mf_getfilesize(libds=&libds,format=yes);
69
+
70
+ %mp_getmaxvarlengths(&libds,outds=&ds)
71
+
72
+ data _null_;
73
+ set &ds end=last;
74
+ file &fref;
75
+ /* grab the types */
76
+ retain dsid;
77
+ if _n_=1 then dsid=open("&libds",'is');
78
+ if dsid le 0 then do;
79
+ msg=sysmsg();
80
+ put msg=;
81
+ stop;
82
+ end;
83
+ type=vartype(dsid,varnum(dsid, name));
84
+ if last then rc=close(dsid);
85
+ /* write out the length statement */
86
+ if _n_=1 then put 'length ';
87
+ length len $6;
88
+ if type='C' then do;
89
+ if maxlen=0 then len='$1';
90
+ else len=cats('$',maxlen);
91
+ end;
92
+ else do;
93
+ if maxlen=0 then len='3';
94
+ else len=cats(maxlen);
95
+ end;
96
+ put ' ' name ' ' len;
97
+ if last then put ';';
98
+ run;
99
+
100
+ /* configure varlenchk - as we are explicitly shortening the variables */
101
+ %let optval=%sysfunc(getoption(varlenchk));
102
+ options varlenchk=NOWARN;
103
+
104
+ data &outds;
105
+ %inc &fref &source;
106
+ set &libds;
107
+ run;
108
+
109
+ options varlenchk=&optval;
110
+
111
+ %if &mdebug=0 %then %do;
112
+ proc sql;
113
+ drop table &ds;
114
+ filename &fref clear;
115
+ %end;
116
+
117
+ %put &sysmacroname: &libds was &startsize;
118
+ %put &sysmacroname: &outds is %mf_getfilesize(libds=&outds,format=yes);
119
+
120
+ %mend mp_ds2squeeze;
@@ -0,0 +1,110 @@
1
+ /**
2
+ @file
3
+ @brief Export dataset metadata to a single output table
4
+ @details Exports the dataset attributes and enginehost information, then
5
+ converts the datasets into a single output table in the following format:
6
+
7
+ |ODS_TABLE:$10.|NAME:$100.|VALUE:$1000.|
8
+ |---|---|---|
9
+ |`ATTRIBUTES `|`Data Set Name `|`SASHELP.CLASS `|
10
+ |`ATTRIBUTES `|`Observations `|`19 `|
11
+ |`ATTRIBUTES `|`Member Type `|`DATA `|
12
+ |`ATTRIBUTES `|`Variables `|`5 `|
13
+ |`ATTRIBUTES `|`Engine `|`V9 `|
14
+ |`ATTRIBUTES `|`Indexes `|`0 `|
15
+ |`ATTRIBUTES `|`Created `|`06/08/2020 00:59:14 `|
16
+ |`ATTRIBUTES `|`Observation Length `|`40 `|
17
+ |`ATTRIBUTES `|`Last Modified `|`06/08/2020 00:59:14 `|
18
+ |`ATTRIBUTES `|`Deleted Observations `|`0 `|
19
+ |`ATTRIBUTES `|`Protection `|`. `|
20
+ |`ATTRIBUTES `|`Compressed `|`NO `|
21
+ |`ATTRIBUTES `|`Data Set Type `|`. `|
22
+ |`ATTRIBUTES `|`Sorted `|`NO `|
23
+ |`ATTRIBUTES `|`Label `|`Student Data `|
24
+ |`ATTRIBUTES `|`Data Representation `|`SOLARIS_X86_64, LINUX_X86_64, ALPHA_TRU64, LINUX_IA64 `|
25
+ |`ATTRIBUTES `|`Encoding `|`us-ascii ASCII (ANSI) `|
26
+ |`ENGINEHOST `|`Data Set Page Size `|`65536 `|
27
+ |`ENGINEHOST `|`Number of Data Set Pages `|`1 `|
28
+ |`ENGINEHOST `|`First Data Page `|`1 `|
29
+ |`ENGINEHOST `|`Max Obs per Page `|`1632 `|
30
+ |`ENGINEHOST `|`Obs in First Data Page `|`19 `|
31
+ |`ENGINEHOST `|`Number of Data Set Repairs `|`0 `|
32
+ |`ENGINEHOST `|`Filename `|`/opt/sas/sas9/SASHome/SASFoundation/9.4/sashelp/class.sas7bdat `|
33
+ |`ENGINEHOST `|`Release Created `|`9.0401M7 `|
34
+ |`ENGINEHOST `|`Host Created `|`Linux `|
35
+ |`ENGINEHOST `|`Inode Number `|`28314616 `|
36
+ |`ENGINEHOST `|`Access Permission `|`rw-r--r-- `|
37
+ |`ENGINEHOST `|`Owner Name `|`sas `|
38
+ |`ENGINEHOST `|`File Size `|`128KB `|
39
+ |`ENGINEHOST `|`File Size (bytes) `|`131072 `|
40
+
41
+ Example usage:
42
+
43
+ %mp_dsmeta(sashelp.class,outds=work.mymeta)
44
+ proc print data=work.mymeta;
45
+ run;
46
+
47
+ For more details on creating datasets from PROC CONTENTS check out this
48
+ excellent [paper](
49
+ https://support.sas.com/resources/papers/proceedings14/1549-2014.pdf) by
50
+ [Louise Hadden](https://www.linkedin.com/in/louisehadden/).
51
+
52
+ @param libds The library.dataset to export the metadata for
53
+ @param outds= (work.dsmeta) The output table to contain the metadata
54
+
55
+ <h4> Related Files </h4>
56
+ @li mp_dsmeta.test.sas
57
+ @li mp_getcols.sas
58
+ @li mp_getdbml.sas
59
+ @li mp_getddl.sas
60
+ @li mp_getformats.sas
61
+ @li mp_getpk.sas
62
+ @li mp_guesspk.sas
63
+
64
+ **/
65
+
66
+ %macro mp_dsmeta(libds,outds=work.dsmeta);
67
+
68
+ %local ds1 ds2;
69
+ data;run; %let ds1=&syslast;
70
+ data;run; %let ds2=&syslast;
71
+
72
+ /* setup the ODS capture */
73
+ ods output attributes=&ds1 enginehost=&ds2;
74
+
75
+ /* export the metadata */
76
+ proc contents data=&libds;
77
+ run;
78
+
79
+ /* load it into a single table */
80
+ data &outds (keep=ods_table name value);
81
+ length ods_table $10 name label2 label1 label $100
82
+ value cvalue cvalue1 cvalue2 $1000
83
+ nvalue nvalue1 nvalue2 8;
84
+ if _n_=1 then call missing (of _all_);
85
+ * putlog (_all_)(=);
86
+ set &ds1 (in=atrs) &ds2 (in=eng);
87
+ if atrs then do;
88
+ ods_table='ATTRIBUTES';
89
+ name=coalescec(label1,label);
90
+ value=coalescec(cvalue1,cvalue,put(coalesce(nvalue1,nvalue),best.));
91
+ output;
92
+ if label2 ne '' then do;
93
+ name=label2;
94
+ value=coalescec(cvalue2,put(nvalue2,best.));
95
+ output;
96
+ end;
97
+ end;
98
+ else if eng then do;
99
+ ods_table='ENGINEHOST';
100
+ name=coalescec(label1,label);
101
+ value=coalescec(cvalue1,cvalue,put(coalesce(nvalue1,nvalue),best.));
102
+ output;
103
+ end;
104
+ run;
105
+
106
+ proc sql;
107
+ drop table &ds1, &ds2;
108
+
109
+ %mend mp_dsmeta;
110
+
@@ -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&reg;](
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&reg](
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;