mm_webout.sas
Go to the documentation of this file.
1 /**
2  @file mm_webout.sas
3  @brief Send data to/from SAS Stored Processes
4  @details This macro should be added to the start of each Stored Process,
5  **immediately** followed by a call to:
6 
7  %mm_webout(FETCH)
8 
9  This will read all the input data and create same-named SAS datasets in the
10  WORK library. You can then insert your code, and send data back using the
11  following syntax:
12 
13  data some datasets; * make some data ;
14  retain some columns;
15  run;
16 
17  %mm_webout(OPEN)
18  %mm_webout(ARR,some) * Array format, fast, suitable for large tables ;
19  %mm_webout(OBJ,datasets) * Object format, easier to work with ;
20 
21  Finally, wrap everything up send some helpful system variables too
22 
23  %mm_webout(CLOSE)
24 
25 
26  @param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE
27  @param [in] ds The dataset to send back to the frontend
28  @param [out] dslabel= Value to use instead of table name for sending to JSON
29  @param [in] fmt= (N) Setting Y converts all vars to their formatted values
30  @param [out] fref= (_webout) The fileref to which to write the JSON
31  @param [in] missing= (NULL) Special numeric missing values can be sent as NULL
32  (eg `null`) or as STRING values (eg `".a"` or `".b"`)
33  @param [in] showmeta= (N) Set to Y to output metadata alongside each table,
34  such as the column formats and types. The metadata is contained inside an
35  object with the same name as the table but prefixed with a dollar sign - ie,
36  `,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`
37  @param [in] workobs= (0) When set to a positive integer, will create a new
38  output object (WORK) which contains this number of observations from all
39  tables in the WORK library.
40  @param [in] maxobs= (MAX) Provide an integer to limit the number of input rows
41  that should be converted to output JSON
42 
43  <h4> SAS Macros </h4>
44  @li mp_jsonout.sas
45 
46  <h4> Related Macros </h4>
47  @li ms_webout.sas
48  @li mv_webout.sas
49 
50  @version 9.3
51  @author Allan Bowe
52 
53 **/
54 %macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL
55  ,showmeta=N,maxobs=MAX,workobs=0
56 );
57 %global _webin_file_count _webin_fileref1 _webin_name1 _program _debug
58  sasjs_tables;
59 %local i tempds jsonengine;
60 
61 /* see https://github.com/sasjs/core/issues/41 */
62 %if "%upcase(&SYSENCODING)" ne "UTF-8" %then %let jsonengine=PROCJSON;
63 %else %let jsonengine=DATASTEP;
64 
65 
66 %if &action=FETCH %then %do;
67  %if %str(&_debug) ge 131 %then %do;
68  options mprint notes mprintnest;
69  %end;
70  %let _webin_file_count=%eval(&_webin_file_count+0);
71  /* now read in the data */
72  %do i=1 %to &_webin_file_count;
73  %if &_webin_file_count=1 %then %do;
74  %let _webin_fileref1=&_webin_fileref;
75  %let _webin_name1=&_webin_name;
76  %end;
77  data _null_;
78  infile &&_webin_fileref&i termstr=crlf;
79  input;
80  call symputx('input_statement',_infile_);
81  putlog "&&_webin_name&i input statement: " _infile_;
82  stop;
83  data &&_webin_name&i;
84  infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding='utf-8';
85  input &input_statement;
86  %if %str(&_debug) ge 131 %then %do;
87  if _n_<20 then putlog _infile_;
88  %end;
89  run;
90  %let sasjs_tables=&sasjs_tables &&_webin_name&i;
91  %end;
92 %end;
93 
94 %else %if &action=OPEN %then %do;
95  /* fix encoding */
96  OPTIONS NOBOMFILE;
97 
98  /**
99  * check xengine type to avoid the below err message:
100  * > Function is only valid for filerefs using the CACHE access method.
101  */
102  data _null_;
103  set sashelp.vextfl(where=(fileref="_WEBOUT"));
104  if xengine='STREAM' then do;
105  rc=stpsrv_header('Content-type',"text/html; encoding=utf-8");
106  end;
107  run;
108 
109  /* setup json */
110  data _null_;file &fref encoding='utf-8';
111  %if %str(&_debug) ge 131 %then %do;
112  put '>>weboutBEGIN<<';
113  %end;
114  put '{"SYSDATE" : "' "&SYSDATE" '"';
115  put ',"SYSTIME" : "' "&SYSTIME" '"';
116  run;
117 
118 %end;
119 
120 %else %if &action=ARR or &action=OBJ %then %do;
121  %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref
122  ,engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs
123  )
124 %end;
125 %else %if &action=CLOSE %then %do;
126  /* To avoid issues with _webout on EBI we use a temporary file */
127  filename _sjsref temp lrecl=131068;
128  %if %str(&workobs) > 0 %then %do;
129  /* if debug mode, send back first XX records of each work table also */
130  data;run;%let tempds=%scan(&syslast,2,.);
131  ods output Members=&tempds;
132  proc datasets library=WORK memtype=data;
133  %local wtcnt;%let wtcnt=0;
134  data _null_;
135  set &tempds;
136  if not (upcase(name) =:"DATA"); /* ignore temp datasets */
137  i+1;
138  call symputx(cats('wt',i),name,'l');
139  call symputx('wtcnt',i,'l');
140  data _null_; file _sjsref mod encoding='utf-8';
141  put ",""WORK"":{";
142  %do i=1 %to &wtcnt;
143  %let wt=&&wt&i;
144  data _null_; file _sjsref mod encoding='utf-8';
145  dsid=open("WORK.&wt",'is');
146  nlobs=attrn(dsid,'NLOBS');
147  nvars=attrn(dsid,'NVARS');
148  rc=close(dsid);
149  if &i>1 then put ','@;
150  put " ""&wt"" : {";
151  put '"nlobs":' nlobs;
152  put ',"nvars":' nvars;
153  %mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y
154  ,maxobs=&workobs
155  )
156  data _null_; file _sjsref mod encoding='utf-8';
157  put "}";
158  %end;
159  data _null_; file _sjsref mod encoding='utf-8';
160  put "}";
161  run;
162  %end;
163  /* close off json */
164  data _null_;file _sjsref mod encoding='utf-8';
165  length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;
166  put ",""_DEBUG"" : ""&_debug"" ";
167  _METAUSER=quote(trim(symget('_METAUSER')));
168  put ",""_METAUSER"": " _METAUSER;
169  _METAPERSON=quote(trim(symget('_METAPERSON')));
170  put ',"_METAPERSON": ' _METAPERSON;
171  _PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
172  put ',"_PROGRAM" : ' _PROGRAM ;
173  autoexec=quote(urlencode(trim(getoption('autoexec'))));
174  put ',"AUTOEXEC" : ' autoexec;
175  put ",""MF_GETUSER"" : ""%mf_getuser()"" ";
176  put ",""SYSCC"" : ""&syscc"" ";
177  put ",""SYSENCODING"" : ""&sysencoding"" ";
178  syserrortext=cats(symget('syserrortext'));
179  if findc(syserrortext,'"\'!!'0A0D09000E0F010210111A'x) then do;
180  syserrortext='"'!!trim(
181  prxchange('s/"/\\"/',-1, /* double quote */
182  prxchange('s/\x0A/\n/',-1, /* new line */
183  prxchange('s/\x0D/\r/',-1, /* carriage return */
184  prxchange('s/\x09/\\t/',-1, /* tab */
185  prxchange('s/\x00/\\u0000/',-1, /* NUL */
186  prxchange('s/\x0E/\\u000E/',-1, /* SS */
187  prxchange('s/\x0F/\\u000F/',-1, /* SF */
188  prxchange('s/\x01/\\u0001/',-1, /* SOH */
189  prxchange('s/\x02/\\u0002/',-1, /* STX */
190  prxchange('s/\x10/\\u0010/',-1, /* DLE */
191  prxchange('s/\x11/\\u0011/',-1, /* DC1 */
192  prxchange('s/\x1A/\\u001A/',-1, /* SUB */
193  prxchange('s/\\/\\\\/',-1,syserrortext)
194  )))))))))))))!!'"';
195  end;
196  else syserrortext=cats('"',syserrortext,'"');
197  put ',"SYSERRORTEXT" : ' syserrortext;
198  put ",""SYSHOSTNAME"" : ""&syshostname"" ";
199  put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";
200  put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";
201  SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));
202  put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;
203  put ",""SYSJOBID"" : ""&sysjobid"" ";
204  put ",""SYSSCPL"" : ""&sysscpl"" ";
205  put ",""SYSSITE"" : ""&syssite"" ";
206  put ",""SYSUSERID"" : ""&sysuserid"" ";
207  sysvlong=quote(trim(symget('sysvlong')));
208  put ',"SYSVLONG" : ' sysvlong;
209  syswarningtext=cats(symget('syswarningtext'));
210  if findc(syswarningtext,'"\'!!'0A0D09000E0F010210111A'x) then do;
211  syswarningtext='"'!!trim(
212  prxchange('s/"/\\"/',-1, /* double quote */
213  prxchange('s/\x0A/\n/',-1, /* new line */
214  prxchange('s/\x0D/\r/',-1, /* carriage return */
215  prxchange('s/\x09/\\t/',-1, /* tab */
216  prxchange('s/\x00/\\u0000/',-1, /* NUL */
217  prxchange('s/\x0E/\\u000E/',-1, /* SS */
218  prxchange('s/\x0F/\\u000F/',-1, /* SF */
219  prxchange('s/\x01/\\u0001/',-1, /* SOH */
220  prxchange('s/\x02/\\u0002/',-1, /* STX */
221  prxchange('s/\x10/\\u0010/',-1, /* DLE */
222  prxchange('s/\x11/\\u0011/',-1, /* DC1 */
223  prxchange('s/\x1A/\\u001A/',-1, /* SUB */
224  prxchange('s/\\/\\\\/',-1,syswarningtext)
225  )))))))))))))!!'"';
226  end;
227  else syswarningtext=cats('"',syswarningtext,'"');
228  put ',"SYSWARNINGTEXT" : ' syswarningtext;
229  put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
230  length memsize $32;
231  memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";
232  memsize=quote(cats(memsize));
233  put ',"MEMSIZE" : ' memsize;
234  put "}" @;
235  %if %str(&_debug) ge 131 %then %do;
236  put '>>weboutEND<<';
237  %end;
238  run;
239  /* now write to _webout 1 char at a time */
240  data _null_;
241  infile _sjsref lrecl=1 recfm=n;
242  file &fref mod lrecl=1 recfm=n;
243  input sourcechar $char1. @@;
244  format sourcechar hex2.;
245  put sourcechar char1. @@;
246  run;
247  filename _sjsref clear;
248 
249 %end;
250 
251 %mend mm_webout;