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

Compare commits

...

80 Commits

Author SHA1 Message Date
Allan Bowe
a1aac785c0 Merge pull request #313 from sasjs/issue312
feat: new mp_hashdirectory() macro and associated test.  Closes #312
2022-09-16 11:00:10 +01:00
munja
dbe8b0b1c3 chore: readme merge 2022-09-16 10:59:28 +01:00
munja
2ee9a4cee4 chore(docs): removed reference to part that is not ready yet 2022-09-16 10:59:02 +01:00
Allan Bowe
3a7afdffb7 Merge branch 'main' into issue312 2022-09-15 16:49:34 +01:00
munja
c78211aa1c feat: new mp_hashdirectory() macro and associated test. Closes #312 2022-09-15 16:47:05 +01:00
Allan Bowe
76c49e96f2 Update README.md 2022-09-15 15:03:36 +01:00
Allan Bowe
984ea44f5d Merge pull request #311 from sasjs/allanbowe/mv-createfile-needs-a-310
feat: adding ctype option to mv_createfile.sas macro
2022-09-13 20:37:28 +01:00
Allan Bowe
88f1222abd Merge branch 'main' into allanbowe/mv-createfile-needs-a-310 2022-09-13 20:37:03 +01:00
Allan Bowe
d88f028ee3 chore: removing ovpn from pipeline 2022-09-06 22:27:40 +00:00
Allan Bowe
07d7c9df4b feat: adding ctype option to mv_createfile.sas macro 2022-09-06 21:20:00 +00:00
munja
6765a1d025 chore(docs): image link 2022-09-03 18:00:00 +01:00
Allan Bowe
952f28a872 Merge pull request #309 from sasjs/dictionary
feat: new mp_dictionary() table
2022-09-03 16:53:05 +01:00
munja
8246b5a42c feat: new mp_dictionary() table 2022-09-03 16:50:11 +01:00
Allan Bowe
72123aeeb7 Merge pull request #305 from sasjs/cli1229
Making _addjesbeginendmacros configurable
2022-08-25 14:21:04 +01:00
Allan Bowe
236d1ae25f Merge branch 'main' into cli1229 2022-08-25 14:20:57 +01:00
munja
b75369b28d fix: pgm uninitialised in mm_getstpinfo 2022-08-23 16:00:42 +01:00
Allan Bowe
63871db170 Merge pull request #308 from sasjs/allanbowe/mp-jsonout-does-not-replace-307
fix: support for SUB (1A) hex char in DATASTEP generated JSON.
2022-08-22 14:16:13 +01:00
Allan Bowe
6456c2f6e2 fix: support for SUB (1A) hex char in DATASTEP generated JSON. Closes #307 2022-08-22 13:14:20 +00:00
munja
36faa194a8 chore(docs): more related files in mp_dsmeta.sas 2022-08-21 21:15:24 +01:00
munja
093dc87aad chore(docs): crediting louise 2022-08-21 19:55:02 +01:00
munja
ca045e3ebf chore(docs): typo 2022-08-21 19:27:52 +01:00
Allan Bowe
be5e2f371d Merge pull request #306 from sasjs/mp_dsmeta
feat: new mp_dsmeta macro
2022-08-21 19:18:56 +01:00
munja
6d15465bac fix: generating all.sas and fixing failing test 2022-08-21 19:17:56 +01:00
munja
2031a5b0c0 feat: new mp_dsmeta macro 2022-08-21 19:01:01 +01:00
Allan Bowe
7b3844a391 chore: updating all.sas 2022-08-21 16:02:20 +00:00
Allan Bowe
202de36042 fix: options to remove _addjesbeginendmacros from Viya Jobs 2022-08-21 16:01:50 +00:00
Allan Bowe
62837b512b feat: mm_getstpinfo.sas
Actually this came from a previous commit but the message was squashed out:  1b5effd584
2022-08-19 11:28:15 +01:00
Allan Bowe
5d5a99fd77 Merge pull request #304 from sasjs/allanbowe/need-a-macro-to-extract-303
chore(lint): reduce length
2022-08-19 11:00:00 +01:00
Allan Bowe
1b5effd584 chore(lint): reduce length 2022-08-19 09:58:42 +00:00
Allan Bowe
1613ab2c9e Merge pull request #302 from sasjs/allanbowe/proc-format-max-can-be-300
fix: switching MAX for LENGTH to get max label value.  Closes #300
2022-08-17 21:59:14 +01:00
Allan Bowe
a2df4e35be fix: switching MAX for LENGTH to get max label value. Closes #300 2022-08-17 20:54:14 +00:00
Allan Bowe
aabbcfdf6b Merge pull request #299 from sasjs/allanbowe/remove-work-tables-from-298
fix: removing automatic dump of WORK tables in mX_webout macros.  Clo…
2022-08-15 18:48:06 +01:00
Allan Bowe
7b7759e1ce chore: fix renegade closing bracket 2022-08-15 17:44:24 +00:00
Allan Bowe
e5a3053600 fix: removing automatic dump of WORK tables in mX_webout macros. Closes 298 2022-08-15 17:21:00 +00:00
Allan Bowe
9856d0ef58 Merge pull request #297 from sasjs/allanbowe/improve-efficiency-of-295
Further improvements to mp_jsonout
2022-08-15 00:48:23 +01:00
Allan Bowe
77b37e5503 chore: regenerated web service macros 2022-08-14 23:43:42 +00:00
Allan Bowe
793319fe38 fix: improved JSON performance for wide tables with a lot of formatted values. 50% improvement! 2022-08-14 23:43:19 +00:00
Allan Bowe
594a895ddd Merge pull request #296 from sasjs/allanbowe/improve-efficiency-of-295
fix: performance optimisations. closes #295
2022-08-12 19:54:04 +01:00
Allan Bowe
0d59266b8d fix: performance optimisations. closes #295 2022-08-12 18:13:35 +00:00
Allan Bowe
4863aafaa8 Merge pull request #294 from sasjs/allanbowe/add-maxobs-parameter-293
feat: adding maxobs param to mX_webout macros
2022-08-12 14:14:45 +01:00
Allan Bowe
6015320145 feat: adding maxobs param to mX_webout macros 2022-08-12 13:12:06 +00:00
Allan Bowe
8c09c0bce0 Merge pull request #292 from sasjs/allanbowe/increase-length-for-syswarningtext-291
fix: adding length statement for SYSWARNINGTEXT. Closes #291
2022-08-01 11:43:53 +01:00
Allan Bowe
437943b779 fix: adding length statement for SYSWARNINGTEXT. Closes #291 2022-08-01 10:40:55 +00:00
Allan Bowe
6a090e45b6 Merge pull request #290 from sasjs/allanbowe/mp-cleancsv-does-not-289
fix: enable embedded blanks in mp_cleancsv, closes #289
2022-07-21 23:47:18 +01:00
Allan Bowe
a7dc314204 fix: enable embedded blanks in mp_cleancsv, closes #289 2022-07-21 22:40:43 +00:00
munja
37076eae89 feat: new mmx_createmetafolder macro 2022-07-20 19:17:06 +01:00
munja
9a9f8dc847 chore(docs): adding matomo analytics 2022-07-15 16:06:03 +01:00
Allan Bowe
719b657267 Merge pull request #288 from sasjs/allanbowe/mp-jsonout-truncates-287
fix: avoid truncation for formatted outputs
2022-07-14 15:22:20 +01:00
Allan Bowe
671a615501 chore(docs): updated label 2022-07-14 14:18:27 +00:00
Allan Bowe
884b45bf12 fix: avoid truncation for formatted outputs
Closes #287
2022-07-14 14:16:42 +00:00
Allan Bowe
ff6ae1b066 Merge pull request #286 from sasjs/ddlfix
fix: comment issue in DDL generation
2022-07-14 14:03:29 +01:00
Allan Bowe
d581fec55e fix: comment issue in DDL generation 2022-07-14 13:02:15 +00:00
Allan Bowe
a5613a79bb chore(docs): adding SASJedi link to README 2022-07-14 11:42:34 +01:00
Allan Bowe
c6703e16e8 Merge pull request #285 from sasjs/mf_increment
feat: new mf_increment macro
2022-07-14 08:57:49 +01:00
munja
6587dce95b feat: new mf_increment macro 2022-07-13 23:57:02 +01:00
Allan Bowe
b60e6448b9 Merge pull request #284 from sasjs/allanbowe/dictionary-table-constraints-283
fix: avoid exceptions from dictionary.table_constraints.
2022-07-13 19:05:33 +01:00
Allan Bowe
46d9b58b32 fix: avoid exceptions from dictionary.table_constraints.
Closes #283
2022-07-13 18:01:52 +00:00
Allan Bowe
349cbabc94 Merge pull request #282 from sasjs/allanbowe/error-multiple-lengths-281
fix: prevent warning from `_label_` variable with different lengths
2022-07-12 23:29:47 +01:00
Allan Bowe
9de056a3fc fix: prevent warning from _label_ variable with different lengths
Closes #281
2022-07-12 22:18:01 +00:00
Allan Bowe
ad497b322f chore(tests): adding some extra test cases 2022-07-12 15:03:41 +00:00
Allan Bowe
7a6408ee44 Merge pull request #280 from sasjs/allanbowe/support-special-missings-279
fix: supporting special missings in BETWEEN and IN operators
2022-07-08 00:25:53 +01:00
Allan Bowe
336743f2b4 fix: applying logic to BETWEEN as well as IN 2022-07-07 23:24:24 +00:00
Allan Bowe
6e32eb3bd6 fix: supporting special missings in BETWEEN and IN operators
Impacts mp_filtercheck.sas.  Tests added.  Closes #279
2022-07-07 22:47:04 +00:00
Allan Bowe
b377b83442 Merge pull request #278 from sasjs/allanbowe/add-iftrue-parameter-277
fix: iftrue parameter for mp_binarycopy.  Closes #277
2022-07-07 11:29:25 +01:00
Allan Bowe
899b94bb6e fix: iftrue parameter for mp_binarycopy. Closes #277 2022-07-07 10:28:24 +00:00
Allan Bowe
d97efdff61 Merge pull request #276 from sasjs/allanbowe/syswarningtext-with-embedded-275
fix: escaping SYSWARNINGTEXT and SYSERRORTEXT for JSON response
2022-07-06 12:57:19 +01:00
Allan Bowe
1097afbcb8 fix: escaping SYSWARNINGTEXT and SYSERRORTEXT for JSON response
Closes #275
2022-07-06 11:55:15 +00:00
Allan Bowe
165b2d3568 Merge pull request #274 from sasjs/getpk
fix: enabling cross-compatibility of mp_getpk macro
2022-07-04 22:36:10 +01:00
Allan Bowe
44a80c8985 fix: enabling cross-compatibility of mp_getpk macro 2022-07-04 21:32:41 +00:00
Allan Bowe
6e32d9b743 Merge pull request #273 from sasjs/allanbowe/mp-jsonout-fails-in-meta-272
fix: setting length of label property in mp_jsonout
2022-07-04 13:27:02 +01:00
Allan Bowe
6b167e7a4c fix: longer label to allow for escapes 2022-07-04 12:26:19 +00:00
Allan Bowe
011672b1ed fix: setting length of label property in mp_jsonout 2022-07-04 12:24:53 +00:00
Allan Bowe
a7eb926810 Merge pull request #271 from sasjs/ms_getusers
fix: ensuring results when strict mode enabled in ms_getusers
2022-07-02 21:14:06 +01:00
Allan Bowe
cad7f13a0e fix: ensuring results when strict mode enabled in ms_getusers 2022-07-02 20:13:02 +00:00
Allan Bowe
65fcea817a Merge pull request #270 from sasjs/forcerelease
fix: forcing release for the previous fix
2022-07-01 00:17:11 +02:00
Allan Bowe
22fade13e7 fix: forcing release for the previous fix 2022-06-30 22:16:45 +00:00
Allan Bowe
7146310072 fix: missing ampersand 2022-06-30 22:06:27 +00:00
Allan Bowe
b7de1c25ec Merge pull request #269 from sasjs/jsonfix
fix: mX_webout macros in DEBUG mode had truncated json
2022-06-30 23:44:44 +02:00
Allan Bowe
f4c7f47ffe fix: mX_webout macros in DEBUG mode had truncated json
This was due to options obs=10 which affected new cross-encoding streaming technique
2022-06-30 21:41:10 +00:00
munja
cdf339d077 fix: reduce logging when debug is off 2022-06-29 20:09:00 +01:00
47 changed files with 2684 additions and 553 deletions

View File

@@ -1,30 +0,0 @@
cipher AES-256-CBC
setenv FORWARD_COMPATIBLE 1
client
server-poll-timeout 4
nobind
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 443 tcp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
dev tun
dev-type tun
ns-cert-type server
setenv opt tls-version-min 1.0 or-highest
reneg-sec 604800
sndbuf 0
rcvbuf 0
# NOTE: LZO commands are pushed by the Access Server at connect time.
# NOTE: The below line doesn't disable LZO.
comp-lzo no
verb 3
setenv PUSH_PEER_INFO
ca ca.crt
cert user.crt
key user.key
tls-auth tls.key 1

View File

@@ -21,31 +21,6 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- name: Write VPN Files
run: |
echo "$CA_CRT" > .github/vpn/ca.crt
echo "$USER_CRT" > .github/vpn/user.crt
echo "$USER_KEY" > .github/vpn/user.key
echo "$TLS_KEY" > .github/vpn/tls.key
shell: bash
env:
CA_CRT: ${{ secrets.CA_CRT}}
USER_CRT: ${{ secrets.USER_CRT }}
USER_KEY: ${{ secrets.USER_KEY }}
TLS_KEY: ${{ secrets.TLS_KEY }}
- name: Install Open VPN
run: |
sudo apt install apt-transport-https
sudo wget https://swupdate.openvpn.net/repos/openvpn-repo-pkg-key.pub
sudo apt-key add openvpn-repo-pkg-key.pub
sudo wget -O /etc/apt/sources.list.d/openvpn3.list https://swupdate.openvpn.net/community/openvpn3/repos/openvpn3-focal.list
sudo apt update
sudo apt install openvpn3
- name: Start Open VPN 3
run: openvpn3 session-start --config .github/vpn/config.ovpn
- name: Install Doxygen
run: sudo apt-get install doxygen
@@ -78,7 +53,5 @@ jobs:
SECRET: ${{secrets.SECRET}}
SAS_USERNAME: ${{secrets.SAS_USERNAME}}
SAS_PASSWORD: ${{secrets.SAS_PASSWORD}}
SERVER_URL: ${{secrets.SERVER_URL}}
SERVER_TYPE: ${{secrets.SERVER_TYPE}}
ACCESS_TOKEN: ${{secrets.ACCESS_TOKEN}}
REFRESH_TOKEN: ${{secrets.REFRESH_TOKEN}}

View File

@@ -36,21 +36,21 @@ Documentation: https://core.sasjs.io
- OS independent
- Works on all SAS Platforms
- No X command
- Prefixes: _mf_, _mp_
- Prefixes: `mf_`, `mp_`
### DDL folder (All Platforms)
- OS independent
- Works on all SAS Platforms
- No X command
- Prefixes: _mddl_(lib)_ -> where lib can be "SAS" (in relation to a SAS component) or "DC" (in relation to a Data Controller component)
- Prefixes: `mddl_(lib)_` -> where lib can be "SAS" (in relation to a SAS component) or "DC" (in relation to a Data Controller component)
This library will not be used for storing data entries (such as formats or datalines). Where this becomes necessary in the future, a new repo will be created, in order to keep the NPM bundle size down (for the benefit of those looking to embed purely macros in their applications).
### FCMP folder (All Platforms)
- Function and macro names are identical, except for special cases
- Prefixes: _mcf_
- Prefixes: `mcf_`
The fcmp macros are used to generate fcmp functions, and can be used with or without the `proc fcmp` wrapper.
@@ -72,7 +72,7 @@ endsubmit;
run;
```
- Prefixes: _ml_
- Prefixes: `ml_`
### META folder (SAS9 only)
@@ -81,14 +81,14 @@ Macros used in SAS EBI, which connect to the metadata server.
- OS independent
- Metadata aware
- No X command
- Prefixes: _mm_
- Prefixes: `mm_`
### METAX folder (SAS9 only)
- OS specific
- Metadata aware
- X command enabled
- Prefixes: _mmw_,_mmu_,_mmx_
- Prefixes: `mmx_`
### SERVER folder (@sasjs/server only)
These macros are used for building applications using [@sasjs/server](https://server.sasjs.io) - an open source REST API for Desktop SAS.
@@ -96,7 +96,7 @@ These macros are used for building applications using [@sasjs/server](https://se
- OS independent
- @sasjs/server aware
- No X command
- Prefixes: _ms_
- Prefixes: `ms_`
### VIYA folder (Viya only)
@@ -104,7 +104,7 @@ Macros used for interfacing with SAS Viya.
- OS independent
- No X command
- Prefixes: _mv_, _mvf_
- Prefixes: `mv_`, `mvf_`
### XPLATFORM folder (Viya, Meta, and Server)
@@ -112,7 +112,7 @@ Sometimes it is helpful to use a macro that can be used interchangeably regardle
- OS independent
- No X command
- Prefixes: _mx_
- Prefixes: `mx_`
## Installation
@@ -237,6 +237,8 @@ If you find this library useful, please leave a [star](https://github.com/sasjs/
The following repositories are also worth checking out:
* [xieliaing/SAS](https://github.com/xieliaing/SAS)
* [SASJedi/sas-macros](https://github.com/SASJedi/sas-macros)
* [chris-swenson/sasmacros](https://github.com/chris-swenson/sasmacros)
* [greg-wotton/sas-programs](https://github.com/greg-wootton/sas-programs)
* [KatjaGlassConsulting/SMILE-SmartSASMacros](https://github.com/KatjaGlassConsulting/SMILE-SmartSASMacros)

1421
all.sas

File diff suppressed because it is too large Load Diff

View File

@@ -40,6 +40,11 @@
%else %if "&SYSVLONG" < "9.04.01M3" %then 0;
%else 1;
%end;
%else %if &feature=DBMS_MEMTYPE %then %do;
/* does dbms_memtype exist in dictionary.tables? */
%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 0;
%else 1;
%end;
%else %if &feature=EXPORTXLS %then %do;
/* is it possible to PROC EXPORT an excel file? */
%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 1;

29
base/mf_increment.sas Normal file
View File

@@ -0,0 +1,29 @@
/**
@file
@brief Increments a macro variable
@details Useful outside of do-loops - will increment a macro variable every
time it is called.
Example:
%let cnt=1;
%put We have run %mf_increment(cnt) lines;
%put Now we have run %mf_increment(cnt) lines;
%put There are %mf_increment(cnt) lines in total;
@param [in] MACRO_NAME the name of the macro variable to increment
@param [in] ITER= The amount to add or subtract to the macro
<h4> Related Files </h4>
@li mf_increment.test.sas
**/
%macro mf_increment(macro_name,incr=1);
/* iterate the value */
%let &macro_name=%eval(&&&macro_name+&incr);
/* return the value */
&&&macro_name
%mend mf_increment;

View File

@@ -225,7 +225,7 @@ and %superq(SYSPROCESSNAME) ne %str(Compute Server)
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
put ',"_PROGRAM" : ' _PROGRAM ;
put ",""SYSCC"" : ""&syscc"" ";
syserrortext=quote(trim(symget('syserrortext')));
syserrortext=cats('"',tranwrd(symget('syserrortext'),'"','\"'),'"');
put ",""SYSERRORTEXT"" : " syserrortext;
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
put ",""SYSJOBID"" : ""&sysjobid"" ";
@@ -233,7 +233,7 @@ and %superq(SYSPROCESSNAME) ne %str(Compute Server)
put ",""SYSSITE"" : ""&syssite"" ";
sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong;
syswarningtext=quote(trim(symget('syswarningtext')));
syswarningtext=cats('"',tranwrd(symget('syswarningtext'),'"','\"'),'"');
put ",""SYSWARNINGTEXT"" : " syswarningtext;
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
put "}" ;

View File

@@ -31,6 +31,7 @@
@param [in] mode (CREATE) Valid values:
@li CREATE - Create the file (even if it already exists)
@li APPEND - Append to the file (don't overwrite)
@param iftrue= (1=1) Supply a condition for which the macro should be executed
@returns nothing
@@ -44,8 +45,12 @@
,inref=____in /* override default to use own filerefs */
,outref=____out /* override default to use own filerefs */
,mode=CREATE
,iftrue=%str(1=1)
)/*/STORE SOURCE*/;
%local mod;
%if not(%eval(%unquote(&iftrue))) %then %return;
%if &mode=APPEND %then %let mod=mod;
/* these IN and OUT filerefs can point to anything */

View File

@@ -1,5 +1,5 @@
/**
@file mp_cleancsv.sas
@file
@brief Fixes embedded cr / lf / crlf in CSV
@details CSVs will sometimes contain lf or crlf within quotes (eg when
saved by excel). When the termstr is ALSO lf or crlf that can be tricky
@@ -7,14 +7,16 @@
This macro converts any csv to follow the convention of a windows excel file,
applying CRLF line endings and converting embedded cr and crlf to lf.
usage:
Usage:
fileref mycsv "/path/your/csv";
%mp_cleancsv(in=mycsv,out=/path/new.csv)
@param in= provide path or fileref to input csv
@param out= output path or fileref to output csv
@param qchar= quote char - hex code 22 is the double quote.
@param in= (NOTPROVIDED) Provide path or fileref to input csv. If a period is
found, it is assumed to be a file.
@param out= (NOTPROVIDED) Output path or fileref to output csv. If a period
is found, it is assumed to be a file.
@param qchar= ('22'x) Quote char - hex code 22 is the double quote.
@version 9.2
@author Allan Bowe
@@ -56,9 +58,14 @@
else do;
/* outside a quote, change cr and lf to crlf */
if inchar='0D'x then do;
crblank:
put '0D0A'x;
input inchar $char1.;
if inchar ne '0A'x then do;
if inchar='0D'x then do;
/* multiple CR indicates CR formatted file with blank lines */
goto crblank;
end;
else if inchar ne '0A'x then do;
put inchar $char1.;
if inchar=qchar then isq = mod(isq+1,2);
end;

View File

@@ -18,11 +18,14 @@
%mp_deleteconstraints(inds=work.constraints,outds=dropped,execute=YES)
%mp_createconstraints(inds=work.constraints,outds=created,execute=YES)
@param inds= The input table containing the constraint info
@param outds= a table containing the create statements (create_statement column)
@param execute= `YES|NO` - default is NO. To actually create, use YES.
@param inds= (work.mp_getconstraints) The input table containing the
constraint info
@param outds= (work.mp_createconstraints) A table containing the create
statements (create_statement column)
@param execute= (NO) To actually create, use YES.
<h4> SAS Macros </h4>
<h4> Related Files </h4>
@li mp_getconstraints.sas
@version 9.2
@author Allan Bowe
@@ -30,7 +33,7 @@
**/
%macro mp_createconstraints(inds=mp_getconstraints
,outds=mp_createconstraints
,outds=work.mp_createconstraints
,execute=NO
)/*/STORE SOURCE*/;
@@ -64,4 +67,4 @@ data &outds;
output;
run;
%mend mp_createconstraints;
%mend mp_createconstraints;

52
base/mp_dictionary.sas Normal file
View File

@@ -0,0 +1,52 @@
/**
@file mp_dictionary.sas
@brief Creates a portal (libref) into the SQL Dictionary Views
@details Provide a libref and the macro will create a series of views against
each view in the special PROC SQL dictionary libref.
This is useful if you would like to visualise (navigate) the views in a SAS
client such as Base SAS, Enterprise Guide, or Studio (or [Data Controller](
https://datacontroller.io)).
It works by extracting the dictionary.dictionaries view into
YOURLIB.dictionaries, then uses that to create a YOURLIB.{viewName} for every
other dictionary.view, eg:
proc sql;
create view YOURLIB.columns as select * from dictionary.columns;
Usage:
libname demo "/lib/directory";
%mp_dictionary(lib=demo)
Or, to just create them in WORK:
%mp_dictionary()
If you'd just like to browse the dictionary data model, you can also check
out [this article](https://rawsas.com/dictionary-of-dictionaries/).
![](https://user-images.githubusercontent.com/4420615/188278365-2987db97-0594-4a39-ac81-dbacdef5cdc8.png)
@param lib= (WORK) The libref in which to create the views
<h4> Related Files </h4>
@li mp_dictionary.test.sas
@version 9.2
@author Allan Bowe
**/
%macro mp_dictionary(lib=WORK)/*/STORE SOURCE*/;
%local list i mem;
proc sql noprint;
create view &lib..dictionaries as select * from dictionary.dictionaries;
select distinct memname into: list separated by ' ' from &lib..dictionaries;
%do i=1 %to %sysfunc(countw(&list,%str( )));
%let mem=%scan(&list,&i,%str( ));
create view &lib..&mem as select * from dictionary.&mem;
%end;
quit;
%mend mp_dictionary;

View File

@@ -27,6 +27,9 @@
@param [in] maxdepth= (0) Set to a positive integer to indicate the level of
subdirectory scan recursion - eg 3, to go `./3/levels/deep`. For unlimited
recursion, set to MAX.
@param [in] showparent= (NO) By default, the initial parent directory is not
part of the results. Set to YES to include it. For this record only,
directory=filepath.
@param [out] outds= (work.mp_dirlist) The output dataset to create
@param [out] getattrs= (NO) If getattrs=YES then the doptname / foptname
functions are used to scan all properties - any characters that are not
@@ -63,6 +66,7 @@
, fref=0
, outds=work.mp_dirlist
, getattrs=NO
, showparent=NO
, maxdepth=0
, level=0 /* The level of recursion to perform. For internal use only. */
)/*/STORE SOURCE*/;
@@ -145,6 +149,15 @@ data &out_ds(compress=no
output;
end;
rc = dclose(did);
%if &showparent=YES and &level=0 %then %do;
filepath=directory;
file_or_folder='folder';
ext='';
filename=scan(directory,-1,'/\');
msg='';
level=&level;
output;
%end;
stop;
run;
@@ -232,6 +245,9 @@ run;
data _null_;
set &out_ds;
where file_or_folder='folder';
%if &showparent=YES and &level=0 %then %do;
if filepath ne directory;
%end;
length code $10000;
code=cats('%nrstr(%mp_dirlist(path=',filepath,",outds=&outds"
,",getattrs=&getattrs,level=%eval(&level+1),maxdepth=&maxdepth))");

View File

@@ -17,7 +17,7 @@
<h4> SAS Macros </h4>
@li mf_existds.sas
<h4> Related Macros <h4>
<h4> Related Macros </h4>
@li mp_jsonout.sas
@version 9.2

View File

@@ -92,7 +92,7 @@ data _null_;
run;
%if %upcase(&showlog)=YES %then %do;
options ps=max;
options ps=max lrecl=max;
data _null_;
infile &outref;
input;
@@ -100,4 +100,4 @@ run;
run;
%end;
%mend mp_ds2md;
%mend mp_ds2md;

110
base/mp_dsmeta.sas Normal file
View File

@@ -0,0 +1,110 @@
/**
@file
@brief Export dataset metadata to a single output table
@details Exports the dataset attributes and enginehost information, then
converts the datasets into a single output table in the following format:
|ODS_TABLE:$10.|NAME:$100.|VALUE:$1000.|
|---|---|---|
|`ATTRIBUTES `|`Data Set Name `|`SASHELP.CLASS `|
|`ATTRIBUTES `|`Observations `|`19 `|
|`ATTRIBUTES `|`Member Type `|`DATA `|
|`ATTRIBUTES `|`Variables `|`5 `|
|`ATTRIBUTES `|`Engine `|`V9 `|
|`ATTRIBUTES `|`Indexes `|`0 `|
|`ATTRIBUTES `|`Created `|`06/08/2020 00:59:14 `|
|`ATTRIBUTES `|`Observation Length `|`40 `|
|`ATTRIBUTES `|`Last Modified `|`06/08/2020 00:59:14 `|
|`ATTRIBUTES `|`Deleted Observations `|`0 `|
|`ATTRIBUTES `|`Protection `|`. `|
|`ATTRIBUTES `|`Compressed `|`NO `|
|`ATTRIBUTES `|`Data Set Type `|`. `|
|`ATTRIBUTES `|`Sorted `|`NO `|
|`ATTRIBUTES `|`Label `|`Student Data `|
|`ATTRIBUTES `|`Data Representation `|`SOLARIS_X86_64, LINUX_X86_64, ALPHA_TRU64, LINUX_IA64 `|
|`ATTRIBUTES `|`Encoding `|`us-ascii ASCII (ANSI) `|
|`ENGINEHOST `|`Data Set Page Size `|`65536 `|
|`ENGINEHOST `|`Number of Data Set Pages `|`1 `|
|`ENGINEHOST `|`First Data Page `|`1 `|
|`ENGINEHOST `|`Max Obs per Page `|`1632 `|
|`ENGINEHOST `|`Obs in First Data Page `|`19 `|
|`ENGINEHOST `|`Number of Data Set Repairs `|`0 `|
|`ENGINEHOST `|`Filename `|`/opt/sas/sas9/SASHome/SASFoundation/9.4/sashelp/class.sas7bdat `|
|`ENGINEHOST `|`Release Created `|`9.0401M7 `|
|`ENGINEHOST `|`Host Created `|`Linux `|
|`ENGINEHOST `|`Inode Number `|`28314616 `|
|`ENGINEHOST `|`Access Permission `|`rw-r--r-- `|
|`ENGINEHOST `|`Owner Name `|`sas `|
|`ENGINEHOST `|`File Size `|`128KB `|
|`ENGINEHOST `|`File Size (bytes) `|`131072 `|
Example usage:
%mp_dsmeta(sashelp.class,outds=work.mymeta)
proc print data=work.mymeta;
run;
For more details on creating datasets from PROC CONTENTS check out this
excellent [paper](
https://support.sas.com/resources/papers/proceedings14/1549-2014.pdf) by
[Louise Hadden](https://www.linkedin.com/in/louisehadden/).
@param libds The library.dataset to export the metadata for
@param outds= (work.dsmeta) The output table to contain the metadata
<h4> Related Files </h4>
@li mp_dsmeta.test.sas
@li mp_getcols.sas
@li mp_getdbml.sas
@li mp_getddl.sas
@li mp_getformats.sas
@li mp_getpk.sas
@li mp_guesspk.sas
**/
%macro mp_dsmeta(libds,outds=work.dsmeta);
%local ds1 ds2;
data;run; %let ds1=&syslast;
data;run; %let ds2=&syslast;
/* setup the ODS capture */
ods output attributes=&ds1 enginehost=&ds2;
/* export the metadata */
proc contents data=&libds;
run;
/* load it into a single table */
data &outds (keep=ods_table name value);
length ods_table $10 name label2 label1 label $100
value cvalue cvalue1 cvalue2 $1000
nvalue nvalue1 nvalue2 8;
if _n_=1 then call missing (of _all_);
* putlog (_all_)(=);
set &ds1 (in=atrs) &ds2 (in=eng);
if atrs then do;
ods_table='ATTRIBUTES';
name=coalescec(label1,label);
value=coalescec(cvalue1,cvalue,put(coalesce(nvalue1,nvalue),best.));
output;
if label2 ne '' then do;
name=label2;
value=coalescec(cvalue2,put(nvalue2,best.));
output;
end;
end;
else if eng then do;
ods_table='ENGINEHOST';
name=coalescec(label1,label);
value=coalescec(cvalue1,cvalue,put(coalesce(nvalue1,nvalue),best.));
output;
end;
run;
proc sql;
drop table &ds1, &ds2;
%mend mp_dsmeta;

View File

@@ -92,7 +92,8 @@ data &outds;
/*length GROUP_LOGIC SUBGROUP_LOGIC $3 SUBGROUP_ID 8 VARIABLE_NM $32
OPERATOR_NM $10 RAW_VALUE $4000;*/
set &inds;
length reason_cd $4032 vtype $1 vnum dsid 8;
length reason_cd $4032 vtype $1 vnum dsid 8 tmp $4000;
drop tmp;
/* quick check to ensure column exists */
if upcase(VARIABLE_NM) not in
@@ -168,18 +169,32 @@ data &outds;
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 start/end bracket in RAW_VALUE';
putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;
call symputx('reason_cd',reason_cd,'l');
call symputx('nobs',_n_,'l');
output;
if OPERATOR_NM in ('IN','NOT IN','BETWEEN') then do;
if OPERATOR_NM='BETWEEN' then raw_value1=tranwrd(raw_value,' AND ',',');
else do;
if substr(raw_value,1,1) ne '('
or substr(cats(reverse(raw_value)),1,1) ne ')'
then do;
REASON_CD='Missing start/end bracket in RAW_VALUE';
putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;
call symputx('reason_cd',reason_cd,'l');
call symputx('nobs',_n_,'l');
output;
end;
else raw_value1=substr(raw_value,2,max(length(raw_value)-2,0));
end;
/* we now have a comma seperated list of values */
if vtype='N' then do i=1 to countc(raw_value1, ',')+1;
tmp=scan(raw_value1,i,',');
if cats(tmp) ne '.' and input(tmp, ?? 8.) eq . then do;
REASON_CD='Non Numeric value provided';
putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;
call symputx('reason_cd',reason_cd,'l');
call symputx('nobs',_n_,'l');
output;
end;
return;
end;
else raw_value1=substr(raw_value,2,max(length(raw_value)-2,0));
end;
else raw_value1=raw_value;

View File

@@ -94,8 +94,11 @@ create table &outds as
/**
* We cannot apply this clause to the underlying dictionary table. See:
* https://communities.sas.com/t5/SAS-Programming/Unexpected-Where-Clause-behaviour-in-dictionary-TABLE/m-p/771554#M244867
* cannot use`where calculated libref="&lib"` either as it will STILL execute
* all the underlying constraint queries, causing exception errors in some
* cases: https://github.com/sasjs/core/issues/283
*/
where calculated libref="&lib"
where a.TABLE_CATALOG="&lib"
%if "&ds" ne "" %then %do;
and upcase(a.TABLE_NAME)="&ds"
and upcase(b.TABLE_NAME)="&ds"

View File

@@ -130,13 +130,13 @@ run;
%local x curds;
%if &flavour=SAS %then %do;
data _null_;
file &fref mod;
put "/* SAS Flavour DDL for %upcase(&libref).&curds */";
put "proc sql;";
run;
%do x=1 %to %sysfunc(countw(&dsnlist));
%let curds=%scan(&dsnlist,&x);
data _null_;
file &fref mod;
put "/* SAS Flavour DDL for %upcase(&libref).&curds */";
put "proc sql;";
run;
data _null_;
file &fref mod;
length lab $1024 typ $20;

View File

@@ -34,6 +34,7 @@
@param [out] outds= (work.mp_getpk) The name of the output table to create.
<h4> SAS Macros </h4>
@li mf_existfeature.sas
@li mf_getengine.sas
@li mf_getschema.sas
@li mp_dropmembers.sas
@@ -229,7 +230,12 @@ create table work.&tabs1 as select
libname as libref
,upcase(memname) as dsn
,memtype
%if %mf_existfeature(DBMS_MEMTYPE)=1 %then %do;
,dbms_memtype
%end;
%else %do;
,'n/a' as dbms_memtype format=$32.
%end;
,typemem
,memlabel
,nvar

View File

@@ -11,7 +11,7 @@
put hashkey=;
run;
![sas md5 hash dataset log results](https://i.imgur.com/MqF98vk.png)
![sas md5 hash dataset log results](https://i.4gl.io/1/KorUKoyE05.png/raw)
<h4> SAS Macros </h4>
@li mf_getattrn.sas
@@ -21,11 +21,12 @@
<h4> Related Files </h4>
@li mp_hashdataset.test.sas
@li mp_hashdirectory.sas
@param [in] libds dataset to hash
@param [in] salt= Provide a salt (could be, for instance, the dataset name)
@param [in] iftrue= A condition under which the macro should be executed.
@param [out] outds= (work.mf_hashdataset) The output dataset to create. This
@param [in] iftrue= (1=1) A condition under which the macro should be executed
@param [out] outds= (work._data_) The output dataset to create. This
will contain one column (hashkey) with one observation (a $hex32.
representation of the input hash)
|hashkey:$32.|

149
base/mp_hashdirectory.sas Normal file
View File

@@ -0,0 +1,149 @@
/**
@file
@brief Returns a unique hash for each file in a directory
@details Hashes each file in each directory, and then hashes the hashes to
create a hash for each directory also.
This makes use of the new `hashing_file()` and `hashing` functions, available
since 9.4m6. Interestingly, these can even be used in pure macro, eg:
%put %sysfunc(hashing_file(md5,/path/to/file.blob,0));
Usage:
%let fpath=/some/directory;
%mp_hashdirectory(&fpath,outds=myhash,maxdepth=2)
data _null_;
set work.myhash;
put (_all_)(=);
run;
Whilst files are hashed in their entirety, the logic for creating a folder
hash is as follows:
@li Sort the files by filename (case sensitive, uppercase then lower)
@li Take the first 100 hashes, concatenate and hash
@li Concatenate this hash with another 100 hashes and hash again
@li Continue until the end of the folder. This is the folder hash
@li If a folder contains other folders, start from the bottom of the tree -
the folder hashes cascade upwards so you know immediately if there is a
change in a sub/sub directory
@li If the folder has no content (empty) then it is ignored. No hash created.
<h4> SAS Macros </h4>
@li mp_dirlist.sas
<h4> Related Files </h4>
@li mp_hashdataset.sas
@li mp_hashdirectory.test.sas
@li mp_md5.sas
@param [in] inloc Full filepath of the file to be hashed (unquoted)
@param [in] iftrue= (1=1) A condition under which the macro should be executed
@param [in] maxdepth= (0) Set to a positive integer to indicate the level of
subdirectory scan recursion - eg 3, to go `./3/levels/deep`. For unlimited
recursion, set to MAX.
@param [in] method= (MD5) the hashing method to use. Available options:
@li MD5
@li SH1
@li SHA256
@li SHA384
@li SHA512
@li CRC32
@param [out] outds= (work.mp_hashdirectory) The output dataset. Contains:
@li directory - the parent folder
@li file_hash - the hash output
@li hash_duration - how long the hash took (first hash always takes longer)
@li file_path - /full/path/to/each/file.ext
@li file_or_folder - contains either "file" or "folder"
@li level - the depth of the directory (top level is 0)
@version 9.4m6
@author Allan Bowe
**/
%macro mp_hashdirectory(inloc,
outds=work.mp_hashdirectory,
method=MD5,
maxdepth=0,
iftrue=%str(1=1)
)/*/STORE SOURCE*/;
%local curlevel tempds ;
%if not(%eval(%unquote(&iftrue))) %then %return;
/* get the directory listing */
%mp_dirlist(path=&inloc, outds=&outds, maxdepth=&maxdepth, showparent=YES)
/* create the hashes */
data &outds;
set &outds (rename=(filepath=file_path));
length FILE_HASH $32 HASH_DURATION 8;
keep directory file_hash hash_duration file_path file_or_folder level;
ts=datetime();
if file_or_folder='file' then do;
file_hash=hashing_file("&method",cats(file_path),0);
end;
hash_duration=datetime()-ts;
run;
proc sort data=&outds ;
by descending level directory file_path;
run;
data _null_;
set &outds;
call symputx('maxlevel',level,'l');
stop;
run;
/* now hash the hashes to populate folder hashes, starting from the bottom */
%do curlevel=&maxlevel %to 0 %by -1;
data work._data_ (keep=directory file_hash);
set &outds;
where level=&curlevel;
by descending level directory file_path;
length str $32767 tmp_hash $32;
retain str tmp_hash ;
/* reset vars when starting a new directory */
if first.directory then do;
str='';
tmp_hash='';
i=0;
end;
/* hash each chunk of 100 file paths */
i+1;
str=cats(str,file_hash);
if mod(i,100)=0 or last.directory then do;
tmp_hash=hashing("&method",cats(tmp_hash,str));
str='';
end;
/* output the hash at directory level */
if last.directory then do;
file_hash=tmp_hash;
output;
end;
if last.level then stop;
run;
%let tempds=&syslast;
/* join the hash back into the main table */
proc sql undo_policy=none;
create table &outds as
select a.directory
,coalesce(b.file_hash,a.file_hash) as file_hash
,a.hash_duration
,a.file_path
,a.file_or_folder
,a.level
from &outds a
left join &tempds b
on a.file_path=b.directory
order by level desc, directory, file_path;
drop table &tempds;
%end;
%mend mp_hashdirectory;

View File

@@ -28,7 +28,7 @@
%mp_jsonout(OPEN,jref=tmp)
%mp_jsonout(OBJ,class,jref=tmp)
%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=YES)
%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)
%mp_jsonout(CLOSE,jref=tmp)
data _null_;
@@ -55,12 +55,14 @@
@li DATASTEP (more reliable when data has non standard characters)
@param [in] missing= (NULL) Special numeric missing values can be sent as NULL
(eg `null`) or as STRING values (eg `".a"` or `".b"`)
@param [in] showmeta= (NO) Set to YES to output metadata alongside each table,
@param [in] showmeta= (N) Set to Y to output metadata alongside each table,
such as the column formats and types. The metadata is contained inside an
object with the same name as the table but prefixed with a dollar sign - ie,
`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`
@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows
that should be converted to JSON
<h4> Related Macros <h4>
<h4> Related Files </h4>
@li mp_ds2fmtds.sas
@version 9.2
@@ -68,14 +70,16 @@
@source https://github.com/sasjs/core
**/
%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y
,engine=DATASTEP
,missing=NULL
,showmeta=NO
,showmeta=N
,maxobs=MAX
)/*/STORE SOURCE*/;
%local tempds colinfo fmtds i numcols;
%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval
tmpds1 tmpds2 tmpds3 tmpds4;
%let numcols=0;
%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);
%if &action=OPEN %then %do;
options nobomfile;
@@ -111,7 +115,7 @@
by varnum;
run;
/* move meta to mac vars */
data _null_;
data &colinfo;
if _n_=1 then call symputx('numcols',nobs,'l');
set &colinfo end=last nobs=nobs;
name=upcase(name);
@@ -122,7 +126,6 @@
if format='' then fmt=cats('$',length,'.');
else if formatl=0 then fmt=cats(format,'.');
else fmt=cats(format,formatl,'.');
newlen=max(formatl,length);
end;
else do;
typelong='num';
@@ -130,23 +133,26 @@
else if formatl=0 then fmt=cats(format,'.');
else if formatd=0 then fmt=cats(format,formatl,'.');
else fmt=cats(format,formatl,'.',formatd);
/* needs to be wide, for datetimes etc */
newlen=max(length,formatl,24);
end;
/* 32 char unique name */
newname='sasjs'!!substr(cats(put(md5(name),$hex32.)),1,27);
call symputx(cats('name',_n_),name,'l');
call symputx(cats('newname',_n_),newname,'l');
call symputx(cats('len',_n_),newlen,'l');
call symputx(cats('length',_n_),length,'l');
call symputx(cats('fmt',_n_),fmt,'l');
call symputx(cats('type',_n_),type,'l');
call symputx(cats('typelong',_n_),typelong,'l');
call symputx(cats('label',_n_),coalescec(label,name),'l');
/* overwritten when fmt=Y and a custom format exists in catalog */
if typelong='num' then call symputx(cats('fmtlen',_n_),200,'l');
else call symputx(cats('fmtlen',_n_),min(32767,ceil((length+10)*1.5)),'l');
run;
%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);
proc sql;
select count(*) into: lastobs from &ds;
%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));
%if &engine=PROCJSON %then %do;
%if &missing=STRING %then %do;
@@ -154,7 +160,9 @@
%put &sysmacroname: Switching to DATASTEP engine;
%goto datastep;
%end;
data &tempds;set &ds;
data &tempds;
set &ds;
&stmt_obs;
%if &fmt=N %then format _numeric_ best32.;;
/* PRETTY is necessary to avoid line truncation in large files */
filename _sjs2 temp lrecl=131068 encoding='utf-8';
@@ -181,26 +189,99 @@
%end;
%if &fmt=Y %then %do;
data _data_;
/**
* Extract format definitions
* First, by getting library locations from dictionary.formats
* Then, by exporting the width using proc format
* Cannot use maxw from sashelp.vformat as not always populated
* Cannot use fmtinfo() as not supported in all flavours
*/
%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);
%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);
%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);
%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);
proc sql noprint;
create table &tmpds1 as
select cats(libname,'.',memname) as FMTCAT,
FMTNAME
from dictionary.formats
where fmttype='F' and libname is not null
and fmtname in (select format from &colinfo where format is not null)
order by 1;
create table &tmpds2(
FMTNAME char(32),
LENGTH num
);
%local catlist cat fmtlist i;
select distinct fmtcat into: catlist separated by ' ' from &tmpds1;
%do i=1 %to %sysfunc(countw(&catlist,%str( )));
%let cat=%scan(&catlist,&i,%str( ));
proc sql;
select distinct fmtname into: fmtlist separated by ' '
from &tmpds1 where fmtcat="&cat";
proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);
select &fmtlist;
run;
proc sql;
insert into &tmpds2 select distinct fmtname,length from &tmpds3;
%end;
proc sql;
create table &tmpds4 as
select a.*, b.length as MAXW
from &colinfo a
left join &tmpds2 b
on cats(a.format)=cats(upcase(b.fmtname))
order by a.varnum;
data _null_;
set &tmpds4;
if not missing(maxw);
call symputx(
cats('fmtlen',_n_),
/* vars need extra padding due to JSON escaping of special chars */
min(32767,ceil((max(length,maxw)+10)*1.5))
,'l'
);
run;
/* configure varlenchk - as we are explicitly shortening the variables */
%let optval=%sysfunc(getoption(varlenchk));
options varlenchk=NOWARN;
data _data_(compress=char);
/* shorten the new vars */
length
%do i=1 %to &numcols;
&&name&i $&&fmtlen&i
%end;
;
/* rename on entry */
set &ds(rename=(
%do i=1 %to &numcols;
&&name&i=&&newname&i
&&name&i=&&newname&i
%end;
));
&stmt_obs;
drop
%do i=1 %to &numcols;
&&newname&i
%end;
;
%do i=1 %to &numcols;
length &&name&i $&&len&i;
%if &&typelong&i=num %then %do;
&&name&i=left(put(&&newname&i,&&fmt&i));
&&name&i=cats(put(&&newname&i,&&fmt&i));
%end;
%else %do;
&&name&i=put(&&newname&i,&&fmt&i);
%end;
drop &&newname&i;
%end;
if _error_ then call symputx('syscc',1012);
if _error_ then do;
call symputx('syscc',1012);
stop;
end;
run;
%let fmtds=&syslast;
options varlenchk=&optval;
%end;
proc format; /* credit yabwon for special null removal */
@@ -219,8 +300,8 @@
attrib _all_ label='';
%do i=1 %to &numcols;
%if &&typelong&i=char or &fmt=Y %then %do;
length &&name&i $32767;
format &&name&i $32767.;
length &&name&i $&&fmtlen&i...;
format &&name&i $&&fmtlen&i...;
%end;
%end;
%if &fmt=Y %then %do;
@@ -229,10 +310,11 @@
%else %do;
set &ds;
%end;
&stmt_obs;
format _numeric_ bart.;
%do i=1 %to &numcols;
%if &&typelong&i=char or &fmt=Y %then %do;
if findc(&&name&i,'"\'!!'0A0D09000E0F01021011'x) then do;
if findc(&&name&i,'"\'!!'0A0D09000E0F010210111A'x) then do;
&&name&i='"'!!trim(
prxchange('s/"/\\"/',-1, /* double quote */
prxchange('s/\x0A/\n/',-1, /* new line */
@@ -245,8 +327,9 @@
prxchange('s/\x02/\\u0002/',-1, /* STX */
prxchange('s/\x10/\\u0010/',-1, /* DLE */
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
prxchange('s/\\/\\\\/',-1,&&name&i)
))))))))))))!!'"';
)))))))))))))!!'"';
end;
else &&name&i=quote(cats(&&name&i));
%end;
@@ -285,10 +368,11 @@
proc sql;
drop table &colinfo, &tempds;
%if &showmeta=YES %then %do;
%if %substr(&showmeta,1,1)=Y %then %do;
filename _sjs4 temp lrecl=131068 encoding='utf-8';
data _null_;
file _sjs4;
length label $350;
put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";
do i=1 to &numcols;
name=quote(trim(symget(cats('name',i))));

View File

@@ -1,13 +1,14 @@
/**
@file
@brief Logs the time the macro was executed in a control dataset.
@details If the dataset does not exist, it is created. Usage:
@brief Logs a message in a dataset every time it is invoked
@details If the dataset does not exist, it is created.
Usage:
%mp_perflog(started)
%mp_perflog()
%mp_perflog(startanew,libds=work.newdataset)
%mp_perflog(finished,libds=work.newdataset)
%mp_perflog(finished)
%mp_perflog(started)
%mp_perflog()
%mp_perflog(startanew,libds=work.newdataset)
%mp_perflog(finished,libds=work.newdataset)
%mp_perflog(finished)
@param label Provide label to go into the control dataset

View File

@@ -154,7 +154,9 @@ run;
%else %let vlist=%mf_getvarlist(&libds,dlm=%str(,),quote=DOUBLE);
data &ds4;
length &inds_keep $41 tgtvar_nm $32;
length &inds_keep $41 tgtvar_nm $32 _label_ $256;
if _n_=1 then call missing(_label_);
drop _label_;
set &ds2 &ds3 indsname=&inds_auto;
tgtvar_nm=upcase(tgtvar_nm);

View File

@@ -93,14 +93,16 @@ data _null_;
file sasjs lrecl=3000 ;
put "/* Created on %sysfunc(datetime(),datetime19.) by %mf_getuser() */";
/* WEBOUT BEGIN */
put ' ';
put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y ';
put ' ,engine=DATASTEP ';
put ' ,missing=NULL ';
put ' ,showmeta=NO ';
put ' ,showmeta=N ';
put ' ,maxobs=MAX ';
put ')/*/STORE SOURCE*/; ';
put '%local tempds colinfo fmtds i numcols; ';
put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval ';
put ' tmpds1 tmpds2 tmpds3 tmpds4; ';
put '%let numcols=0; ';
put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;); ';
put ' ';
put '%if &action=OPEN %then %do; ';
put ' options nobomfile; ';
@@ -136,7 +138,7 @@ data _null_;
put ' by varnum; ';
put ' run; ';
put ' /* move meta to mac vars */ ';
put ' data _null_; ';
put ' data &colinfo; ';
put ' if _n_=1 then call symputx(''numcols'',nobs,''l''); ';
put ' set &colinfo end=last nobs=nobs; ';
put ' name=upcase(name); ';
@@ -147,7 +149,6 @@ data _null_;
put ' if format='''' then fmt=cats(''$'',length,''.''); ';
put ' else if formatl=0 then fmt=cats(format,''.''); ';
put ' else fmt=cats(format,formatl,''.''); ';
put ' newlen=max(formatl,length); ';
put ' end; ';
put ' else do; ';
put ' typelong=''num''; ';
@@ -155,23 +156,26 @@ data _null_;
put ' else if formatl=0 then fmt=cats(format,''.''); ';
put ' else if formatd=0 then fmt=cats(format,formatl,''.''); ';
put ' else fmt=cats(format,formatl,''.'',formatd); ';
put ' /* needs to be wide, for datetimes etc */ ';
put ' newlen=max(length,formatl,24); ';
put ' end; ';
put ' /* 32 char unique name */ ';
put ' newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27); ';
put ' ';
put ' call symputx(cats(''name'',_n_),name,''l''); ';
put ' call symputx(cats(''newname'',_n_),newname,''l''); ';
put ' call symputx(cats(''len'',_n_),newlen,''l''); ';
put ' call symputx(cats(''length'',_n_),length,''l''); ';
put ' call symputx(cats(''fmt'',_n_),fmt,''l''); ';
put ' call symputx(cats(''type'',_n_),type,''l''); ';
put ' call symputx(cats(''typelong'',_n_),typelong,''l''); ';
put ' call symputx(cats(''label'',_n_),coalescec(label,name),''l''); ';
put ' /* overwritten when fmt=Y and a custom format exists in catalog */ ';
put ' if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l''); ';
put ' else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l''); ';
put ' run; ';
put ' ';
put ' %let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
put ' proc sql; ';
put ' select count(*) into: lastobs from &ds; ';
put ' %if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs)); ';
put ' ';
put ' %if &engine=PROCJSON %then %do; ';
put ' %if &missing=STRING %then %do; ';
@@ -179,7 +183,9 @@ data _null_;
put ' %put &sysmacroname: Switching to DATASTEP engine; ';
put ' %goto datastep; ';
put ' %end; ';
put ' data &tempds;set &ds; ';
put ' data &tempds; ';
put ' set &ds; ';
put ' &stmt_obs; ';
put ' %if &fmt=N %then format _numeric_ best32.;; ';
put ' /* PRETTY is necessary to avoid line truncation in large files */ ';
put ' filename _sjs2 temp lrecl=131068 encoding=''utf-8''; ';
@@ -206,26 +212,99 @@ data _null_;
put ' %end; ';
put ' ';
put ' %if &fmt=Y %then %do; ';
put ' data _data_; ';
put ' /** ';
put ' * Extract format definitions ';
put ' * First, by getting library locations from dictionary.formats ';
put ' * Then, by exporting the width using proc format ';
put ' * Cannot use maxw from sashelp.vformat as not always populated ';
put ' * Cannot use fmtinfo() as not supported in all flavours ';
put ' */ ';
put ' %let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
put ' %let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
put ' %let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
put ' %let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
put ' proc sql noprint; ';
put ' create table &tmpds1 as ';
put ' select cats(libname,''.'',memname) as FMTCAT, ';
put ' FMTNAME ';
put ' from dictionary.formats ';
put ' where fmttype=''F'' and libname is not null ';
put ' and fmtname in (select format from &colinfo where format is not null) ';
put ' order by 1; ';
put ' create table &tmpds2( ';
put ' FMTNAME char(32), ';
put ' LENGTH num ';
put ' ); ';
put ' %local catlist cat fmtlist i; ';
put ' select distinct fmtcat into: catlist separated by '' '' from &tmpds1; ';
put ' %do i=1 %to %sysfunc(countw(&catlist,%str( ))); ';
put ' %let cat=%scan(&catlist,&i,%str( )); ';
put ' proc sql; ';
put ' select distinct fmtname into: fmtlist separated by '' '' ';
put ' from &tmpds1 where fmtcat="&cat"; ';
put ' proc format lib=&cat cntlout=&tmpds3(keep=fmtname length); ';
put ' select &fmtlist; ';
put ' run; ';
put ' proc sql; ';
put ' insert into &tmpds2 select distinct fmtname,length from &tmpds3; ';
put ' %end; ';
put ' ';
put ' proc sql; ';
put ' create table &tmpds4 as ';
put ' select a.*, b.length as MAXW ';
put ' from &colinfo a ';
put ' left join &tmpds2 b ';
put ' on cats(a.format)=cats(upcase(b.fmtname)) ';
put ' order by a.varnum; ';
put ' data _null_; ';
put ' set &tmpds4; ';
put ' if not missing(maxw); ';
put ' call symputx( ';
put ' cats(''fmtlen'',_n_), ';
put ' /* vars need extra padding due to JSON escaping of special chars */ ';
put ' min(32767,ceil((max(length,maxw)+10)*1.5)) ';
put ' ,''l'' ';
put ' ); ';
put ' run; ';
put ' ';
put ' /* configure varlenchk - as we are explicitly shortening the variables */ ';
put ' %let optval=%sysfunc(getoption(varlenchk)); ';
put ' options varlenchk=NOWARN; ';
put ' data _data_(compress=char); ';
put ' /* shorten the new vars */ ';
put ' length ';
put ' %do i=1 %to &numcols; ';
put ' &&name&i $&&fmtlen&i ';
put ' %end; ';
put ' ; ';
put ' /* rename on entry */ ';
put ' set &ds(rename=( ';
put ' %do i=1 %to &numcols; ';
put ' &&name&i=&&newname&i ';
put ' &&name&i=&&newname&i ';
put ' %end; ';
put ' )); ';
put ' &stmt_obs; ';
put ' ';
put ' drop ';
put ' %do i=1 %to &numcols; ';
put ' &&newname&i ';
put ' %end; ';
put ' ; ';
put ' %do i=1 %to &numcols; ';
put ' length &&name&i $&&len&i; ';
put ' %if &&typelong&i=num %then %do; ';
put ' &&name&i=left(put(&&newname&i,&&fmt&i)); ';
put ' &&name&i=cats(put(&&newname&i,&&fmt&i)); ';
put ' %end; ';
put ' %else %do; ';
put ' &&name&i=put(&&newname&i,&&fmt&i); ';
put ' %end; ';
put ' drop &&newname&i; ';
put ' %end; ';
put ' if _error_ then call symputx(''syscc'',1012); ';
put ' if _error_ then do; ';
put ' call symputx(''syscc'',1012); ';
put ' stop; ';
put ' end; ';
put ' run; ';
put ' %let fmtds=&syslast; ';
put ' options varlenchk=&optval; ';
put ' %end; ';
put ' ';
put ' proc format; /* credit yabwon for special null removal */ ';
@@ -244,8 +323,8 @@ data _null_;
put ' attrib _all_ label=''''; ';
put ' %do i=1 %to &numcols; ';
put ' %if &&typelong&i=char or &fmt=Y %then %do; ';
put ' length &&name&i $32767; ';
put ' format &&name&i $32767.; ';
put ' length &&name&i $&&fmtlen&i...; ';
put ' format &&name&i $&&fmtlen&i...; ';
put ' %end; ';
put ' %end; ';
put ' %if &fmt=Y %then %do; ';
@@ -254,10 +333,11 @@ data _null_;
put ' %else %do; ';
put ' set &ds; ';
put ' %end; ';
put ' &stmt_obs; ';
put ' format _numeric_ bart.; ';
put ' %do i=1 %to &numcols; ';
put ' %if &&typelong&i=char or &fmt=Y %then %do; ';
put ' if findc(&&name&i,''"\''!!''0A0D09000E0F01021011''x) then do; ';
put ' if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do; ';
put ' &&name&i=''"''!!trim( ';
put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
@@ -270,8 +350,9 @@ data _null_;
put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ ';
put ' prxchange(''s/\\/\\\\/'',-1,&&name&i) ';
put ' ))))))))))))!!''"''; ';
put ' )))))))))))))!!''"''; ';
put ' end; ';
put ' else &&name&i=quote(cats(&&name&i)); ';
put ' %end; ';
@@ -310,10 +391,11 @@ data _null_;
put ' proc sql; ';
put ' drop table &colinfo, &tempds; ';
put ' ';
put ' %if &showmeta=YES %then %do; ';
put ' %if %substr(&showmeta,1,1)=Y %then %do; ';
put ' filename _sjs4 temp lrecl=131068 encoding=''utf-8''; ';
put ' data _null_; ';
put ' file _sjs4; ';
put ' length label $350; ';
put ' put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{"; ';
put ' do i=1 to &numcols; ';
put ' name=quote(trim(symget(cats(''name'',i)))); ';
@@ -365,8 +447,8 @@ data _null_;
put ' %quote(&user) ';
put ' ';
put '%mend mf_getuser; ';
put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL ';
put ' ,showmeta=NO ';
put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL ';
put ' ,showmeta=N,maxobs=MAX,workobs=0 ';
put '); ';
put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug ';
put ' sasjs_tables; ';
@@ -433,15 +515,14 @@ data _null_;
put ' ';
put '%else %if &action=ARR or &action=OBJ %then %do; ';
put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref ';
put ' ,engine=&jsonengine,missing=&missing,showmeta=&showmeta ';
put ' ,engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs ';
put ' ) ';
put '%end; ';
put '%else %if &action=CLOSE %then %do; ';
put ' /* To avoid issues with _webout on EBI we use a temporary file */ ';
put ' filename _sjsref temp lrecl=131068; ';
put ' %if %str(&_debug) ge 131 %then %do; ';
put ' /* if debug mode, send back first 10 records of each work table also */ ';
put ' options obs=10; ';
put ' %if %str(&workobs) > 0 %then %do; ';
put ' /* if debug mode, send back first XX records of each work table also */ ';
put ' data;run;%let tempds=%scan(&syslast,2,.); ';
put ' ods output Members=&tempds; ';
put ' proc datasets library=WORK memtype=data; ';
@@ -465,7 +546,9 @@ data _null_;
put ' put " ""&wt"" : {"; ';
put ' put ''"nlobs":'' nlobs; ';
put ' put '',"nvars":'' nvars; ';
put ' %mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=YES) ';
put ' %mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y,maxobs=10 ';
put ' ,maxobs=&workobs ';
put ' ) ';
put ' data _null_; file _sjsref mod encoding=''utf-8''; ';
put ' put "}"; ';
put ' %end; ';
@@ -475,26 +558,33 @@ data _null_;
put ' %end; ';
put ' /* close off json */ ';
put ' data _null_;file _sjsref mod encoding=''utf-8''; ';
put ' _PROGRAM=quote(trim(resolve(symget(''_PROGRAM'')))); ';
put ' put ",""SYSUSERID"" : ""&sysuserid"" "; ';
put ' put ",""MF_GETUSER"" : ""%mf_getuser()"" "; ';
put ' length SYSPROCESSNAME syserrortext syswarningtext autoexec $512; ';
put ' put ",""_DEBUG"" : ""&_debug"" "; ';
put ' _METAUSER=quote(trim(symget(''_METAUSER''))); ';
put ' put ",""_METAUSER"": " _METAUSER; ';
put ' _METAPERSON=quote(trim(symget(''_METAPERSON''))); ';
put ' put '',"_METAPERSON": '' _METAPERSON; ';
put ' _PROGRAM=quote(trim(resolve(symget(''_PROGRAM'')))); ';
put ' put '',"_PROGRAM" : '' _PROGRAM ; ';
put ' autoexec=quote(urlencode(trim(getoption(''autoexec'')))); ';
put ' put '',"AUTOEXEC" : '' autoexec; ';
put ' put ",""MF_GETUSER"" : ""%mf_getuser()"" "; ';
put ' put ",""SYSCC"" : ""&syscc"" "; ';
put ' put ",""SYSENCODING"" : ""&sysencoding"" "; ';
put ' syserrortext=quote(cats(symget(''SYSERRORTEXT''))); ';
put ' syserrortext=cats(''"'',tranwrd(symget(''syserrortext''),''"'',''\"''),''"''); ';
put ' put '',"SYSERRORTEXT" : '' syserrortext; ';
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
put ' put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" "; ';
put ' put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" "; ';
put ' SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME))); ';
put ' put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME; ';
put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
put ' put ",""SYSSCPL"" : ""&sysscpl"" "; ';
put ' put ",""SYSSITE"" : ""&syssite"" "; ';
put ' put ",""SYSUSERID"" : ""&sysuserid"" "; ';
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
put ' put '',"SYSVLONG" : '' sysvlong; ';
put ' syswarningtext=quote(cats(symget(''SYSWARNINGTEXT''))); ';
put ' syswarningtext=cats(''"'',tranwrd(symget(''syswarningtext''),''"'',''\"''),''"''); ';
put ' put '',"SYSWARNINGTEXT" : '' syswarningtext; ';
put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" ''; ';
put ' length memsize $32; ';

View File

@@ -58,7 +58,7 @@ data _null_;
cnt=1;
do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);
rc=metadata_getattr(tsuri,"Name",value);
put tsuri= value=;
&mD.put tsuri= value=;
if value="SourceCode" then do;
/* found it! */
rc=metadata_getattr(tsuri,"Id",value);

67
meta/mm_getstpinfo.sas Normal file
View File

@@ -0,0 +1,67 @@
/**
@file
@brief Get the properties of a Stored Process
@details Extracts various properties and creates an output table in the
structure below:
|STP_URI:$200.|SERVERCONTEXT:$200.|STOREDPROCESSCONFIGURATION:$1000.|SOURCECODE_FIRST32K:$32767.|PATH:$76.|
|---|---|---|---|---|
|`A5DN9TDQ.BH0000C8 `|`SASApp `|`<?xml version="1.0" encoding="UTF-8"?><StoredProcess><ServerContext LogicalServerType="Sps" OtherAllowed="false"/><ResultCapabilities Package="false" Streaming="true"/><OutputParameters/></StoredProcess> `|`%put first 32767 bytes of code; `|`/path/to/my/stp`|
@param [in] pgm The metadata path of the Stored Process
@param [out] outds= (work.mm_getstpinfo) The output table to create
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
<h4> Related Files </h4>
@li mm_getstpcode.sas
@li mm_getstps.sas
@li mm_createstp.sas
@li mm_deletestp.sas
**/
%macro mm_getstpinfo(pgm
,outds=work.mm_getstpinfo
,mDebug=0
);
%local mD;
%if &mDebug=1 %then %let mD=;
%else %let mD=%str(*);
%&mD.put Executing &sysmacroname..sas;
%&mD.put _local_;
data &outds;
length type stp_uri tsuri servercontext value $200
StoredProcessConfiguration $1000 sourcecode_first32k $32767;
keep path stp_uri sourcecode_first32k StoredProcessConfiguration
servercontext;
call missing (of _all_);
path="&pgm(StoredProcess)";
/* first, find the STP ID */
if metadata_pathobj("",path,"StoredProcess",type,stp_uri)>0 then do;
/* get attributes */
cnt=1;
do while (metadata_getnasn(stp_uri,"Notes",cnt,tsuri)>0);
rc1=metadata_getattr(tsuri,"Name",value);
&mD.put tsuri= value=;
if value="SourceCode" then do;
rc2=metadata_getattr(tsuri,"StoredText",sourcecode_first32k);
end;
else if value="Stored Process" then do;
rc3=metadata_getattr(tsuri,"StoredText",StoredProcessConfiguration);
end;
cnt+1;
end;
/* get context (should only be one) */
rc4=metadata_getnasn(stp_uri,"ComputeLocations",1,tsuri);
rc5=metadata_getattr(tsuri,"Name",servercontext);
end;
else do;
put "%str(ERR)OR: could not find " path;
put (_all_)(=);
end;
&md.put (_all_)(=);
run;
%mend mm_getstpinfo ;

View File

@@ -43,7 +43,9 @@ data _null_;
cnt=1;
do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);
rc=metadata_getattr(tsuri,"Name",value);
%if &mdebug=1 %then %do;
put tsuri= value=;
%end;
if value="SourceCode" then do;
/* found it! */
rc=metadata_getattr(tsuri,"Id",value);
@@ -126,4 +128,4 @@ run;
filename &frefout clear;
%end;
%mend mm_updatestpsourcecode;
%mend mm_updatestpsourcecode;

View File

@@ -26,25 +26,33 @@
@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE
@param [in] ds The dataset to send back to the frontend
@param [out] dslabel= Value to use instead of table name for sending to JSON
@param [in] fmt=(Y) Set to N to send back unformatted values
@param [in] fmt= (N) Setting Y converts all vars to their formatted values
@param [out] fref= (_webout) The fileref to which to write the JSON
@param [in] missing= (NULL) Special numeric missing values can be sent as NULL
(eg `null`) or as STRING values (eg `".a"` or `".b"`)
@param [in] showmeta= (NO) Set to YES to output metadata alongside each table,
@param [in] showmeta= (N) Set to Y to output metadata alongside each table,
such as the column formats and types. The metadata is contained inside an
object with the same name as the table but prefixed with a dollar sign - ie,
`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`
@param [in] workobs= (0) When set to a positive integer, will create a new
output object (WORK) which contains this number of observations from all
tables in the WORK library.
@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows
that should be converted to output JSON
<h4> SAS Macros </h4>
@li mp_jsonout.sas
<h4> Related Macros </h4>
@li ms_webout.sas
@li mv_webout.sas
@version 9.3
@author Allan Bowe
**/
%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL
,showmeta=NO
%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL
,showmeta=N,maxobs=MAX,workobs=0
);
%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug
sasjs_tables;
@@ -111,15 +119,14 @@
%else %if &action=ARR or &action=OBJ %then %do;
%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref
,engine=&jsonengine,missing=&missing,showmeta=&showmeta
,engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs
)
%end;
%else %if &action=CLOSE %then %do;
/* To avoid issues with _webout on EBI we use a temporary file */
filename _sjsref temp lrecl=131068;
%if %str(&_debug) ge 131 %then %do;
/* if debug mode, send back first 10 records of each work table also */
options obs=10;
%if %str(&workobs) > 0 %then %do;
/* if debug mode, send back first XX records of each work table also */
data;run;%let tempds=%scan(&syslast,2,.);
ods output Members=&tempds;
proc datasets library=WORK memtype=data;
@@ -143,7 +150,9 @@
put " ""&wt"" : {";
put '"nlobs":' nlobs;
put ',"nvars":' nvars;
%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=YES)
%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y,maxobs=10
,maxobs=&workobs
)
data _null_; file _sjsref mod encoding='utf-8';
put "}";
%end;
@@ -153,26 +162,33 @@
%end;
/* close off json */
data _null_;file _sjsref mod encoding='utf-8';
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
put ",""SYSUSERID"" : ""&sysuserid"" ";
put ",""MF_GETUSER"" : ""%mf_getuser()"" ";
length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;
put ",""_DEBUG"" : ""&_debug"" ";
_METAUSER=quote(trim(symget('_METAUSER')));
put ",""_METAUSER"": " _METAUSER;
_METAPERSON=quote(trim(symget('_METAPERSON')));
put ',"_METAPERSON": ' _METAPERSON;
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
put ',"_PROGRAM" : ' _PROGRAM ;
autoexec=quote(urlencode(trim(getoption('autoexec'))));
put ',"AUTOEXEC" : ' autoexec;
put ",""MF_GETUSER"" : ""%mf_getuser()"" ";
put ",""SYSCC"" : ""&syscc"" ";
put ",""SYSENCODING"" : ""&sysencoding"" ";
syserrortext=quote(cats(symget('SYSERRORTEXT')));
syserrortext=cats('"',tranwrd(symget('syserrortext'),'"','\"'),'"');
put ',"SYSERRORTEXT" : ' syserrortext;
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";
put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";
SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));
put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;
put ",""SYSJOBID"" : ""&sysjobid"" ";
put ",""SYSSCPL"" : ""&sysscpl"" ";
put ",""SYSSITE"" : ""&syssite"" ";
put ",""SYSUSERID"" : ""&sysuserid"" ";
sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong;
syswarningtext=quote(cats(symget('SYSWARNINGTEXT')));
syswarningtext=cats('"',tranwrd(symget('syswarningtext'),'"','\"'),'"');
put ',"SYSWARNINGTEXT" : ' syswarningtext;
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
length memsize $32;

View File

@@ -0,0 +1,50 @@
/**
@file
@brief Creates a metadata folder
@details Creates a metadata folder using the batch tools
Usage:
%mmx_createmetafolder(loc=/some/meta/folder,user=sasdemo,pass=mars345)
<h4> SAS Macros </h4>
@li mf_loc.sas
@li mp_abort.sas
@param loc= the metadata folder to delete
@param user= username
@param pass= password
@version 9.4
@author Allan Bowe
**/
%macro mmx_createmetafolder(loc=,user=,pass=);
%local host port path connx_string msg;
%let host=%sysfunc(getoption(metaserver));
%let port=%sysfunc(getoption(metaport));
%let path=%mf_loc(POF)/tools;
%let connx_string= -host &host -port &port -user '&user' -password '&pass';
/* remove directory */
data _null_;
infile " &path/sas-make-folder &connx_string ""&loc"" -makeFullPath 2>&1"
pipe lrecl=10000;
input;
putlog _infile_;
run;
data _null_; /* check tree exists */
length type uri $256;
rc=metadata_pathobj("","&loc","Folder",type,uri);
call symputx('foldertype',type,'l');
run;
%let msg=Location (&loc) was not created!!;
%mp_abort(iftrue= (&foldertype ne Tree)
,mac=&_program..sas
,msg=%superq(msg)
)
%mend mmx_createmetafolder;

View File

@@ -7,7 +7,7 @@
Usage:
%mmx_deletemetafolder(loc=/some/meta/folder,user=sasdemo,pass=mars345)
%mmx_deletemetafolder(loc=/some/meta/folder,user=sasdemo,pass=mars345)
<h4> SAS Macros </h4>
@li mf_loc.sas
@@ -37,4 +37,4 @@ data _null_;
putlog _infile_;
run;
%mend mmx_deletemetafolder;
%mend mmx_deletemetafolder;

View File

@@ -25,6 +25,21 @@
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
<link rel="shortcut icon" href="$relpath^favicon.ico" type="image/x-icon" />
$extrastylesheet
<!-- Matomo -->
<script>
var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function () {
var u = "https://analytics.4gl.io/";
_paq.push(['setTrackerUrl', u + 'matomo.php']);
_paq.push(['setSiteId', '6']);
var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0];
g.async = true; g.src = u + 'matomo.js'; s.parentNode.insertBefore(g, s);
})();
</script>
<!-- End Matomo Code -->
</head>
<body>
@@ -68,4 +83,4 @@
</div>
</body>
</html>
</html>

View File

@@ -67,7 +67,7 @@
},
{
"name": "server",
"serverUrl": "",
"serverUrl": "https://sas.4gl.io",
"serverType": "SASJS",
"httpsAgentOptions": {
"allowInsecureRequests": false
@@ -107,4 +107,4 @@
"contextName": "SAS Job Execution compute context"
}
]
}
}

View File

@@ -94,14 +94,16 @@ data _null_;
file &sasjsref termstr=crlf lrecl=512;
put "/* Created on %sysfunc(datetime(),datetime19.) by %mf_getuser() */";
/* WEBOUT BEGIN */
put ' ';
put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y ';
put ' ,engine=DATASTEP ';
put ' ,missing=NULL ';
put ' ,showmeta=NO ';
put ' ,showmeta=N ';
put ' ,maxobs=MAX ';
put ')/*/STORE SOURCE*/; ';
put '%local tempds colinfo fmtds i numcols; ';
put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval ';
put ' tmpds1 tmpds2 tmpds3 tmpds4; ';
put '%let numcols=0; ';
put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;); ';
put ' ';
put '%if &action=OPEN %then %do; ';
put ' options nobomfile; ';
@@ -137,7 +139,7 @@ data _null_;
put ' by varnum; ';
put ' run; ';
put ' /* move meta to mac vars */ ';
put ' data _null_; ';
put ' data &colinfo; ';
put ' if _n_=1 then call symputx(''numcols'',nobs,''l''); ';
put ' set &colinfo end=last nobs=nobs; ';
put ' name=upcase(name); ';
@@ -148,7 +150,6 @@ data _null_;
put ' if format='''' then fmt=cats(''$'',length,''.''); ';
put ' else if formatl=0 then fmt=cats(format,''.''); ';
put ' else fmt=cats(format,formatl,''.''); ';
put ' newlen=max(formatl,length); ';
put ' end; ';
put ' else do; ';
put ' typelong=''num''; ';
@@ -156,23 +157,26 @@ data _null_;
put ' else if formatl=0 then fmt=cats(format,''.''); ';
put ' else if formatd=0 then fmt=cats(format,formatl,''.''); ';
put ' else fmt=cats(format,formatl,''.'',formatd); ';
put ' /* needs to be wide, for datetimes etc */ ';
put ' newlen=max(length,formatl,24); ';
put ' end; ';
put ' /* 32 char unique name */ ';
put ' newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27); ';
put ' ';
put ' call symputx(cats(''name'',_n_),name,''l''); ';
put ' call symputx(cats(''newname'',_n_),newname,''l''); ';
put ' call symputx(cats(''len'',_n_),newlen,''l''); ';
put ' call symputx(cats(''length'',_n_),length,''l''); ';
put ' call symputx(cats(''fmt'',_n_),fmt,''l''); ';
put ' call symputx(cats(''type'',_n_),type,''l''); ';
put ' call symputx(cats(''typelong'',_n_),typelong,''l''); ';
put ' call symputx(cats(''label'',_n_),coalescec(label,name),''l''); ';
put ' /* overwritten when fmt=Y and a custom format exists in catalog */ ';
put ' if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l''); ';
put ' else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l''); ';
put ' run; ';
put ' ';
put ' %let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
put ' proc sql; ';
put ' select count(*) into: lastobs from &ds; ';
put ' %if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs)); ';
put ' ';
put ' %if &engine=PROCJSON %then %do; ';
put ' %if &missing=STRING %then %do; ';
@@ -180,7 +184,9 @@ data _null_;
put ' %put &sysmacroname: Switching to DATASTEP engine; ';
put ' %goto datastep; ';
put ' %end; ';
put ' data &tempds;set &ds; ';
put ' data &tempds; ';
put ' set &ds; ';
put ' &stmt_obs; ';
put ' %if &fmt=N %then format _numeric_ best32.;; ';
put ' /* PRETTY is necessary to avoid line truncation in large files */ ';
put ' filename _sjs2 temp lrecl=131068 encoding=''utf-8''; ';
@@ -207,26 +213,99 @@ data _null_;
put ' %end; ';
put ' ';
put ' %if &fmt=Y %then %do; ';
put ' data _data_; ';
put ' /** ';
put ' * Extract format definitions ';
put ' * First, by getting library locations from dictionary.formats ';
put ' * Then, by exporting the width using proc format ';
put ' * Cannot use maxw from sashelp.vformat as not always populated ';
put ' * Cannot use fmtinfo() as not supported in all flavours ';
put ' */ ';
put ' %let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
put ' %let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
put ' %let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
put ' %let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
put ' proc sql noprint; ';
put ' create table &tmpds1 as ';
put ' select cats(libname,''.'',memname) as FMTCAT, ';
put ' FMTNAME ';
put ' from dictionary.formats ';
put ' where fmttype=''F'' and libname is not null ';
put ' and fmtname in (select format from &colinfo where format is not null) ';
put ' order by 1; ';
put ' create table &tmpds2( ';
put ' FMTNAME char(32), ';
put ' LENGTH num ';
put ' ); ';
put ' %local catlist cat fmtlist i; ';
put ' select distinct fmtcat into: catlist separated by '' '' from &tmpds1; ';
put ' %do i=1 %to %sysfunc(countw(&catlist,%str( ))); ';
put ' %let cat=%scan(&catlist,&i,%str( )); ';
put ' proc sql; ';
put ' select distinct fmtname into: fmtlist separated by '' '' ';
put ' from &tmpds1 where fmtcat="&cat"; ';
put ' proc format lib=&cat cntlout=&tmpds3(keep=fmtname length); ';
put ' select &fmtlist; ';
put ' run; ';
put ' proc sql; ';
put ' insert into &tmpds2 select distinct fmtname,length from &tmpds3; ';
put ' %end; ';
put ' ';
put ' proc sql; ';
put ' create table &tmpds4 as ';
put ' select a.*, b.length as MAXW ';
put ' from &colinfo a ';
put ' left join &tmpds2 b ';
put ' on cats(a.format)=cats(upcase(b.fmtname)) ';
put ' order by a.varnum; ';
put ' data _null_; ';
put ' set &tmpds4; ';
put ' if not missing(maxw); ';
put ' call symputx( ';
put ' cats(''fmtlen'',_n_), ';
put ' /* vars need extra padding due to JSON escaping of special chars */ ';
put ' min(32767,ceil((max(length,maxw)+10)*1.5)) ';
put ' ,''l'' ';
put ' ); ';
put ' run; ';
put ' ';
put ' /* configure varlenchk - as we are explicitly shortening the variables */ ';
put ' %let optval=%sysfunc(getoption(varlenchk)); ';
put ' options varlenchk=NOWARN; ';
put ' data _data_(compress=char); ';
put ' /* shorten the new vars */ ';
put ' length ';
put ' %do i=1 %to &numcols; ';
put ' &&name&i $&&fmtlen&i ';
put ' %end; ';
put ' ; ';
put ' /* rename on entry */ ';
put ' set &ds(rename=( ';
put ' %do i=1 %to &numcols; ';
put ' &&name&i=&&newname&i ';
put ' &&name&i=&&newname&i ';
put ' %end; ';
put ' )); ';
put ' &stmt_obs; ';
put ' ';
put ' drop ';
put ' %do i=1 %to &numcols; ';
put ' &&newname&i ';
put ' %end; ';
put ' ; ';
put ' %do i=1 %to &numcols; ';
put ' length &&name&i $&&len&i; ';
put ' %if &&typelong&i=num %then %do; ';
put ' &&name&i=left(put(&&newname&i,&&fmt&i)); ';
put ' &&name&i=cats(put(&&newname&i,&&fmt&i)); ';
put ' %end; ';
put ' %else %do; ';
put ' &&name&i=put(&&newname&i,&&fmt&i); ';
put ' %end; ';
put ' drop &&newname&i; ';
put ' %end; ';
put ' if _error_ then call symputx(''syscc'',1012); ';
put ' if _error_ then do; ';
put ' call symputx(''syscc'',1012); ';
put ' stop; ';
put ' end; ';
put ' run; ';
put ' %let fmtds=&syslast; ';
put ' options varlenchk=&optval; ';
put ' %end; ';
put ' ';
put ' proc format; /* credit yabwon for special null removal */ ';
@@ -245,8 +324,8 @@ data _null_;
put ' attrib _all_ label=''''; ';
put ' %do i=1 %to &numcols; ';
put ' %if &&typelong&i=char or &fmt=Y %then %do; ';
put ' length &&name&i $32767; ';
put ' format &&name&i $32767.; ';
put ' length &&name&i $&&fmtlen&i...; ';
put ' format &&name&i $&&fmtlen&i...; ';
put ' %end; ';
put ' %end; ';
put ' %if &fmt=Y %then %do; ';
@@ -255,10 +334,11 @@ data _null_;
put ' %else %do; ';
put ' set &ds; ';
put ' %end; ';
put ' &stmt_obs; ';
put ' format _numeric_ bart.; ';
put ' %do i=1 %to &numcols; ';
put ' %if &&typelong&i=char or &fmt=Y %then %do; ';
put ' if findc(&&name&i,''"\''!!''0A0D09000E0F01021011''x) then do; ';
put ' if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do; ';
put ' &&name&i=''"''!!trim( ';
put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
@@ -271,8 +351,9 @@ data _null_;
put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ ';
put ' prxchange(''s/\\/\\\\/'',-1,&&name&i) ';
put ' ))))))))))))!!''"''; ';
put ' )))))))))))))!!''"''; ';
put ' end; ';
put ' else &&name&i=quote(cats(&&name&i)); ';
put ' %end; ';
@@ -311,10 +392,11 @@ data _null_;
put ' proc sql; ';
put ' drop table &colinfo, &tempds; ';
put ' ';
put ' %if &showmeta=YES %then %do; ';
put ' %if %substr(&showmeta,1,1)=Y %then %do; ';
put ' filename _sjs4 temp lrecl=131068 encoding=''utf-8''; ';
put ' data _null_; ';
put ' file _sjs4; ';
put ' length label $350; ';
put ' put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{"; ';
put ' do i=1 to &numcols; ';
put ' name=quote(trim(symget(cats(''name'',i)))); ';
@@ -367,8 +449,8 @@ data _null_;
put ' ';
put '%mend mf_getuser; ';
put ' ';
put '%macro ms_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL ';
put ' ,showmeta=NO ';
put '%macro ms_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL ';
put ' ,showmeta=N,maxobs=MAX,workobs=0 ';
put '); ';
put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug ';
put ' sasjs_tables; ';
@@ -427,13 +509,12 @@ data _null_;
put ' %let missing=NULL; ';
put ' %end; ';
put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref ';
put ' ,engine=DATASTEP,missing=&missing,showmeta=&showmeta ';
put ' ,engine=DATASTEP,missing=&missing,showmeta=&showmeta,maxobs=&maxobs ';
put ' ) ';
put '%end; ';
put '%else %if &action=CLOSE %then %do; ';
put ' %if %str(&_debug) ge 131 %then %do; ';
put ' /* if debug mode, send back first 10 records of each work table also */ ';
put ' options obs=10; ';
put ' %if %str(&workobs) > 0 %then %do; ';
put ' /* if debug mode, send back first XX records of each work table also */ ';
put ' data;run;%let tempds=%scan(&syslast,2,.); ';
put ' ods output Members=&tempds; ';
put ' proc datasets library=WORK memtype=data; ';
@@ -458,7 +539,9 @@ data _null_;
put ' put " ""&wt"" : {"; ';
put ' put ''"nlobs":'' nlobs; ';
put ' put '',"nvars":'' nvars; ';
put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=YES) ';
put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=Y,maxobs=10 ';
put ' ,maxobs=&workobs ';
put ' ) ';
put ' data _null_; file &fref mod encoding=''utf-8'' termstr=lf; ';
put ' put "}"; ';
put ' %end; ';
@@ -468,35 +551,34 @@ data _null_;
put ' %end; ';
put ' /* close off json */ ';
put ' data _null_;file &fref mod encoding=''utf-8'' termstr=lf lrecl=32767; ';
put ' _PROGRAM=quote(trim(resolve(symget(''_PROGRAM'')))); ';
put ' put ",""SYSUSERID"" : ""&sysuserid"" "; ';
put ' put ",""MF_GETUSER"" : ""%mf_getuser()"" "; ';
put ' length SYSPROCESSNAME syserrortext syswarningtext autoexec $512; ';
put ' put ",""_DEBUG"" : ""&_debug"" "; ';
put ' _PROGRAM=quote(trim(resolve(symget(''_PROGRAM'')))); ';
put ' put '',"_PROGRAM" : '' _PROGRAM ; ';
put ' autoexec=quote(urlencode(trim(getoption(''autoexec'')))); ';
put ' put '',"AUTOEXEC" : '' autoexec; ';
put ' put ",""MF_GETUSER"" : ""%mf_getuser()"" "; ';
put ' put ",""SYSCC"" : ""&syscc"" "; ';
put ' put ",""SYSENCODING"" : ""&sysencoding"" "; ';
put ' syserrortext=quote(cats(symget(''SYSERRORTEXT''))); ';
put ' syserrortext=cats(''"'',tranwrd(symget(''syserrortext''),''"'',''\"''),''"''); ';
put ' put '',"SYSERRORTEXT" : '' syserrortext; ';
put ' SYSHOSTINFOLONG=quote(trim(symget(''SYSHOSTINFOLONG''))); ';
put ' put '',"SYSHOSTINFOLONG" : '' SYSHOSTINFOLONG; ';
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
put ' put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" "; ';
put ' put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" "; ';
put ' length SYSPROCESSNAME $512; ';
put ' SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME))); ';
put ' put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME; ';
put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
put ' put ",""SYSSCPL"" : ""&sysscpl"" "; ';
put ' put ",""SYSSITE"" : ""&syssite"" "; ';
put ' put ",""SYSTCPIPHOSTNAME"" : ""&SYSTCPIPHOSTNAME"" "; ';
put ' put ",""SYSUSERID"" : ""&sysuserid"" "; ';
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
put ' put '',"SYSVLONG" : '' sysvlong; ';
put ' syswarningtext=quote(cats(symget(''SYSWARNINGTEXT''))); ';
put ' syswarningtext=cats(''"'',tranwrd(symget(''syswarningtext''),''"'',''\"''),''"''); ';
put ' put '',"SYSWARNINGTEXT" : '' syswarningtext; ';
put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" ''; ';
put ' length autoexec $512; ';
put ' autoexec=quote(urlencode(trim(getoption(''autoexec'')))); ';
put ' put '',"AUTOEXEC" : '' autoexec; ';
put ' length memsize $32; ';
put ' memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)"; ';
put ' memsize=quote(cats(memsize)); ';

View File

@@ -116,14 +116,16 @@ libname &libref JSON fileref=&fref1;
%if "&group"="0" and "&gid"="0" %then %do;
data &outds;
length DISPLAYNAME $60 USERNAME:$30 ID 8;
set &libref..root;
if nobs=0 then call missing(of _all_);
set &libref..root nobs=nobs;
drop ordinal_root;
run;
%end;
%else %do;
data &outds;
length DISPLAYNAME $60 USERNAME:$30 ID 8;
set &libref..users;
if nobs=0 then call missing(of _all_);
set &libref..users nobs=nobs;
drop ordinal_root ordinal_users;
run;
%end;

View File

@@ -23,14 +23,19 @@
@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE
@param [in] ds The dataset to send back to the frontend
@param [out] dslabel= value to use instead of table name for sending to JSON
@param [in] fmt= (Y) Set to N to send back unformatted values
@param [in] fmt= (N) Setting Y converts all vars to their formatted values
@param [out] fref= (_webout) The fileref to which to write the JSON
@param [in] missing= (NULL) Special numeric missing values can be sent as NULL
(eg `null`) or as STRING values (eg `".a"` or `".b"`)
@param [in] showmeta= (NO) Set to YES to output metadata alongside each table,
@param [in] showmeta= (N) Set to Y to output metadata alongside each table,
such as the column formats and types. The metadata is contained inside an
object with the same name as the table but prefixed with a dollar sign - ie,
`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`
@param [in] workobs= (0) When set to a positive integer, will create a new
output object (WORK) which contains this number of observations from all
tables in the WORK library.
@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows
that should be converted to output JSON
<h4> SAS Macros </h4>
@li mf_getuser.sas
@@ -46,8 +51,8 @@
**/
%macro ms_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL
,showmeta=NO
%macro ms_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL
,showmeta=N,maxobs=MAX,workobs=0
);
%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug
sasjs_tables;
@@ -106,13 +111,12 @@
%let missing=NULL;
%end;
%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref
,engine=DATASTEP,missing=&missing,showmeta=&showmeta
,engine=DATASTEP,missing=&missing,showmeta=&showmeta,maxobs=&maxobs
)
%end;
%else %if &action=CLOSE %then %do;
%if %str(&_debug) ge 131 %then %do;
/* if debug mode, send back first 10 records of each work table also */
options obs=10;
%if %str(&workobs) > 0 %then %do;
/* if debug mode, send back first XX records of each work table also */
data;run;%let tempds=%scan(&syslast,2,.);
ods output Members=&tempds;
proc datasets library=WORK memtype=data;
@@ -137,7 +141,9 @@
put " ""&wt"" : {";
put '"nlobs":' nlobs;
put ',"nvars":' nvars;
%mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=YES)
%mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=Y,maxobs=10
,maxobs=&workobs
)
data _null_; file &fref mod encoding='utf-8' termstr=lf;
put "}";
%end;
@@ -147,35 +153,34 @@
%end;
/* close off json */
data _null_;file &fref mod encoding='utf-8' termstr=lf lrecl=32767;
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
put ",""SYSUSERID"" : ""&sysuserid"" ";
put ",""MF_GETUSER"" : ""%mf_getuser()"" ";
length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;
put ",""_DEBUG"" : ""&_debug"" ";
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
put ',"_PROGRAM" : ' _PROGRAM ;
autoexec=quote(urlencode(trim(getoption('autoexec'))));
put ',"AUTOEXEC" : ' autoexec;
put ",""MF_GETUSER"" : ""%mf_getuser()"" ";
put ",""SYSCC"" : ""&syscc"" ";
put ",""SYSENCODING"" : ""&sysencoding"" ";
syserrortext=quote(cats(symget('SYSERRORTEXT')));
syserrortext=cats('"',tranwrd(symget('syserrortext'),'"','\"'),'"');
put ',"SYSERRORTEXT" : ' syserrortext;
SYSHOSTINFOLONG=quote(trim(symget('SYSHOSTINFOLONG')));
put ',"SYSHOSTINFOLONG" : ' SYSHOSTINFOLONG;
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";
put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";
length SYSPROCESSNAME $512;
SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));
put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;
put ",""SYSJOBID"" : ""&sysjobid"" ";
put ",""SYSSCPL"" : ""&sysscpl"" ";
put ",""SYSSITE"" : ""&syssite"" ";
put ",""SYSTCPIPHOSTNAME"" : ""&SYSTCPIPHOSTNAME"" ";
put ",""SYSUSERID"" : ""&sysuserid"" ";
sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong;
syswarningtext=quote(cats(symget('SYSWARNINGTEXT')));
syswarningtext=cats('"',tranwrd(symget('syswarningtext'),'"','\"'),'"');
put ',"SYSWARNINGTEXT" : ' syswarningtext;
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
length autoexec $512;
autoexec=quote(urlencode(trim(getoption('autoexec'))));
put ',"AUTOEXEC" : ' autoexec;
length memsize $32;
memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";
memsize=quote(cats(memsize));

View File

@@ -0,0 +1,35 @@
/**
@file
@brief Testing mf_increment macro
<h4> SAS Macros </h4>
@li mf_increment.sas
@li mp_assert.sas
**/
%let var=0;
%mp_assert(
iftrue=(
"%mf_increment(var)"="1"
),
desc=Checking basic mf_increment usage 1,
outds=work.test_results
)
%mp_assert(
iftrue=(
"%mf_increment(var)"="2"
),
desc=Checking basic mf_increment usage 2,
outds=work.test_results
)
%mp_assert(
iftrue=(
"%mf_increment(var,incr=2)"="4"
),
desc=Checking incr option,
outds=work.test_results
)

View File

@@ -0,0 +1,43 @@
/**
@file
@brief Testing mp_cleancsv.sas macro
@details Credit for test 1 goes to
[Tom](https://communities.sas.com/t5/user/viewprofilepage/user-id/159) from
SAS Communities:
https://communities.sas.com/t5/SAS-Programming/Removing-embedded-carriage-returns/m-p/824790#M325761
<h4> SAS Macros </h4>
@li mf_nobs.sas
@li mp_cleancsv.sas
@li mp_assert.sas
@li mp_assertscope.sas
**/
/* test 1 - cope with empty rows on CR formatted file */
filename crlf "%sysfunc(pathname(work))/crlf";
filename cr "%sysfunc(pathname(work))/cr";
data _null_;
file cr termstr=cr ;
put 'line 1'///'line 4'/'line 5';
run;
%mp_assertscope(SNAPSHOT)
%mp_cleancsv(in=cr,out=crlf)
%mp_assertscope(COMPARE)
/* 5 rows as all converted to OD0A */
data test1;
infile "%sysfunc(pathname(work))/crlf" lrecl=100 termstr=crlf;
input;
list;
run;
%put test1=%mf_nobs(test1);
%mp_assert(
iftrue=(%mf_nobs(work.test1)=5),
desc=Checking blank rows on CR formatted file,
outds=work.test_results
)

View File

@@ -0,0 +1,26 @@
/**
@file
@brief Testing mp_dictionary.sas macro
<h4> SAS Macros </h4>
@li mp_dictionary.sas
@li mp_assert.sas
**/
libname test (work);
%mp_dictionary(lib=test)
proc sql;
create table work.compare1 as select * from test.styles;
create table work.compare2 as select * from dictionary.styles;
proc compare base=compare1 compare=compare2;
run;
%put _all_;
%mp_assert(
iftrue=(%mf_existds(&sysinfo)=0),
desc=Compare was exact,
outds=work.test_results
)

View File

@@ -0,0 +1,32 @@
/**
@file
@brief Testing mp_dsmeta.sas macro
<h4> SAS Macros </h4>
@li mp_assert.sas
@li mp_assertscope.sas
@li mp_dsmeta.sas
**/
data work.Example;
set sashelp.vmacro;
run;
%mp_assertscope(SNAPSHOT)
%mp_dsmeta(work.example,outds=work.test)
%mp_assertscope(COMPARE)
proc sql noprint;
select count(*) into: nobs from work.test;
select count(distinct ods_table) into: tnobs from work.test;
%mp_assert(
iftrue=(&tnobs=2),
desc=Check that both ATTRIBUTES and ENGINEHOST are provided
)
%mp_assert(
iftrue=(&nobs>10),
desc=Check that sufficient details are provided
)

View File

@@ -38,7 +38,7 @@ William,M,15,66.5,112
;;;;
run;
/* valid filter conditions */
/* VALID filter conditions */
data work.inds;
infile datalines4 dsd;
input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
@@ -51,6 +51,9 @@ AND,OR,2,Weight,>=,77.7
AND,OR,2,Weight,NE,77.7
AND,AND,1,age,=,.A
AND,AND,1,height,<,.B
AND,AND,1,age,IN,"(.a,.b,.)"
AND,AND,1,age,IN,"(.A)"
;;;;
run;
@@ -115,6 +118,28 @@ run;
outds=work.test_results
)
/* invalid IN 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:$4000.;
datalines4;
AND,OR,2,age,IN,"(.,.a,X)"
;;;;
run;
%mp_filtercheck(work.inds,
targetds=work.class,
outds=work.badrecords,
abort=NO
)
%let syscc=0;
%mp_assertdsobs(work.badrecords,
desc=Invalid IN value,
test=HASOBS,
outds=work.test_results
)
/* Code injection - column name */
data work.inds;
infile datalines4 dsd;
@@ -163,7 +188,7 @@ run;
data work.inds;
infile datalines4 dsd;
input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
OPERATOR_NM:$10. RAW_VALUE:8;
OPERATOR_NM:$10. RAW_VALUE:8.;
datalines4;
AND,AND,1,age,=,0
;;;;

View File

@@ -56,6 +56,9 @@ AND,AND,1,SEX,<=,"'M'"
AND,OR,2,Name,NOT IN,"('Jane','Alfred')"
AND,OR,2,Weight,>=,77.7
AND,OR,2,Weight,NE,77.7
AND,AND,3,age,NOT IN,"(.a,.b,.)"
AND,AND,3,age,NOT IN,"(.A)"
AND,AND,4,Name,=,"'Jeremiah'"
;;;;
run;

View File

@@ -0,0 +1,133 @@
/**
@file
@brief Testing mp_hashdirectory.sas macro
<h4> SAS Macros </h4>
@li mf_mkdir.sas
@li mf_nobs.sas
@li mp_assert.sas
@li mp_assertscope.sas
@li mp_hashdirectory.sas
**/
/* set up a directory to hash */
%let fpath=%sysfunc(pathname(work))/testdir;
%mf_mkdir(&fpath)
%mf_mkdir(&fpath/sub1)
%mf_mkdir(&fpath/sub2)
%mf_mkdir(&fpath/sub1/subsub)
/* note - the path in the file means the hash is different in each run */
%macro makefile(path,name);
data _null_;
file "&path/&name" termstr=lf;
put "This file is located at:";
put "&path";
put "and it is called:";
put "&name";
run;
%mend makefile;
%macro spawner(path);
%do x=1 %to 5;
%makefile(&path,file&x..txt)
%end;
%mend spawner;
%spawner(&fpath)
%spawner(&fpath/sub1)
%spawner(&fpath/sub1/subsub)
%mp_assertscope(SNAPSHOT)
%mp_hashdirectory(&fpath,outds=work.hashes,maxdepth=MAX)
%mp_assertscope(COMPARE)
%mp_assert(
iftrue=(&syscc=0),
desc=No errors,
outds=work.test_results
)
%mp_assert(
iftrue=(%mf_nobs(work.hashes)=19),
desc=record created for each entry,
outds=work.test_results
)
proc sql;
select count(*) into: misscheck
from work.hashes
where file_hash is missing;
%mp_assert(
iftrue=(&misscheck=1),
desc=Only one missing hash - the empty directory,
outds=work.test_results
)
data _null_;
set work.hashes;
if directory=file_path then call symputx('tophash',file_hash);
run;
%mp_assert(
iftrue=(%length(&tophash)=32),
desc=ensure valid top level hash created,
outds=work.test_results
)
/* now change a file and re-hash */
data _null_;
file "&fpath/sub1/subsub/file1.txt" termstr=lf;
put "This file has changed!";
run;
%mp_hashdirectory(&fpath,outds=work.hashes2,maxdepth=MAX)
data _null_;
set work.hashes2;
if directory=file_path then call symputx('tophash2',file_hash);
run;
%mp_assert(
iftrue=(&tophash ne &tophash2),
desc=ensure the changing of the hash results in a new value,
outds=work.test_results
)
/* now change it back and see if it matches */
data _null_;
file "&fpath/sub1/subsub/file1.txt" termstr=lf;
put "This file is located at:";
put "&fpath/sub1/subsub";
put "and it is called:";
put "file1.txt";
run;
run;
%mp_hashdirectory(&fpath,outds=work.hashes3,maxdepth=MAX)
data _null_;
set work.hashes3;
if directory=file_path then call symputx('tophash3',file_hash);
run;
%mp_assert(
iftrue=(&tophash=&tophash3),
desc=ensure the same files result in the same hash,
outds=work.test_results
)
/* dump contents for debugging */
data _null_;
set work.hashes;
put file_hash file_path;
run;
data _null_;
set work.hashes2;
put file_hash file_path;
run;

View File

@@ -24,7 +24,8 @@
@param [in] contentdisp= (inline) Content Disposition. Example values:
@li inline
@li attachment
@param [in] ctype= (0) Set a default HTTP Content-Type header to be returned
with the file when the content is retrieved from the Files service.
@param [in] access_token_var= The global macro variable to contain the access
token, if using authorization_code grant type.
@param [in] grant_type= (sas_services) Valid values are:
@@ -52,6 +53,7 @@
,inref=
,intype=BINARY
,contentdisp=inline
,ctype=0
,access_token_var=ACCESS_TOKEN
,grant_type=sas_services
,mdebug=0
@@ -103,8 +105,10 @@ filename &fref filesrvc
folderPath="&path"
filename="&name"
cdisp="&contentdisp"
%if "&ctype" ne "0" %then %do;
ctype="&ctype"
%end;
lrecl=1048544;
%if &intype=BINARY %then %do;
%mp_binarycopy(inref=&inref, outref=&fref)
%end;

View File

@@ -34,14 +34,25 @@
@param path= The full path (on SAS Drive) where the job will be created
@param name= The name of the job
@param desc= The description of the job
@param desc= (Created by the mv_createjob.sas macro) The job description
@param precode= Space separated list of filerefs, pointing to the code that
needs to be attached to the beginning of the job
@param code= Fileref(s) of the actual code to be added
@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 replace= select NO to avoid replacing any existing job in that location
@param code= (ft15f001) Fileref(s) of the actual code to be added
@param access_token_var= (ACCESS_TOKEN) Global macro variable containing the
access token
@param grant_type= (sas_services) Valid values:
@li sas_services
@li detect
@li authorization_code
@li password
@param replace= (YES) select NO to avoid replacing any existing job
@param addjesbeginendmacros= (false) Relates to the `_addjesbeginendmacros`
setting. Normally this would always be false however due to a Viya bug
(https://github.com/sasjs/cli/issues/1229) this is now configurable. Valid
values:
@li true
@li false
@li 0 - this will prevent the flag from being set (job will default to true)
@param contextname= Choose a specific context on which to run the Job. Leave
blank to use the default context. From Viya 3.5 it is possible to configure
a shared context - see
@@ -62,6 +73,7 @@ https://go.documentation.sas.com/?docsetId=calcontexts&docsetTarget=n1hjn8eobk5p
,replace=YES
,debug=0
,contextname=
,addjesbeginendmacros=false
);
%local oauth_bearer;
%if &grant_type=detect %then %do;
@@ -185,19 +197,29 @@ run;
%end;
/* set up the body of the request to create the service */
%local fname3;
%local fname3 comma;
%let fname3=%mf_getuniquefileref();
data _null_;
file &fname3 TERMSTR=' ';
length string $32767;
string=cats('{"version": 0,"name":"'
,"&name"
,'","type":"Compute","parameters":[{"name":"_addjesbeginendmacros"'
,',"type":"CHARACTER","defaultValue":"false"}');
,'","type":"Compute","parameters":['
%if &addjesbeginendmacros ne 0 %then %do;
,'{"name":"_addjesbeginendmacros"'
,',"type":"CHARACTER","defaultValue":"'
,"&addjesbeginendmacros"
,'"}'
%let comma=%str(,);
%end;
);
context=quote(cats(symget('contextname')));
if context ne '""' then do;
string=cats(string,',{"version": 1,"name": "_contextName","defaultValue":'
,context,',"type":"CHARACTER","label":"Context Name","required": false}');
string=cats(string
,"&comma"
,'{"version": 1,"name": "_contextName","defaultValue":'
,context,',"type":"CHARACTER","label":"Context Name","required": false}'
);
end;
string=cats(string,'],"code":"');
put string;

View File

@@ -236,14 +236,16 @@ data _null_;
file &adapter;
put "/* Created on %sysfunc(datetime(),datetime19.) by &sysuserid */";
/* WEBOUT BEGIN */
put ' ';
put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y ';
put ' ,engine=DATASTEP ';
put ' ,missing=NULL ';
put ' ,showmeta=NO ';
put ' ,showmeta=N ';
put ' ,maxobs=MAX ';
put ')/*/STORE SOURCE*/; ';
put '%local tempds colinfo fmtds i numcols; ';
put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval ';
put ' tmpds1 tmpds2 tmpds3 tmpds4; ';
put '%let numcols=0; ';
put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;); ';
put ' ';
put '%if &action=OPEN %then %do; ';
put ' options nobomfile; ';
@@ -279,7 +281,7 @@ data _null_;
put ' by varnum; ';
put ' run; ';
put ' /* move meta to mac vars */ ';
put ' data _null_; ';
put ' data &colinfo; ';
put ' if _n_=1 then call symputx(''numcols'',nobs,''l''); ';
put ' set &colinfo end=last nobs=nobs; ';
put ' name=upcase(name); ';
@@ -290,7 +292,6 @@ data _null_;
put ' if format='''' then fmt=cats(''$'',length,''.''); ';
put ' else if formatl=0 then fmt=cats(format,''.''); ';
put ' else fmt=cats(format,formatl,''.''); ';
put ' newlen=max(formatl,length); ';
put ' end; ';
put ' else do; ';
put ' typelong=''num''; ';
@@ -298,23 +299,26 @@ data _null_;
put ' else if formatl=0 then fmt=cats(format,''.''); ';
put ' else if formatd=0 then fmt=cats(format,formatl,''.''); ';
put ' else fmt=cats(format,formatl,''.'',formatd); ';
put ' /* needs to be wide, for datetimes etc */ ';
put ' newlen=max(length,formatl,24); ';
put ' end; ';
put ' /* 32 char unique name */ ';
put ' newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27); ';
put ' ';
put ' call symputx(cats(''name'',_n_),name,''l''); ';
put ' call symputx(cats(''newname'',_n_),newname,''l''); ';
put ' call symputx(cats(''len'',_n_),newlen,''l''); ';
put ' call symputx(cats(''length'',_n_),length,''l''); ';
put ' call symputx(cats(''fmt'',_n_),fmt,''l''); ';
put ' call symputx(cats(''type'',_n_),type,''l''); ';
put ' call symputx(cats(''typelong'',_n_),typelong,''l''); ';
put ' call symputx(cats(''label'',_n_),coalescec(label,name),''l''); ';
put ' /* overwritten when fmt=Y and a custom format exists in catalog */ ';
put ' if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l''); ';
put ' else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l''); ';
put ' run; ';
put ' ';
put ' %let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
put ' proc sql; ';
put ' select count(*) into: lastobs from &ds; ';
put ' %if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs)); ';
put ' ';
put ' %if &engine=PROCJSON %then %do; ';
put ' %if &missing=STRING %then %do; ';
@@ -322,7 +326,9 @@ data _null_;
put ' %put &sysmacroname: Switching to DATASTEP engine; ';
put ' %goto datastep; ';
put ' %end; ';
put ' data &tempds;set &ds; ';
put ' data &tempds; ';
put ' set &ds; ';
put ' &stmt_obs; ';
put ' %if &fmt=N %then format _numeric_ best32.;; ';
put ' /* PRETTY is necessary to avoid line truncation in large files */ ';
put ' filename _sjs2 temp lrecl=131068 encoding=''utf-8''; ';
@@ -349,26 +355,99 @@ data _null_;
put ' %end; ';
put ' ';
put ' %if &fmt=Y %then %do; ';
put ' data _data_; ';
put ' /** ';
put ' * Extract format definitions ';
put ' * First, by getting library locations from dictionary.formats ';
put ' * Then, by exporting the width using proc format ';
put ' * Cannot use maxw from sashelp.vformat as not always populated ';
put ' * Cannot use fmtinfo() as not supported in all flavours ';
put ' */ ';
put ' %let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
put ' %let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
put ' %let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
put ' %let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
put ' proc sql noprint; ';
put ' create table &tmpds1 as ';
put ' select cats(libname,''.'',memname) as FMTCAT, ';
put ' FMTNAME ';
put ' from dictionary.formats ';
put ' where fmttype=''F'' and libname is not null ';
put ' and fmtname in (select format from &colinfo where format is not null) ';
put ' order by 1; ';
put ' create table &tmpds2( ';
put ' FMTNAME char(32), ';
put ' LENGTH num ';
put ' ); ';
put ' %local catlist cat fmtlist i; ';
put ' select distinct fmtcat into: catlist separated by '' '' from &tmpds1; ';
put ' %do i=1 %to %sysfunc(countw(&catlist,%str( ))); ';
put ' %let cat=%scan(&catlist,&i,%str( )); ';
put ' proc sql; ';
put ' select distinct fmtname into: fmtlist separated by '' '' ';
put ' from &tmpds1 where fmtcat="&cat"; ';
put ' proc format lib=&cat cntlout=&tmpds3(keep=fmtname length); ';
put ' select &fmtlist; ';
put ' run; ';
put ' proc sql; ';
put ' insert into &tmpds2 select distinct fmtname,length from &tmpds3; ';
put ' %end; ';
put ' ';
put ' proc sql; ';
put ' create table &tmpds4 as ';
put ' select a.*, b.length as MAXW ';
put ' from &colinfo a ';
put ' left join &tmpds2 b ';
put ' on cats(a.format)=cats(upcase(b.fmtname)) ';
put ' order by a.varnum; ';
put ' data _null_; ';
put ' set &tmpds4; ';
put ' if not missing(maxw); ';
put ' call symputx( ';
put ' cats(''fmtlen'',_n_), ';
put ' /* vars need extra padding due to JSON escaping of special chars */ ';
put ' min(32767,ceil((max(length,maxw)+10)*1.5)) ';
put ' ,''l'' ';
put ' ); ';
put ' run; ';
put ' ';
put ' /* configure varlenchk - as we are explicitly shortening the variables */ ';
put ' %let optval=%sysfunc(getoption(varlenchk)); ';
put ' options varlenchk=NOWARN; ';
put ' data _data_(compress=char); ';
put ' /* shorten the new vars */ ';
put ' length ';
put ' %do i=1 %to &numcols; ';
put ' &&name&i $&&fmtlen&i ';
put ' %end; ';
put ' ; ';
put ' /* rename on entry */ ';
put ' set &ds(rename=( ';
put ' %do i=1 %to &numcols; ';
put ' &&name&i=&&newname&i ';
put ' &&name&i=&&newname&i ';
put ' %end; ';
put ' )); ';
put ' &stmt_obs; ';
put ' ';
put ' drop ';
put ' %do i=1 %to &numcols; ';
put ' &&newname&i ';
put ' %end; ';
put ' ; ';
put ' %do i=1 %to &numcols; ';
put ' length &&name&i $&&len&i; ';
put ' %if &&typelong&i=num %then %do; ';
put ' &&name&i=left(put(&&newname&i,&&fmt&i)); ';
put ' &&name&i=cats(put(&&newname&i,&&fmt&i)); ';
put ' %end; ';
put ' %else %do; ';
put ' &&name&i=put(&&newname&i,&&fmt&i); ';
put ' %end; ';
put ' drop &&newname&i; ';
put ' %end; ';
put ' if _error_ then call symputx(''syscc'',1012); ';
put ' if _error_ then do; ';
put ' call symputx(''syscc'',1012); ';
put ' stop; ';
put ' end; ';
put ' run; ';
put ' %let fmtds=&syslast; ';
put ' options varlenchk=&optval; ';
put ' %end; ';
put ' ';
put ' proc format; /* credit yabwon for special null removal */ ';
@@ -387,8 +466,8 @@ data _null_;
put ' attrib _all_ label=''''; ';
put ' %do i=1 %to &numcols; ';
put ' %if &&typelong&i=char or &fmt=Y %then %do; ';
put ' length &&name&i $32767; ';
put ' format &&name&i $32767.; ';
put ' length &&name&i $&&fmtlen&i...; ';
put ' format &&name&i $&&fmtlen&i...; ';
put ' %end; ';
put ' %end; ';
put ' %if &fmt=Y %then %do; ';
@@ -397,10 +476,11 @@ data _null_;
put ' %else %do; ';
put ' set &ds; ';
put ' %end; ';
put ' &stmt_obs; ';
put ' format _numeric_ bart.; ';
put ' %do i=1 %to &numcols; ';
put ' %if &&typelong&i=char or &fmt=Y %then %do; ';
put ' if findc(&&name&i,''"\''!!''0A0D09000E0F01021011''x) then do; ';
put ' if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do; ';
put ' &&name&i=''"''!!trim( ';
put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
@@ -413,8 +493,9 @@ data _null_;
put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ ';
put ' prxchange(''s/\\/\\\\/'',-1,&&name&i) ';
put ' ))))))))))))!!''"''; ';
put ' )))))))))))))!!''"''; ';
put ' end; ';
put ' else &&name&i=quote(cats(&&name&i)); ';
put ' %end; ';
@@ -453,10 +534,11 @@ data _null_;
put ' proc sql; ';
put ' drop table &colinfo, &tempds; ';
put ' ';
put ' %if &showmeta=YES %then %do; ';
put ' %if %substr(&showmeta,1,1)=Y %then %do; ';
put ' filename _sjs4 temp lrecl=131068 encoding=''utf-8''; ';
put ' data _null_; ';
put ' file _sjs4; ';
put ' length label $350; ';
put ' put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{"; ';
put ' do i=1 to &numcols; ';
put ' name=quote(trim(symget(cats(''name'',i)))); ';
@@ -508,8 +590,8 @@ data _null_;
put ' %quote(&user) ';
put ' ';
put '%mend mf_getuser; ';
put '%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=Y,stream=Y,missing=NULL ';
put ' ,showmeta=NO ';
put '%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=N,stream=Y,missing=NULL ';
put ' ,showmeta=N,maxobs=MAX,workobs=0 ';
put '); ';
put '%global _webin_file_count _webin_fileuri _debug _omittextlog _webin_name ';
put ' sasjs_tables SYS_JES_JOB_URI; ';
@@ -601,8 +683,7 @@ data _null_;
put ' ';
put ' /* setup temp ref */ ';
put ' %if %upcase(&fref) ne _WEBOUT %then %do; ';
put ' filename &fref temp lrecl=999999 permission=''A::u::rwx,A::g::rw-,A::o::---'' ';
put ' mod; ';
put ' filename &fref temp lrecl=999999 permission=''A::u::rwx,A::g::rw-,A::o::---''; ';
put ' %end; ';
put ' ';
put ' /* setup json */ ';
@@ -612,14 +693,13 @@ data _null_;
put ' run; ';
put '%end; ';
put '%else %if &action=ARR or &action=OBJ %then %do; ';
put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt ';
put ' ,jref=&fref,engine=DATASTEP,missing=&missing,showmeta=&showmeta ';
put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref ';
put ' ,engine=DATASTEP,missing=&missing,showmeta=&showmeta,maxobs=&maxobs ';
put ' ) ';
put '%end; ';
put '%else %if &action=CLOSE %then %do; ';
put ' %if %str(&_debug) ge 131 %then %do; ';
put ' /* send back first 10 records of each work table for debugging */ ';
put ' options obs=10; ';
put ' %if %str(&workobs) > 0 %then %do; ';
put ' /* send back first XX records of each work table for debugging */ ';
put ' data;run;%let tempds=%scan(&syslast,2,.); ';
put ' ods output Members=&tempds; ';
put ' proc datasets library=WORK memtype=data; ';
@@ -642,7 +722,9 @@ data _null_;
put ' put " ""&wt"" : {"; ';
put ' put ''"nlobs":'' nlobs; ';
put ' put '',"nvars":'' nvars; ';
put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=YES) ';
put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=Y ';
put ' ,maxobs=&workobs ';
put ' ) ';
put ' data _null_; file &fref mod;put "}"; ';
put ' %end; ';
put ' data _null_; file &fref mod;put "}";run; ';
@@ -650,23 +732,31 @@ data _null_;
put ' ';
put ' /* close off json */ ';
put ' data _null_;file &fref mod; ';
put ' length SYSPROCESSNAME syserrortext syswarningtext autoexec $512; ';
put ' put ",""_DEBUG"" : ""&_debug"" "; ';
put ' _PROGRAM=quote(trim(resolve(symget(''_PROGRAM'')))); ';
put ' put ",""SYSUSERID"" : ""&sysuserid"" "; ';
put ' put '',"_PROGRAM" : '' _PROGRAM ; ';
put ' autoexec=quote(urlencode(trim(getoption(''autoexec'')))); ';
put ' put '',"AUTOEXEC" : '' autoexec; ';
put ' put ",""MF_GETUSER"" : ""%mf_getuser()"" "; ';
put ' SYS_JES_JOB_URI=quote(trim(resolve(symget(''SYS_JES_JOB_URI'')))); ';
put ' put '',"SYS_JES_JOB_URI" : '' SYS_JES_JOB_URI ; ';
put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
put ' put ",""_DEBUG"" : ""&_debug"" "; ';
put ' put '',"_PROGRAM" : '' _PROGRAM ; ';
put ' put ",""SYSCC"" : ""&syscc"" "; ';
put ' syserrortext=quote(cats(symget(''SYSERRORTEXT''))); ';
put ' syserrortext=cats(''"'',tranwrd(symget(''syserrortext''),''"'',''\"''),''"''); ';
put ' put '',"SYSERRORTEXT" : '' syserrortext; ';
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
put ' put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" "; ';
put ' put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" "; ';
put ' SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME))); ';
put ' put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME; ';
put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
put ' put ",""SYSSCPL"" : ""&sysscpl"" "; ';
put ' put ",""SYSSITE"" : ""&syssite"" "; ';
put ' put ",""SYSUSERID"" : ""&sysuserid"" "; ';
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
put ' put '',"SYSVLONG" : '' sysvlong; ';
put ' syswarningtext=quote(cats(symget(''SYSWARNINGTEXT''))); ';
put ' syswarningtext=cats(''"'',tranwrd(symget(''syswarningtext''),''"'',''\"''),''"''); ';
put ' put '',"SYSWARNINGTEXT" : '' syswarningtext; ';
put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" ''; ';
put ' length memsize $32; ';

View File

@@ -25,25 +25,34 @@
@param [in] _webout= fileref for returning the json
@param [out] fref=(_mvwtemp) Temp fileref to which to write the output
@param [out] dslabel= value to use instead of table name for sending to JSON
@param [in] fmt=(Y) change to N to strip formats from output
@param [in] fmt= (N) Setting Y converts all vars to their formatted values
@param [in] stream=(Y) Change to N if not streaming to _webout
@param [in] missing= (NULL) Special numeric missing values can be sent as NULL
(eg `null`) or as STRING values (eg `".a"` or `".b"`)
@param [in] showmeta= (NO) Set to YES to output metadata alongside each table,
@param [in] showmeta= (N) Set to Y to output metadata alongside each table,
such as the column formats and types. The metadata is contained inside an
object with the same name as the table but prefixed with a dollar sign - ie,
`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`
@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows
that should be converted to output JSON
@param [in] workobs= (0) When set to a positive integer, will create a new
output object (WORK) which contains this number of observations from all
tables in the WORK library.
<h4> SAS Macros </h4>
@li mp_jsonout.sas
@li mf_getuser.sas
<h4> Related Macros </h4>
@li ms_webout.sas
@li mm_webout.sas
@version Viya 3.3
@author Allan Bowe, source: https://github.com/sasjs/core
**/
%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=Y,stream=Y,missing=NULL
,showmeta=NO
%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=N,stream=Y,missing=NULL
,showmeta=N,maxobs=MAX,workobs=0
);
%global _webin_file_count _webin_fileuri _debug _omittextlog _webin_name
sasjs_tables SYS_JES_JOB_URI;
@@ -135,8 +144,7 @@
/* setup temp ref */
%if %upcase(&fref) ne _WEBOUT %then %do;
filename &fref temp lrecl=999999 permission='A::u::rwx,A::g::rw-,A::o::---'
mod;
filename &fref temp lrecl=999999 permission='A::u::rwx,A::g::rw-,A::o::---';
%end;
/* setup json */
@@ -146,14 +154,13 @@
run;
%end;
%else %if &action=ARR or &action=OBJ %then %do;
%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt
,jref=&fref,engine=DATASTEP,missing=&missing,showmeta=&showmeta
%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref
,engine=DATASTEP,missing=&missing,showmeta=&showmeta,maxobs=&maxobs
)
%end;
%else %if &action=CLOSE %then %do;
%if %str(&_debug) ge 131 %then %do;
/* send back first 10 records of each work table for debugging */
options obs=10;
%if %str(&workobs) > 0 %then %do;
/* send back first XX records of each work table for debugging */
data;run;%let tempds=%scan(&syslast,2,.);
ods output Members=&tempds;
proc datasets library=WORK memtype=data;
@@ -176,7 +183,9 @@
put " ""&wt"" : {";
put '"nlobs":' nlobs;
put ',"nvars":' nvars;
%mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=YES)
%mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=Y
,maxobs=&workobs
)
data _null_; file &fref mod;put "}";
%end;
data _null_; file &fref mod;put "}";run;
@@ -184,23 +193,31 @@
/* close off json */
data _null_;file &fref mod;
length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;
put ",""_DEBUG"" : ""&_debug"" ";
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
put ",""SYSUSERID"" : ""&sysuserid"" ";
put ',"_PROGRAM" : ' _PROGRAM ;
autoexec=quote(urlencode(trim(getoption('autoexec'))));
put ',"AUTOEXEC" : ' autoexec;
put ",""MF_GETUSER"" : ""%mf_getuser()"" ";
SYS_JES_JOB_URI=quote(trim(resolve(symget('SYS_JES_JOB_URI'))));
put ',"SYS_JES_JOB_URI" : ' SYS_JES_JOB_URI ;
put ",""SYSJOBID"" : ""&sysjobid"" ";
put ",""_DEBUG"" : ""&_debug"" ";
put ',"_PROGRAM" : ' _PROGRAM ;
put ",""SYSCC"" : ""&syscc"" ";
syserrortext=quote(cats(symget('SYSERRORTEXT')));
syserrortext=cats('"',tranwrd(symget('syserrortext'),'"','\"'),'"');
put ',"SYSERRORTEXT" : ' syserrortext;
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";
put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";
SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));
put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;
put ",""SYSJOBID"" : ""&sysjobid"" ";
put ",""SYSSCPL"" : ""&sysscpl"" ";
put ",""SYSSITE"" : ""&syssite"" ";
put ",""SYSUSERID"" : ""&sysuserid"" ";
sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong;
syswarningtext=quote(cats(symget('SYSWARNINGTEXT')));
syswarningtext=cats('"',tranwrd(symget('syswarningtext'),'"','\"'),'"');
put ',"SYSWARNINGTEXT" : ' syswarningtext;
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
length memsize $32;