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

Compare commits

...

192 Commits

Author SHA1 Message Date
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
Allan Bowe
31702df19b Merge pull request #268 from sasjs/allanbowe/stored-process-returns-267
fix: removing endsas for 9.4m6+ WIN enviornments in mp_abort
2022-06-28 15:47:31 +02:00
Allan Bowe
cf0d1c0473 fix: removing endsas for 9.4m6+ WIN enviornments in mp_abort 2022-06-28 13:46:07 +00:00
Allan Bowe
1f369f9848 Merge pull request #266 from sasjs/latin9fixes
fix: writing utf-8 to _webout on windows in a latin9 session causes problems
2022-06-26 23:16:59 +02:00
munja
2372ff5f4f fix: writing utf-8 to _webout on windows in a latin9 session causes problems with subsequent (regular) put statements. The workaround is to write to a different file and stream it back to _webout. 2022-06-26 22:09:54 +01:00
Allan Bowe
6d0e34ba1d Merge pull request #265 from sasjs/binaryfix
enable file copy of files with an encoding that does not match session encoding
2022-06-26 17:14:25 +02:00
munja
7a69698178 fix: adding fileref options and an additional test for mp_binarycopy 2022-06-26 16:12:49 +01:00
Allan Bowe
532bf84e06 fix: mp_jsonout 2022-06-25 21:38:05 +00:00
Allan Bowe
e1afbc02c4 fix: enabling binary copy of files with encoding that does not match session encoding 2022-06-25 21:32:21 +00:00
Allan Bowe
756f00d88d chore: reduce blankspace in compiled streaming apps 2022-06-25 21:30:15 +00:00
Allan Bowe
b72e404d52 Merge pull request #264 from sasjs/allanbowe/add-sysencoding-to-webout-263
feat: adding sysencoding to SASJS and SAS9 server types
2022-06-25 15:16:13 +02:00
Allan Bowe
e31cdeef42 feat: adding sysencoding to SASJS and SAS9 server types
Not added for mv_webout (viya) as that is always UTF-8.  Closes #263
2022-06-25 13:11:14 +00:00
Allan Bowe
8a4e32cc27 chore: updating sasjsconfig 2022-06-21 21:33:33 +00:00
Allan Bowe
f285505b79 Merge pull request #262 from sasjs/allanbowe/mp-ds-cards-does-not-261
fix: special missing support in mp_ds2cards()
2022-06-21 19:37:51 +02:00
Allan Bowe
67f5c50300 fix: special missing support in mp_ds2cards() 2022-06-21 17:34:44 +00:00
Yury Shkoda
ce39e4f779 Merge pull request #260 from sasjs/pr-template
chore(template): added pull request template
2022-06-21 19:57:11 +03:00
Yury Shkoda
9c80f5664c chore(template): added pull request template 2022-06-21 19:51:23 +03:00
Allan Bowe
83466c001b Merge pull request #259 from sasjs/allanbowe/mp-jsonout-not-escaping-258
fix: escaping labels in mp_jsonout when showmeta=YES.  Closes #258
2022-06-21 16:16:07 +02:00
Allan Bowe
ad315be503 fix: escaping labels in mp_jsonout when showmeta=YES. Closes #258 2022-06-21 14:14:35 +00:00
Allan Bowe
c41ae2dcc8 fix: enabling sasjsprocessmode as global var in mp_abort
Also, reduced indentation
2022-06-18 13:11:35 +00:00
d9f8e92fac Merge pull request #257 from sasjs/userfeat
feat: filter mm_getusers on a particular user
2022-06-17 19:59:38 +02:00
Allan Bowe
d42ede15db fix: superq 2022-06-17 17:58:39 +00:00
Allan Bowe
08ea9f7c00 chore: all.sas 2022-06-17 17:55:28 +00:00
Allan Bowe
c327e1fc0d fix: apostrophes 2022-06-17 17:55:01 +00:00
Allan Bowe
02fddcf9a1 fix: removing pipes 2022-06-17 17:51:36 +00:00
Allan Bowe
4752bfbb05 fix: refactor xml 2022-06-17 17:47:34 +00:00
Allan Bowe
767ddd7add feat: filter mm_getusers on a particular user
This can be useful for extracting the uri of a metadata user
2022-06-17 17:03:25 +00:00
Allan Bowe
54a24ced83 fix: using sasjs username in mf_getuser() 2022-06-17 15:54:30 +00:00
Allan Bowe
57ae2981f1 Merge pull request #256 from sasjs/allanbowe/mp-abort-fails-on-windows-254
fix: superq() for sysprocessname
2022-06-17 15:29:22 +02:00
Allan Bowe
a3043ac685 fix: superq() for sysprocessname 2022-06-17 13:28:51 +00:00
Allan Bowe
2bdb90b0be Merge pull request #255 from sasjs/allanbowe/mp-abort-fails-on-windows-254
fix: handling embedded speechmarks in SYSPROCESSNAME.  Closes #254
2022-06-17 14:32:02 +02:00
Allan Bowe
2cd846d504 fix: handling embedded speechmarks in SYSPROCESSNAME. Closes #254 2022-06-17 12:30:40 +00:00
Allan Bowe
f593c7bec9 fix: returning user list (single user) in desktop mode in ms_getusers() 2022-06-17 07:52:39 +00:00
Allan Bowe
c8805db0b5 feat: filter for groups by user id in ms_getgroups 2022-06-17 07:46:51 +00:00
Allan Bowe
1eb6d8cec9 feat: enabling user list by group id as well as name 2022-06-17 07:16:57 +00:00
Allan Bowe
ed19ee03af Merge pull request #253 from sasjs/servergroups
feat: enabling group macros on sasjs/server
2022-06-16 13:40:36 +02:00
Allan Bowe
a1c931b5e6 fix: tests with new APIs are now passing 2022-06-16 11:37:31 +00:00
Allan Bowe
cb553a31ab fix: failing test for filtering groups for a particular user (api isn't ready yet) 2022-06-14 19:37:06 +00:00
Allan Bowe
557df272ff fix: using new user/by/username api in sasjs/server 2022-06-14 19:24:41 +00:00
Allan Bowe
0cb3c96c15 feat: enabling group macros on sasjs/server
This PR updates ms_getgroups with a user filter, and ms_getusers with a group filter. ms_adduser2group was also created to faciliate the necessary test(s).
2022-06-14 13:40:05 +00:00
Allan Bowe
1cb39d4d61 Merge pull request #252 from sasjs/allanbowe/ms-getgroups-fails-in-251
fix: creating empty table in desktop mode (ms_getgroups)
2022-06-11 21:30:03 +02:00
Allan Bowe
934b7d4f8a fix: creating empty table in desktop mode (ms_getgroups) 2022-06-11 19:29:21 +00:00
Allan Bowe
24c50cde56 fix: use options nobomfile for sasjs server mp_abort, also doc update in mf_existvarlist 2022-06-09 22:16:34 +00:00
Allan Bowe
055e8d2f13 fix: setting header in mp_abort for sasjs server 2022-06-07 14:01:54 +00:00
Allan Bowe
abfe7fe339 fix: no json wrapper in SASjs mode in mp_abort.sas 2022-06-07 11:28:59 +00:00
Allan Bowe
16ed91f6a9 fix: mp_abort on windows m6+ 2022-06-06 11:28:37 +00:00
Allan Bowe
67ba2a5286 fix: mp_abort on m6 win needs endsas 2022-06-03 17:45:27 +00:00
Allan Bowe
3d7f9b71e1 fix: hard abort in mm_getstpcode when the stp does not exist 2022-06-02 17:50:50 +00:00
Allan Bowe
1d972fad11 fix: using outref instead of outloc in mm_getstpcode invocation from mx_getcode 2022-05-31 22:42:40 +00:00
Allan Bowe
e23bc461c4 fix: mx_getcode platform support 2022-05-31 22:00:58 +00:00
Allan Bowe
28ed458b83 Merge pull request #250 from sasjs/allanbowe/mp-jsonout-needs-to-support-249
fix: enable reserved names in mp_jsonout.  Closes #249
2022-05-31 16:48:45 +03:00
Allan Bowe
827210e010 fix: enable reserved names in mp_jsonout. Closes #249 2022-05-31 13:48:09 +00:00
Allan Bowe
de2f32da36 Merge pull request #248 from sasjs/mm_createdataset_update
fix: cater for case of zero cols in mm_createdataset.sas
2022-05-31 14:02:13 +03:00
Allan Bowe
6fa0fc5dc6 fix: cater for case of zero cols in mm_createdataset.sas 2022-05-31 11:01:06 +00:00
Allan Bowe
73e3d9d419 Merge pull request #247 from sasjs/allanbowe/mp-abort-on-sasjs-server-246
fix: ensuring webout on abort, closes #246
2022-05-30 15:42:47 +03:00
Allan Bowe
5f2229e3d5 fix: ensuring webout on abort, closes #246 2022-05-30 12:38:34 +00:00
Allan Bowe
d19c4a517c chore: updated document header 2022-05-20 19:01:47 +00:00
munja
c47480f60c fix: space in dependencies 2022-05-20 12:55:39 +01:00
munja
295211bb72 fix: doc config and test-folders 2022-05-20 11:30:38 +01:00
Allan Bowe
818bc3cc2b Merge pull request #244 from sasjs/getcode
feat: creating new mx_ suite of macros!
2022-05-20 13:18:16 +03:00
munja
bb6111e2b3 fix: all.sas, readme, dependency issue and sasjsconfig file 2022-05-20 11:16:30 +01:00
munja
512f05c0b2 feat: creating new mx_ suite of macros!
also adding new mx_getcode macro
2022-05-19 22:02:19 +01:00
Allan Bowe
500fb8124f Merge pull request #243 from sasjs/resetoption
fix: resetoption
2022-05-19 14:28:56 +03:00
Allan Bowe
88ddba2a4b fix: runall 2022-05-19 11:27:39 +00:00
Allan Bowe
86f6d06b85 fix: update resetoption and adding test 2022-05-19 11:26:55 +00:00
Allan Bowe
1cefc0e7ee feat: adding check in mf_existfeature() for ability to proc export xlsx 2022-05-18 15:33:41 +00:00
munja
412182a022 fix: fileref option in ms_runstp 2022-05-17 22:27:39 +01:00
Allan Bowe
43b8ee1c7e Merge pull request #242 from sasjs/ms_getgroups
Two new sasjs/server macros and associated tests
2022-05-17 18:05:05 +03:00
Allan Bowe
83eea02240 chore: updating all.sas 2022-05-17 15:03:36 +00:00
Allan Bowe
a14e31804a chore: fix test 2022-05-17 15:01:28 +00:00
Allan Bowe
3fa639ebf7 fix: tests for ms_creategroup 2022-05-17 14:56:11 +00:00
Allan
ed11d44fe8 feat: ms_creategroup and ms_getgroups macros with associated tests 2022-05-17 14:40:38 +00:00
Allan
de4ea8888f fix: updating mp_ds2md to cope with tables with no columns 2022-05-17 14:34:33 +00:00
Allan
ea0a936871 fix: adding codespaces config 2022-05-17 14:34:10 +00:00
Allan Bowe
042987c91e Merge pull request #241 from sasjs/copyfolder_enhancement
feat: Adding copymax parameter
2022-05-17 11:55:52 +03:00
Allan Bowe
6669e74baa chore: running all.sas and updating docs 2022-05-17 08:55:02 +00:00
Ivor Townsend
906f9a139d feat: Adding copymax parameter 2022-05-17 08:53:06 +01:00
Allan Bowe
b31f960635 Merge pull request #240 from sasjs/allanbowe/error-the-function-md-239
fix: avoiding use of md5() in sysfunc().  Closes #239
2022-05-12 13:35:08 +03:00
Allan Bowe
1ed3cb31b5 fix: put wrapper 2022-05-12 10:17:37 +00:00
Allan Bowe
ca7c332f20 fix: avoiding use of md5() in sysfunc(). Closes #239 2022-05-12 10:14:19 +00:00
Allan Bowe
d587b44b34 Merge pull request #238 from sasjs/allanbowe/mp-filtercheck-does-not-237
Updates to filtercheck macro (and tests)
2022-05-11 15:46:02 +03:00
munja
e43aac972a fix: coretable.sas 2022-05-11 13:41:34 +01:00
munja
7dbe31b5d3 fix: ensuring tests passing on sas 9 2022-05-11 13:32:27 +01:00
Allan Bowe
1672c96340 fix: testing fixes 2022-05-10 21:36:16 +00:00
Allan Bowe
453aee2c1f fix: update to mp_filtercheck & tests for special missings 2022-05-10 20:58:52 +00:00
Allan Bowe
00abbdcd65 fix: switching to proc datasets for ddl indexes 2022-05-10 20:58:35 +00:00
Allan Bowe
88685dc585 chore: providing description for DDL folder in main.dox 2022-05-10 19:19:53 +00:00
Allan Bowe
cf8147d6ca fix: making base_ds parameter positional in mp_ds2cards 2022-05-10 19:18:19 +00:00
Allan Bowe
f28f6b1530 Merge pull request #236 from sasjs/allanbowe/error-data-set-sashelp-235
fix: conditional logic for mp_getconstraints, test also updated.  Closes #235
2022-05-10 18:00:20 +03:00
Allan Bowe
cb4ea71e81 fix: conditional logic for mp_getconstraints, test also updated. Closes #235 2022-05-10 14:59:31 +00:00
Allan Bowe
fe94d3781a Merge pull request #234 from sasjs/all-contributors/add-eltociear
docs: add eltociear as a contributor for code
2022-05-10 13:54:01 +03:00
Allan Bowe
7c17b39dad Merge pull request #233 from eltociear/patch-1
chore: fix typo in mddl_sas_cntlout.sas
2022-05-10 13:53:26 +03:00
Allan Bowe
73dab4c651 chore: updating all.sas 2022-05-10 10:53:05 +00:00
allcontributors[bot]
5d72843167 docs: update .all-contributorsrc [skip ci] 2022-05-10 10:52:36 +00:00
allcontributors[bot]
f43df47cff docs: update README.md [skip ci] 2022-05-10 10:52:35 +00:00
Ikko Ashimine
aaca26770b chore: fix typo in mddl_sas_cntlout.sas
accomodate -> accommodate
2022-05-10 19:41:31 +09:00
Allan Bowe
4a124d5bd8 Update README.md 2022-05-09 09:58:31 +01:00
Allan Bowe
03cd52a01a chore: readme update and removal of main.dox in .npmignore (to allow doxygen folder descriptions in downstream apps) 2022-05-08 17:37:05 +00:00
Allan Bowe
da79181b00 Merge pull request #232 from sasjs/allanbowe/update-headers-in-sasjs-231
fix: using latest sasjs headers, closes #231
2022-05-08 01:27:02 +03:00
Allan Bowe
a405104052 fix: using latest sasjs headers, closes #231 2022-05-07 22:25:49 +00:00
Allan Bowe
56fdaa65d2 Merge pull request #230 from sasjs/dttmfix
feat: mf_dttm macro and associated test
2022-05-07 21:12:33 +03:00
Allan Bowe
9d60c49c9f chore: incorrect dependency 2022-05-07 18:04:28 +00:00
Allan Bowe
380170d5ba chore: all.sas 2022-05-07 18:02:16 +00:00
Allan Bowe
4b450f2091 fix: implementation of mf_fmtddtm() 2022-05-07 18:02:00 +00:00
Allan Bowe
1b013fbf1c fix: rename to mf_fmtdttm() 2022-05-07 17:49:06 +00:00
Allan Bowe
bf7459bd2d feat: mf_dttm macro and associated test 2022-05-07 17:45:36 +00:00
Allan Bowe
1096db0846 Merge pull request #229 from sasjs/runtimeissues
Improving WPS compatibility
2022-05-03 18:48:18 +03:00
Allan Bowe
fc9b765246 chore: updating all.sas 2022-05-03 15:46:34 +00:00
Allan Bowe
4a8f7bb014 fix: all the fixings 2022-05-03 15:46:15 +00:00
Allan Bowe
e0469be0d8 feat: updating mf_getvarcount to allow filtering by column type 2022-05-03 15:24:22 +00:00
Allan Bowe
e9e576b5ec fix: forcing misstype to NULL in ms_webout where not supported 2022-05-02 23:17:11 +00:00
Allan Bowe
1a32d114f1 fix: conditional execution of mp_init() 2022-05-02 22:32:32 +00:00
Allan Bowe
94e83f6b8d fix: updating mf_existfeature for constraint check 2022-05-02 22:24:56 +00:00
Allan Bowe
35a6dede6f fix: enabling ms_testservice() without inputs 2022-05-02 13:56:22 +00:00
Allan Bowe
039ec397dd chore: local scoping vars in mp_testservice 2022-04-30 19:16:25 +00:00
Allan Bowe
dce4630eb8 Merge pull request #228 from sasjs/allanbowe/add-markdown-as-mime-227
feat: adding MARKDOWN support to `mp_streamfile()`
2022-04-30 18:58:54 +03:00
Allan Bowe
1e142f042b chore: improving docs 2022-04-30 15:58:00 +00:00
Allan Bowe
7caca2f139 chore: typo in docs 2022-04-30 15:56:36 +00:00
Allan Bowe
61556b2de8 feat: adding MARKDOWN support to mp_streamfile(), correcting Content-type case issue also 2022-04-30 15:53:59 +00:00
Allan Bowe
9e12409389 fix: fileref cleanup in SASJS mode for mp_testservice() 2022-04-29 19:18:03 +00:00
Allan Bowe
c8050f5a79 Merge pull request #226 from sasjs/allanbowe/mp-testservice-error-225
3 new macros and updates to several others.  Supporting sasjs/server and options lrecl=80
2022-04-28 23:58:40 +03:00
munja
cb4f71c7cd chore: all.sas generation 2022-04-28 21:47:42 +01:00
munja
a39f4e4eee fix: ensuring test passes for mfs_httpheader 2022-04-28 21:47:23 +01:00
munja
b525b4171d fix: updating ms_runstp to accept parameters and file inputs. Explicitly setting lrecl everywhere. Adding lrecl=80 as default in testinit.sas 2022-04-28 21:06:28 +01:00
munja
f2d80b3b63 feat: ms_testservice.sas macro for testing services on sasjs/server 2022-04-28 21:02:54 +01:00
munja
96dda87f37 chore: all.sas update 2022-04-28 21:01:41 +01:00
munja
3435509eec fix: avoiding lua compilation issues by setting wide enough lrecl 2022-04-28 20:57:44 +01:00
munja
42f2767129 fix: mac var and chop point issue in mp_chop.sas 2022-04-28 19:20:03 +01:00
munja
099a5f7840 fix: ms_createfile will now overwrite if existing 2022-04-28 11:17:25 +01:00
munja
c83ea705a2 fix: ensuring sufficient lrecl in mf_getuniquefilref 2022-04-27 23:04:55 +01:00
munja
9ea6c875f2 fix: fixing tests for mp_chop 2022-04-27 12:14:26 +01:00
Allan Bowe
0728f72c4f fix: adding mp_chop to mp_testservice (WIP) 2022-04-26 15:29:12 +00:00
Allan Bowe
a90a6f00cf chore: adding test for mp_chop() 2022-04-26 15:13:18 +00:00
Allan Bowe
f71e53af8d feat: mp_chop() macro 2022-04-26 15:07:31 +00:00
Allan Bowe
cc1b971e19 fix: setting blank value to 0 in mf_isint() 2022-04-26 14:04:54 +00:00
Allan Bowe
8484c752ed fix: wip - requires new mp_chop() macro to parse long JSON response 2022-04-25 22:19:46 +00:00
Allan Bowe
8bd31e6c97 fix: tests for ms_createwebservice and mp_createwebservice 2022-04-25 21:33:14 +00:00
Allan Bowe
f9b0f87f44 feat: ms_createwebservice macro (and update to build.py) 2022-04-25 21:14:12 +00:00
Allan Bowe
d7e9f10291 Merge pull request #224 from sasjs/issue223
feat: ms_createuser macro
2022-04-22 14:49:55 +03:00
munja
3edc3587b3 chore: running all.sas 2022-04-21 23:52:04 +01:00
munja
63bf00e28f fix: adding (and fixing) tests for ms_* macro suite 2022-04-21 23:51:47 +01:00
munja
6b2574947a feat: ms_getusers 2022-04-21 23:08:57 +01:00
munja
eb9027ecb6 feat: ms_createuser macro 2022-04-21 22:48:54 +01:00
Allan Bowe
2ad931a566 Merge pull request #222 from sasjs/allanbowe/the-ms-macros-are-not-221
fix: adding authentication to server macros.  Closes #221
2022-04-20 18:55:22 +03:00
Allan Bowe
cebe119304 fix: failing test (chagned response) 2022-04-20 15:53:44 +00:00
Allan Bowe
bd3082d7e3 chore: updating sasjsconfig 2022-04-20 15:45:37 +00:00
Allan Bowe
11da53f1cb fix: adding authentication to server macros. Closes #221 2022-04-20 15:41:36 +00:00
Allan Bowe
c4cb0b2395 Merge pull request #220 from sasjs/perf
fix: using findc instead of regex for faster parsing
2022-04-16 23:31:20 +03:00
munja
58614e9a3d fix: using findc instead of regex for faster parsing 2022-04-16 21:30:18 +01:00
Allan Bowe
c94c334c4b Merge pull request #219 from sasjs/issue218
fix: check 4 special chars b4 replacing. Closes #218
2022-04-16 22:44:35 +03:00
munja
3bf44405f8 chore: alignment 2022-04-16 20:37:47 +01:00
munja
db68a256cb fix: check 4 special chars b4 replacing. Closes #218 2022-04-16 20:33:21 +01:00
Allan Bowe
611fac6338 Merge pull request #217 from sasjs/bom
fix: avoid sending BOM marker to SASjs API
2022-04-14 21:32:25 +03:00
munja
ddd120bb75 fix: avoid sending BOM marker to SASjs API 2022-04-14 19:31:36 +01:00
Allan Bowe
0d75e0bad8 Merge pull request #216 from sasjs/cli-issue-1113
chore(dep): removed sasjs/core and bumped sasjs/cli
2022-04-14 18:25:17 +03:00
Yury Shkoda
ba8c4ac844 chore: Merge branch 'main' of https://github.com/sasjs/core into cli-issue-1113 2022-04-14 18:22:57 +03:00
Allan Bowe
6938a42896 Merge pull request #215 from sasjs/serverconfig
Change test server & update mx_webout() macros
2022-04-14 15:41:44 +03:00
munja
efff77c94e fix: mx_webout 2022-04-14 13:40:14 +01:00
Yury Shkoda
2b10cf6192 chore(dep): removed sasjs/core and bumped sasjs/cli 2022-04-14 14:55:43 +03:00
Allan Bowe
134b91f266 fix: avoiding running tests on viya 2022-04-14 11:14:35 +00:00
Allan Bowe
969f551e10 Merge pull request #214 from sasjs/cond
fix: adding cond/endcond to mp_ds2cards
2022-04-13 23:34:52 +03:00
munja
26623ba085 fix: adding cond/endcond to mp_ds2cards 2022-04-13 21:32:47 +01:00
Allan Bowe
8eb495890d Merge pull request #213 from sasjs/issue212
fix: ensuring indexes are picked up in mp_getpk().  Closes #212
2022-04-13 15:59:22 +03:00
munja
c1a30977f1 chore: removing serverurl 2022-04-13 13:58:55 +01:00
munja
9a6be61651 fix: ensuring indexes are picked up in mp_getpk(). Closes #212 2022-04-13 13:57:43 +01:00
Allan Bowe
388839039e Merge pull request #211 from sasjs/streamhtmlasfile
fix: send html as attachment rather than streamed content
2022-04-11 12:56:02 +03:00
Allan Bowe
e760a89a6a fix: send html as attachment rather than streamed content when mp_streamfile is used on all platforms 2022-04-11 09:54:07 +00:00
Allan Bowe
d2e30267e8 Merge pull request #210 from sasjs/nulfixes
fix: using regex special chars instead of hex constants in mp_jsonout
2022-04-07 11:53:37 +03:00
munja
190dbddfe3 fix: using regex special chars instead of hex constants in mp_jsonout 2022-04-07 09:52:56 +01:00
Allan Bowe
05e769794e Merge pull request #209 from sasjs/missref
fix: avoid: ERROR: Variable "fref" may not be initialized
2022-04-06 18:26:45 +03:00
munja
558ebaf6f2 fix: avoid: ERROR: Variable "fref" may not be initialized 2022-04-06 16:25:50 +01:00
Allan Bowe
970b56fe5a Merge pull request #208 from sasjs/issue207
fix: removing LUA dependency from mv_webout to enable Viya 4 compatibility
2022-04-01 20:42:22 +03:00
munja
c2597bd07b fix: missing dependency in mp_hashdataset.test.sas 2022-04-01 17:29:06 +01:00
munja
c4baca477b fix: removing LUA dependency from mv_webout to enable Viya 4 compatibility 2022-04-01 16:56:49 +01:00
Allan Bowe
7726b0e0b0 Merge pull request #206 from sasjs/ms_getfile
fix: ensuring ms_getfile works on specific installs
2022-03-29 20:41:20 +03:00
munja
0a536245f3 fix: missing dependency in mp_hashdataset.test.sas 2022-03-29 18:40:58 +01:00
munja
edfa9ecc07 fix: ensuring ms_getfile works on specific installs 2022-03-29 18:08:24 +01:00
munja
f4982c85ca fix: adding nonote2err option on mp_hashdataset 2022-03-29 15:00:33 +01:00
Allan Bowe
3ce771d587 Merge pull request #205 from sasjs/issue204
fix: updating mp_hashdataset to cope with STRICT mode.  Adding test a…
2022-03-29 15:47:17 +03:00
munja
72d6b446c3 fix: updating mp_hashdataset to cope with STRICT mode. Adding test and improving sasjs/server compatibility. 2022-03-29 13:22:24 +01:00
Allan Bowe
40d694eec8 Merge pull request #203 from sasjs/invisibles
fix: support SOH, STX and DC1 control characters in mp_jsonout
2022-03-29 12:02:09 +03:00
munja
6af1423666 fix: more invisibles 2022-03-28 16:26:58 +01:00
munja
23a01347f1 fix: support SOH, STX and DC1 control characters in mp_jsonout 2022-03-28 15:54:28 +01:00
Allan Bowe
7c86d6163a Merge pull request #201 from sasjs/mm_assignlib_fix
fix: enabling more descriptive mm_assignlib abort messages when library cannot be assigned
2022-03-21 18:10:56 +02:00
munja
d7233208f1 fix: enabling more descriptive mm_assignlib abort messages when library cannot be assigned 2022-03-21 16:07:27 +00:00
189 changed files with 8892 additions and 4517 deletions

View File

@@ -126,6 +126,15 @@
"contributions": [
"code"
]
},
{
"login": "eltociear",
"name": "Ikko Ashimine",
"avatar_url": "https://avatars.githubusercontent.com/u/22633385?v=4",
"profile": "https://bandism.net/",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,

8
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,8 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/javascript-node/.devcontainer/base.Dockerfile
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
ARG VARIANT="18-bullseye"
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
RUN apt-get update \
&& apt-get install -y doxygen

View File

@@ -0,0 +1,26 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/typescript-node
{
"name": "Node.js & TypeScript",
"build": {
"dockerfile": "Dockerfile",
// Update 'VARIANT' to pick a Node version: 18, 16, 14.
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local on arm64/Apple Silicon.
"args": {
"VARIANT": "16-bullseye"
}
},
// Set *default* container specific settings.json values on container create.
"settings": {},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"SASjs.sasjs-for-vscode"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "npm i && npm i -g @sasjs/cli",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "node"
}

17
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,17 @@
## Issue
Link any related issue(s) in this section.
## Intent
What this PR intends to achieve.
## Implementation
What code changes have been made to achieve the intent.
## Checks
- [ ] Code is formatted correctly (`sasjs lint`).
- [ ] Any new functionality has been unit tested.
- [ ] All unit tests are passing (`sasjs test`).

View File

@@ -6,7 +6,6 @@ sasjs/
.github/
.git-hooks/
.vscode/
main.dox
make_singlefile.sh
*.md
.all-contributorsrc

100
README.md
View File

@@ -18,7 +18,7 @@
[dependency-url]:https://github.com/sasjs/core/blob/main/package.json
Much quality. Many standards. The **Macro Core** library exists to save time and development effort! Herein ye shall find a veritable host of MIT-licenced, production quality SAS macros. These are a mix of tools, utilities, functions and code generators that are useful in the context of [Application Development](https://sasapps.io) on the SAS platform (eg https://datacontroller.io). [Contributions](https://github.com/sasjs/core/blob/main/CONTRIBUTING.md) are welcomed.
Much quality. Many standards. The **Macro Core** library exists to save time and development effort! Herein ye shall find a veritable host of MIT-licenced, production quality SAS macros. These are a mix of tools, utilities, functions and code generators that are useful in the context of [Application Development](https://sasapps.io) on the SAS platform (eg https://datacontroller.io). [Contributions](https://github.com/sasjs/core/blob/main/.github/CONTRIBUTING.md) are welcome.
You can download and compile them all in just two lines of SAS code:
@@ -31,14 +31,14 @@ Documentation: https://core.sasjs.io
## Components
### BASE library (All Platforms)
### BASE folder (All Platforms)
- OS independent
- Works on all SAS Platforms
- No X command
- Prefixes: _mf_, _mp_
### DDL library (All Platforms)
### DDL folder (All Platforms)
- OS independent
- Works on all SAS Platforms
@@ -47,46 +47,14 @@ Documentation: https://core.sasjs.io
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 library (All Platforms)
### FCMP folder (All Platforms)
- Function and macro names are identical, except for special cases
- Prefixes: _mcf_
The fcmp macros are used to generate fcmp functions, and can be used with or without the `proc fcmp` wrapper.
### META library (SAS9 only)
Macros used in SAS EBI, which connect to the metadata server.
- OS independent
- Metadata aware
- No X command
- Prefixes: _mm_
### SERVER library (@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.
- OS independent
- @sasjs/server aware
- No X command
- Prefixes: _ms_
### VIYA library (Viya only)
Macros used for interfacing with SAS Viya.
- OS independent
- No X command
- Prefixes: _mv_, _mvf_
### METAX library (SAS9 only)
- OS specific
- Metadata aware
- X command enabled
- Prefixes: _mmw_,_mmu_,_mmx_
### LUA library
### LUA folder
Wait - this is a macro library - what is LUA doing here? Well, it is a little known fact that you CAN run LUA within a SAS Macro. It has to be written to a text file with a `.lua` extension, from where you can `%include` it. So, without using the `proc lua` wrapper.
@@ -106,13 +74,61 @@ run;
- Prefixes: _ml_
### META folder (SAS9 only)
Macros used in SAS EBI, which connect to the metadata server.
- OS independent
- Metadata aware
- No X command
- Prefixes: _mm_
### METAX folder (SAS9 only)
- OS specific
- Metadata aware
- X command enabled
- Prefixes: _mmw_,_mmu_,_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.
- OS independent
- @sasjs/server aware
- No X command
- Prefixes: _ms_
### VIYA folder (Viya only)
Macros used for interfacing with SAS Viya.
- OS independent
- No X command
- Prefixes: _mv_, _mvf_
### XPLATFORM folder (Viya, Meta, and Server)
Sometimes it is helpful to use a macro that can be used interchangeably regardless of the server type on which is is running (SASVIYA, SAS9, SASJS).
- OS independent
- No X command
- Prefixes: _mx_
## Installation
First, download the repo to a location your SAS system can access. Then update your sasautos path to include the components you wish to have available, eg:
```sas
options insert=(sasautos="/your/path/macrocore/base");
options insert=(sasautos="/your/path/macrocore/meta");
%let repoloc=/your/path/core;
options insert=(sasautos="&repoloc/base");
options insert=(sasautos="&repoloc/ddl");
options insert=(sasautos="&repoloc/fcmp");
options insert=(sasautos="&repoloc/lua");
options insert=(sasautos="&repoloc/meta");
options insert=(sasautos="&repoloc/metax");
options insert=(sasautos="&repoloc/server");
options insert=(sasautos="&repoloc/viya");
options insert=(sasautos="&repoloc/xplatform");
```
The above can be done directly in your sas program, via an autoexec, or an initialisation program.
@@ -142,7 +158,7 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
- _mp_ for macro procedures (which generate sas code)
- _ms_ for macro procedures that will only work with [@sasjs/server](https://github.com/sasjs/server)
- _mv_ for macro procedures that will only work in Viya
- _mx_ for macros that are XCMD enabled (working on both windows and unix)
- _mx_ for macros that work on Viya, SAS 9 EBI and SASjs Server
- follow verb-noun convention
- unix style line endings (lf)
- individual lines should be no more than 80 characters long
@@ -224,12 +240,13 @@ The following repositories are also worth checking out:
* [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)
* [rogerjdeangelis](https://github.com/rogerjdeangelis)
* [scottbass/sas](https://github.com/scottbass/SAS)
* [yabwon/sas_packages](https://github.com/yabwon/SAS_PACKAGES)
## Contributors ✨
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-11-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-12-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
@@ -251,6 +268,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center"><a href="https://github.com/VladislavParhomchik"><img src="https://avatars.githubusercontent.com/u/83717836?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vladislav Parhomchik</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=VladislavParhomchik" title="Tests">⚠️</a> <a href="https://github.com/sasjs/core/pulls?q=is%3Apr+reviewed-by%3AVladislavParhomchik" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/vznesh"><img src="https://avatars.githubusercontent.com/u/28916792?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vignesh T.</b></sub></a><br /><a href="https://github.com/sasjs/core/issues?q=author%3Avznesh" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/yabwon"><img src="https://avatars.githubusercontent.com/u/9314894?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Bart Jablonski</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=yabwon" title="Code">💻</a></td>
<td align="center"><a href="https://bandism.net/"><img src="https://avatars.githubusercontent.com/u/22633385?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=eltociear" title="Code">💻</a></td>
</tr>
</table>

4667
all.sas

File diff suppressed because it is too large Load Diff

View File

@@ -30,7 +30,7 @@
%put No feature was requested for detection;
%end;
%else %if &feature=COLCONSTRAINTS %then %do;
%if %substr(&sysver,1,1)=4 %then 0;
%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 0;
%else 1;
%end;
%else %if &feature=PROCLUA %then %do;
@@ -40,6 +40,12 @@
%else %if "&SYSVLONG" < "9.04.01M3" %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;
%else %if %sysfunc(sysprod(SAS/ACCESS Interface to PC Files)) = 1 %then 1;
%else 0;
%end;
%else %do;
-1
%put &sysmacroname: &feature not found;

View File

@@ -6,9 +6,6 @@
%put %mf_existVarList(sashelp.class, age sex name dummyvar);
<h4> SAS Macros </h4>
@li mf_abort.sas
@param libds 2 part dataset or view reference
@param varlist space separated variable names

42
base/mf_fmtdttm.sas Normal file
View File

@@ -0,0 +1,42 @@
/**
@file
@brief Returns E8601DT26.6 if compatible else DATETIME19.3
@details From our experience in [Data Controller for SAS]
(https://datacontroller.io) deployments, the E8601DT26.6 datetime format has
the widest support when it comes to pass-through SQL queries.
However, it is not supported in WPS or early versions of SAS 9 (M3 and below)
when used as a datetime literal, eg:
data _null_;
demo="%sysfunc(datetime(),E8601DT26.6)"dt;
demo=;
run;
This macro will therefore return DATEITME19.3 as an alternative format
based on the runtime environment so that it can be used in such cases, eg:
data _null_;
demo="%sysfunc(datetime(),%mf_fmtdttm())"dt;
demo=;
run;
<h4> Related Macros </h4>
@li mf_fmtdttm.test.sas
@author Allan Bowe
**/
%macro mf_fmtdttm(
)/*/STORE SOURCE*/;
%if "&sysver"="9.2" or "&sysver"="9.3"
or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")
or "%substr(&sysver,1,1)"="4"
or "%substr(&sysver,1,1)"="5"
%then %do;DATETIME19.3%end;
%else %do;E8601DT26.6%end;
%mend mf_fmtdttm;

View File

@@ -5,8 +5,12 @@
%put %mf_getplatform();
returns:
SASMETA (or SASVIYA)
returns one of:
@li SASMETA
@li SASVIYA
@li SASJS
@li BASESAS
@param switch the param for which to return a platform specific variable
@@ -68,4 +72,4 @@
%else %if &switch=VIYARESTAPI %then %do;
%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)
%end;
%mend mf_getplatform;
%mend mf_getplatform;

View File

@@ -28,15 +28,17 @@
be 8 characters, so a 7 letter prefix would mean `maxtries` should be 10.
if using zero (0) as the prefix, a native assignment is used.
@param [in] maxtries= (1000) the last part of the libref. Must be an integer.
@param [in] lrecl= (32767) Provide a default lrecl with which to initialise
the generated fileref.
@version 9.2
@author Allan Bowe
**/
%macro mf_getuniquefileref(prefix=_,maxtries=1000);
%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);
%local rc fname;
%if &prefix=0 %then %do;
%let rc=%sysfunc(filename(fname,,temp));
%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));
%if &rc %then %put %sysfunc(sysmsg());
&fname
%end;
@@ -47,7 +49,7 @@
%do x=0 %to &maxtries;
%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);
%if %sysfunc(fileref(&fname)) > 0 %then %do;
%let rc=%sysfunc(filename(fname,,temp));
%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));
%if &rc %then %put %sysfunc(sysmsg());
&fname
%return;

View File

@@ -3,12 +3,12 @@
@brief Returns an unused libref
@details Use as follows:
libname mclib0 (work);
libname mclib1 (work);
libname mclib2 (work);
libname mclib0 (work);
libname mclib1 (work);
libname mclib2 (work);
%let libref=%mf_getuniquelibref();
%put &=libref;
%let libref=%mf_getuniquelibref();
%put &=libref;
which returns:

View File

@@ -23,18 +23,19 @@
@author Allan Bowe
**/
%macro mf_getuser(type=META
%macro mf_getuser(
)/*/STORE SOURCE*/;
%local user metavar;
%if &type=OS %then %let metavar=_secureusername;
%else %let metavar=_metaperson;
%local user;
%if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER;
%else %if %symexist(&metavar) %then %do;
%if %length(&&&metavar)=0 %then %let user=&sysuserid;
%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;
%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;
%let user=&SYS_COMPUTE_SESSION_OWNER;
%end;
%else %if %symexist(_metaperson) %then %do;
%if %length(&_metaperson)=0 %then %let user=&sysuserid;
/* sometimes SAS will add @domain extension - remove for consistency */
/* but be sure to quote in case of usernames with commas */
%else %let user=%unquote(%scan(%quote(&&&metavar),1,@));
%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));
%end;
%else %let user=&sysuserid;

View File

@@ -2,31 +2,48 @@
@file
@brief Returns number of variables in a dataset
@details Useful to identify those renagade datasets that have no columns!
Can also be used to count for numeric, or character columns
%put Number of Variables=%mf_getvarcount(sashelp.class);
%put Number of Variables=%mf_getvarcount(sashelp.class);
%put Character Variables=%mf_getvarcount(sashelp.class,typefilter=C);
%put Numeric Variables = %mf_getvarcount(sashelp.class,typefilter=N);
returns:
> Number of Variables=4
@param libds Two part dataset (or view) reference.
@param [in] libds Two part dataset (or view) reference.
@param [in] typefilter= (A) Filter for certain types of column. Valid values:
@li A Count All columns
@li C Count Character columns only
@li N Count Numeric columns only
@version 9.2
@author Allan Bowe
**/
%macro mf_getvarcount(libds
%macro mf_getvarcount(libds,typefilter=A
)/*/STORE SOURCE*/;
%local dsid nvars rc ;
%local dsid nvars rc outcnt x;
%let dsid=%sysfunc(open(&libds));
%let nvars=.;
%let outcnt=0;
%let typefilter=%upcase(&typefilter);
%if &dsid %then %do;
%let nvars=%sysfunc(attrn(&dsid,NVARS));
%if &typefilter=A %then %let outcnt=&nvars;
%else %if &nvars>0 %then %do x=1 %to &nvars;
/* increment based on variable type */
%if %sysfunc(vartype(&dsid,&x))=&typefilter %then %do;
%let outcnt=%eval(&outcnt+1);
%end;
%end;
%let rc=%sysfunc(close(&dsid));
%end;
%else %do;
%put unable to open &libds (rc=&dsid);
%let rc=%sysfunc(close(&dsid));
%end;
&nvars
&outcnt
%mend mf_getvarcount;

View File

@@ -20,8 +20,11 @@
%macro mf_isint(arg
)/*/STORE SOURCE*/;
/* remove minus sign if exists */
/* blank val is not an integer */
%if "&arg"="" %then %do;0%return;%end;
/* remove minus sign if exists */
%local val;
%if "%substr(%str(&arg),1,1)"="-" %then %let val=%substr(%str(&arg),2);
%else %let val=&arg;
@@ -30,4 +33,4 @@
%if %sysfunc(findc(%str(&val),,kd)) %then %do;0%end;
%else %do;1%end;
%mend mf_isint;
%mend mf_isint;

View File

@@ -8,23 +8,29 @@
The method used varies according to the context. Important points:
@li should not use endsas or abort cancel in 9.4m3 environments as this can
cause hung multibridge sessions and result in a frozen STP server
@li should not use endsas or abort cancel in 9.4m3 WIN environments as this
can cause hung multibridge sessions and result in a frozen STP server
@li The use of endsas in 9.4m6+ windows environments for POST requests to the
STP server can result in an empty response body
@li should not use endsas in viya 3.5 as this destroys the session and cannot
fetch results (although both mv_getjoblog.sas and the @sasjs/adapter will
recognise this and fetch the log of the parent session instead)
@li STP environments must finish cleanly to avoid the log being sent to
_webout. To assist with this, we also run stpsrvset('program error', 0)
and set SYSCC=0. We take a unique "soft abort" approach - we open a macro
and set SYSCC=0.
Where possible, we take a unique "soft abort" approach - we open a macro
but don't close it! This works everywhere EXCEPT inside a \%include inside
a macro. For that, we recommend you use mp_include.sas to perform the
include, and then call \%mp_abort(mode=INCLUDE) from the source program (ie,
OUTSIDE of the top-parent macro).
The soft abort has become ineffective in 9.4m6 WINDOWS environments. We are
currently investigating approaches to deal with this.
@param mac= to contain the name of the calling macro
@param mac= (mp_abort.sas) To contain the name of the calling macro. Do not
use &sysmacroname as this will always resolve to MP_ABORT.
@param msg= message to be returned
@param iftrue= supply a condition under which the macro should be executed.
@param iftrue= (1=1) Supply a condition for which the macro should be executed
@param errds= (work.mp_abort_errds) There is no clean way to end a process
within a %include called within a macro. Furthermore, there is no way to
test if a macro is called within a %include. To handle this particular
@@ -45,11 +51,12 @@
@li REGULAR (default)
@li INCLUDE
@version 9.4
@author Allan Bowe
<h4> Related Macros </h4>
@li mp_include.sas
@version 9.4
@author Allan Bowe
@cond
**/
@@ -58,167 +65,197 @@
, mode=REGULAR
)/*/STORE SOURCE*/;
%global sysprocessmode sysprocessname;
%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;
%local fref fid i;
%if not(%eval(%unquote(&iftrue))) %then %return;
%if not(%eval(%unquote(&iftrue))) %then %return;
%put NOTE: /// mp_abort macro executing //;
%if %length(&mac)>0 %then %put NOTE- called by &mac;
%put NOTE - &msg;
%put NOTE: /// mp_abort macro executing //;
%if %length(&mac)>0 %then %put NOTE- called by &mac;
%put NOTE - &msg;
%if %symexist(_SYSINCLUDEFILEDEVICE)
/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */
and "&SYSPROCESSNAME " ne "Compute Server "
%if %symexist(_SYSINCLUDEFILEDEVICE)
/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */
and %superq(SYSPROCESSNAME) ne %str(Compute Server)
%then %do;
%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;
data &errds;
iftrue='1=1';
length mac $100 msg $5000;
mac=symget('mac');
msg=symget('msg');
run;
data _null_;
abort cancel FILE;
run;
%return;
%end;
%end;
/* Web App Context */
%if %symexist(_PROGRAM)
or %superq(SYSPROCESSNAME) = %str(Compute Server)
or &mode=INCLUDE
%then %do;
options obs=max replace mprint;
%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"
%then %do;
%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;
data &errds;
iftrue='1=1';
length mac $100 msg $5000;
mac=symget('mac');
msg=symget('msg');
run;
options nosyntaxcheck;
%end;
%if &mode=INCLUDE %then %do;
%if %sysfunc(exist(&errds))=1 %then %do;
data _null_;
abort cancel FILE;
set &errds;
call symputx('iftrue',iftrue,'l');
call symputx('mac',mac,'l');
call symputx('msg',msg,'l');
putlog (_all_)(=);
run;
%if (&iftrue)=0 %then %return;
%end;
%else %do;
%put &sysmacroname: No include errors found;
%return;
%end;
%end;
/* Stored Process Server web app context */
%if %symexist(_metaport)
or "&SYSPROCESSNAME "="Compute Server "
or &mode=INCLUDE
%then %do;
options obs=max replace nosyntaxcheck mprint;
%if &mode=INCLUDE %then %do;
%if %sysfunc(exist(&errds))=1 %then %do;
data _null_;
set &errds;
call symputx('iftrue',iftrue,'l');
call symputx('mac',mac,'l');
call symputx('msg',msg,'l');
putlog (_all_)(=);
run;
%if (&iftrue)=0 %then %return;
%end;
%else %do;
%put &sysmacroname: No include errors found;
%return;
%end;
%end;
/* extract log errs / warns, if exist */
%local logloc logline;
%global logmsg; /* capture global messages */
%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;
%else %let logloc=%qsysfunc(getoption(LOG));
proc printto log=log;run;
%let logline=0;
%if %length(&logloc)>0 %then %do;
/* extract log errs / warns, if exist */
%local logloc logline;
%global logmsg; /* capture global messages */
%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;
%else %let logloc=%qsysfunc(getoption(LOG));
proc printto log=log;run;
%let logline=0;
%if %length(&logloc)>0 %then %do;
data _null_;
infile &logloc lrecl=5000;
input; putlog _infile_;
i=1;
retain logonce 0;
if (
_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"
) and logonce=0 then
do;
call symputx('logline',_n_);
logonce+1;
end;
run;
/* capture log including lines BEFORE the err */
%if &logline>0 %then %do;
data _null_;
infile &logloc lrecl=5000;
input; putlog _infile_;
input;
i=1;
retain logonce 0;
if (
_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"
) and logonce=0 then
do;
call symputx('logline',_n_);
logonce+1;
end;
run;
/* capture log including lines BEFORE the err */
%if &logline>0 %then %do;
data _null_;
infile &logloc lrecl=5000;
stoploop=0;
if _n_ ge &logline-15 and stoploop=0 then do until (i>22);
call symputx('logmsg',catx('\n',symget('logmsg'),_infile_));
input;
i=1;
stoploop=0;
if _n_ ge &logline-15 and stoploop=0 then do until (i>22);
call symputx('logmsg',catx('\n',symget('logmsg'),_infile_));
input;
i+1;
stoploop=1;
end;
if stoploop=1 then stop;
run;
%end;
%end;
%if %symexist(SYS_JES_JOB_URI) %then %do;
/* setup webout */
OPTIONS NOBOMFILE;
%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;
filename _webout temp lrecl=999999 mod;
%end;
%else %do;
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"
name="_webout.json" lrecl=999999 mod;
%end;
%end;
/* send response in SASjs JSON format */
data _null_;
file _webout mod lrecl=32000 encoding='utf-8';
length msg $32767 ;
sasdatetime=datetime();
msg=symget('msg');
%if &logline>0 %then %do;
msg=cats(msg,'\n\nLog Extract:\n',symget('logmsg'));
%end;
/* escape the quotes */
msg=tranwrd(msg,'"','\"');
/* ditch the CRLFs as chrome complains */
msg=compress(msg,,'kw');
/* quote without quoting the quotes (which are escaped instead) */
msg=cats('"',msg,'"');
if symexist('_debug') then debug=quote(trim(symget('_debug')));
else debug='""';
put '>>weboutBEGIN<<';
put '{"SYSDATE" : "' "&SYSDATE" '"';
put ',"SYSTIME" : "' "&SYSTIME" '"';
put ',"sasjsAbort" : [{';
put ' "MSG":' msg ;
put ' ,"MAC": "' "&mac" '"}]';
put ",""SYSUSERID"" : ""&sysuserid"" ";
put ',"_DEBUG":' debug ;
if symexist('_metauser') then do;
_METAUSER=quote(trim(symget('_METAUSER')));
put ",""_METAUSER"": " _METAUSER;
_METAPERSON=quote(trim(symget('_METAPERSON')));
put ',"_METAPERSON": ' _METAPERSON;
end;
if symexist('SYS_JES_JOB_URI') then do;
SYS_JES_JOB_URI=quote(trim(symget('SYS_JES_JOB_URI')));
put ',"SYS_JES_JOB_URI": ' SYS_JES_JOB_URI;
end;
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
put ',"_PROGRAM" : ' _PROGRAM ;
put ",""SYSCC"" : ""&syscc"" ";
syserrortext=quote(trim(symget('syserrortext')));
put ",""SYSERRORTEXT"" : " syserrortext;
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
put ",""SYSJOBID"" : ""&sysjobid"" ";
put ",""SYSSCPL"" : ""&sysscpl"" ";
put ",""SYSSITE"" : ""&syssite"" ";
sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong;
syswarningtext=quote(trim(symget('syswarningtext')));
put ",""SYSWARNINGTEXT"" : " syswarningtext;
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
put "}" @;
put '>>weboutEND<<';
run;
%put _all_;
%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;
data _null_;
putlog 'stpsrvset program err and syscc';
rc=stpsrvset('program error', 0);
call symputx("syscc",0,"g");
i+1;
stoploop=1;
end;
if stoploop=1 then stop;
run;
%end;
%end;
%if %symexist(SYS_JES_JOB_URI) %then %do;
/* setup webout for Viya */
options nobomfile;
%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;
filename _webout temp lrecl=999999 mod;
%end;
%else %do;
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"
name="_webout.json" lrecl=999999 mod;
%end;
%end;
%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;
options nobomfile;
/* set up http header for SASjs Server */
%let fid=%sysfunc(fopen(&fref,A));
%if &fid=0 %then %do;
%put %str(ERR)OR: %sysfunc(sysmsg());
%return;
%end;
%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));
%let rc=%sysfunc(fwrite(&fid));
%let rc=%sysfunc(fclose(&fid));
%let rc=%sysfunc(filename(&fref));
%end;
/* send response in SASjs JSON format */
data _null_;
file _webout mod lrecl=32000 encoding='utf-8';
length msg syswarningtext syserrortext $32767 mode $10 ;
sasdatetime=datetime();
msg=symget('msg');
%if &logline>0 %then %do;
msg=cats(msg,'\n\nLog Extract:\n',symget('logmsg'));
%end;
/* escape the escapes */
msg=tranwrd(msg,'\','\\');
/* escape the quotes */
msg=tranwrd(msg,'"','\"');
/* ditch the CRLFs as chrome complains */
msg=compress(msg,,'kw');
/* quote without quoting the quotes (which are escaped instead) */
msg=cats('"',msg,'"');
if symexist('_debug') then debug=quote(trim(symget('_debug')));
else debug='""';
if symget('sasjsprocessmode')='Stored Program' then mode='SASJS';
if mode ne 'SASJS' then put '>>weboutBEGIN<<';
put '{"SYSDATE" : "' "&SYSDATE" '"';
put ',"SYSTIME" : "' "&SYSTIME" '"';
put ',"sasjsAbort" : [{';
put ' "MSG":' msg ;
put ' ,"MAC": "' "&mac" '"}]';
put ",""SYSUSERID"" : ""&sysuserid"" ";
put ',"_DEBUG":' debug ;
if symexist('_metauser') then do;
_METAUSER=quote(trim(symget('_METAUSER')));
put ",""_METAUSER"": " _METAUSER;
_METAPERSON=quote(trim(symget('_METAPERSON')));
put ',"_METAPERSON": ' _METAPERSON;
end;
if symexist('SYS_JES_JOB_URI') then do;
SYS_JES_JOB_URI=quote(trim(symget('SYS_JES_JOB_URI')));
put ',"SYS_JES_JOB_URI": ' SYS_JES_JOB_URI;
end;
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
put ',"_PROGRAM" : ' _PROGRAM ;
put ",""SYSCC"" : ""&syscc"" ";
syserrortext=quote(trim(symget('syserrortext')));
put ",""SYSERRORTEXT"" : " syserrortext;
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
put ",""SYSJOBID"" : ""&sysjobid"" ";
put ",""SYSSCPL"" : ""&sysscpl"" ";
put ",""SYSSITE"" : ""&syssite"" ";
sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong;
syswarningtext=quote(trim(symget('syswarningtext')));
put ",""SYSWARNINGTEXT"" : " syswarningtext;
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
put "}" ;
if mode ne 'SASJS' then put '>>weboutEND<<';
run;
%put _all_;
%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;
data _null_;
putlog 'stpsrvset program err and syscc';
rc=stpsrvset('program error', 0);
call symputx("syscc",0,"g");
run;
%if &sysscp=WIN
and 1=0 /* deprecating this logic until we figure out a consistent abort */
and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"
and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;
/* skip approach (below) does not work in windows m6+ envs */
endsas;
%end;
%else %do;
/**
* endsas kills 9.4m3 deployments by orphaning multibridges.
* Abort variants are ungraceful (non zero return code)
@@ -236,28 +273,29 @@
run;
%inc skip;
%end;
%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
/* endsas kills the session making it harder to fetch results */
data _null_;
syswarningtext=symget('syswarningtext');
syserrortext=symget('syserrortext');
abort_msg=symget('msg');
syscc=symget('syscc');
sysuserid=symget('sysuserid');
iftrue=symget('iftrue');
put (_all_)(/=);
call symputx('syscc',0);
abort cancel nolist;
run;
%end;
%else %do;
%abort cancel;
%end;
%end;
%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
/* endsas kills the session making it harder to fetch results */
data _null_;
syswarningtext=symget('syswarningtext');
syserrortext=symget('syserrortext');
abort_msg=symget('msg');
syscc=symget('syscc');
sysuserid=symget('sysuserid');
iftrue=symget('iftrue');
put (_all_)(/=);
call symputx('syscc',0);
abort cancel nolist;
run;
%end;
%else %do;
%put _all_;
%abort cancel;
%end;
%end;
%else %do;
%put _all_;
%abort cancel;
%end;
%mend mp_abort;
/** @endcond */

View File

@@ -106,7 +106,7 @@
proc compare
base=&scopeds(where=(upcase(name) not in (%mf_getquotedstr(&ilist))))
compare=&ds;
compare=&ds noprint;
run;
%if &sysinfo=0 %then %do;

View File

@@ -4,8 +4,8 @@
@details Reads in a file byte by byte and writes it back out. Is an
os-independent method to copy files. In case of naming collision, the
default filerefs can be modified.
Based on:
https://stackoverflow.com/questions/13046116/using-sas-to-copy-a-text-file
Note that if you have a new enough version of SAS, and you don't need features
such as APPEND, you may be better of using the fcopy() function instead.
%mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout)
@@ -45,14 +45,9 @@
,outref=____out /* override default to use own filerefs */
,mode=CREATE
)/*/STORE SOURCE*/;
%local mod outmode;
%if &mode=APPEND %then %do;
%let mod=mod;
%let outmode='a';
%end;
%else %do;
%let outmode='o';
%end;
%local mod;
%if &mode=APPEND %then %let mod=mod;
/* these IN and OUT filerefs can point to anything */
%if &inref = ____in %then %do;
filename &inref &inloc lrecl=1048576 ;
@@ -63,22 +58,17 @@
/* copy the file byte-for-byte */
data _null_;
length filein 8 fileid 8;
filein = fopen("&inref",'I',1,'B');
fileid = fopen("&outref",&outmode,1,'B');
rec = '20'x;
do while(fread(filein)=0);
rc = fget(filein,rec,1);
rc = fput(fileid, rec);
rc =fwrite(fileid);
end;
rc = fclose(filein);
rc = fclose(fileid);
infile &inref lrecl=1 recfm=n;
file &outref &mod recfm=n;
input sourcechar $char1. @@;
format sourcechar hex2.;
put sourcechar char1. @@;
run;
%if &inref = ____in %then %do;
filename &inref clear;
%end;
%if &outref=____out %then %do;
filename &outref clear;
%end;
%mend mp_binarycopy;
%mend mp_binarycopy;

194
base/mp_chop.sas Normal file
View File

@@ -0,0 +1,194 @@
/**
@file
@brief Splits a file of ANY SIZE by reference to a search string.
@details Provide a fileref and a search string to chop off part of a file.
Works by reading in the file byte by byte, then marking the beginning and end
of each matched string, before finally doing the chop.
Choose whether to keep the FIRST or the LAST section of the file. Optionally,
use an OFFSET to fix the precise chop point.
Usage:
%let src="%sysfunc(pathname(work))/file.txt";
%let str=Chop here!;
%let out1="%sysfunc(pathname(work))/file1.txt";
%let out2="%sysfunc(pathname(work))/file2.txt";
%let out3="%sysfunc(pathname(work))/file3.txt";
%let out4="%sysfunc(pathname(work))/file4.txt";
data _null_;
file &src;
put "startsection&str.endsection";
run;
%mp_chop(&src, matchvar=str, keep=FIRST, outfile=&out1)
%mp_chop(&src, matchvar=str, keep=LAST, outfile=&out2)
%mp_chop(&src, matchvar=str, keep=FIRST, matchpoint=END, outfile=&out3)
%mp_chop(&src, matchvar=str, keep=LAST, matchpoint=END, outfile=&out4)
filename results (&out1 &out2 &out3 &out4);
data _null_;
infile results;
input;
list;
run;
Results:
@li `startsection`
@li `Chop here!endsection`
@li `startsectionChop here!`
@li `endsection`
For more examples, see mp_chop.test.sas
@param [in] infile The QUOTED path to the file on which to perform the chop
@param [in] matchvar= Macro variable NAME containing the string to split by
@param [in] matchpoint= (START) Valid values:
@li START - chop at the beginning of the string in `matchvar`.
@li END - chop at the end of the string in `matchvar`.
@param [in] offset= (0) An adjustment to the precise chop location, by
by reference to the `matchpoint`. Should be a positive or negative integer.
@param [in] keep= (FIRST) Valid values:
@li FIRST - keep the section of the file before the chop
@li LAST - keep the section of the file after the chop
@param [in] mdebug= (0) Set to 1 to provide macro debugging
@param outfile= (0) Optional QUOTED path to the adjusted output file (avoids
overwriting the first file).
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mf_getuniquename.sas
<h4> Related Macros </h4>
@li mp_abort.sas
@li mp_gsubfile.sas
@li mp_replace.sas
@li mp_chop.test.sas
@version 9.4
@author Allan Bowe
**/
%macro mp_chop(infile,
matchvar=,
matchpoint=START,
keep=FIRST,
offset=0,
mdebug=0,
outfile=0
)/*/STORE SOURCE*/;
%local fref0 dttm ds1 outref;
%let fref0=%mf_getuniquefileref();
%let ds1=%mf_getuniquename(prefix=allchars);
%let ds2=%mf_getuniquename(prefix=startmark);
%if &outfile=0 %then %let outfile=&infile;
%mp_abort(iftrue= (%length(%superq(&matchvar))=0)
,mac=mp_chop.sas
,msg=%str(&matchvar is an empty variable)
)
/* START */
%let dttm=%sysfunc(datetime());
filename &fref0 &infile lrecl=1 recfm=n;
/* create dataset with one char per row */
data &ds1;
infile &fref0;
input sourcechar $char1. @@;
format sourcechar hex2.;
run;
/* get start & stop position of first matchvar string (one row, two vars) */
data &ds2;
/* set find string to length in bytes to cover trailing spaces */
length string $ %length(%superq(&matchvar));
string =symget("&matchvar");
drop string;
firstchar=char(string,1);
findlen=lengthm(string); /* <- for trailing bytes */
do _N_=1 to nobs;
set &ds1 nobs=nobs point=_N_;
if sourcechar=firstchar then do;
pos=1;
s=0;
do point=_N_ to min(_N_ + findlen -1,nobs);
set &ds1 point=point;
if sourcechar=char(string, pos) then s + 1;
else goto _leave_;
pos+1;
end;
_leave_:
if s=findlen then do;
START =_N_;
_N_ =_N_+ s - 1;
STOP =_N_;
output;
/* matched! */
stop;
end;
end;
end;
stop;
keep START STOP;
run;
%local split;
%let split=0;
data _null_;
set &ds2;
if "&matchpoint"='START' then do;
if "&keep"='FIRST' then mp=start;
else if "&keep"='LAST' then mp=start-1;
end;
else if "&matchpoint"='END' then do;
if "&keep"='FIRST' then mp=stop+1;
else if "&keep"='LAST' then mp=stop;
end;
split=mp+&offset;
call symputx('split',split,'l');
%if &mdebug=1 %then %do;
put (_all_)(=);
%put &=offset;
%end;
run;
%if &split=0 %then %do;
%put &sysmacroname: No match found in &infile for string %superq(&matchvar);
%return;
%end;
data _null_;
file &outfile recfm=n;
set &ds1;
%if &keep=FIRST %then %do;
if _n_ ge &split then stop;
%end;
%else %do;
if _n_ gt &split;
%end;
put sourcechar char1.;
run;
%if &mdebug=0 %then %do;
filename &fref0 clear;
%end;
%else %do;
data _null_;
infile &outfile lrecl=32767;
input;
list;
if _n_>50 then stop;
run;
%end;
/* END */
%put &sysmacroname took %sysevalf(%sysfunc(datetime())-&dttm) seconds to run;
%mend mp_chop;

View File

@@ -16,8 +16,11 @@
%mp_copyfolder(&rootdir,&copydir)
@param source Unquoted path to the folder to copy from.
@param target Unquoted path to the folder to copy to.
@param [in] source Unquoted path to the folder to copy from.
@param [out] target Unquoted path to the folder to copy to.
@param [in] copymax=(MAX) Set to a positive integer to indicate the level of
subdirectory copy recursion - eg 3, to go `./3/levels/deep`. For unlimited
recursion, set to MAX.
<h4> SAS Macros </h4>
@li mf_getuniquename.sas
@@ -31,7 +34,7 @@
**/
%macro mp_copyfolder(source,target);
%macro mp_copyfolder(source,target,copymax=MAX);
%mp_abort(iftrue=(%mf_isdir(&source)=0)
,mac=&sysmacroname
@@ -50,7 +53,7 @@
%let tempds=%mf_getuniquename();
/* recursive directory listing */
%mp_dirlist(path=&source,outds=work.&tempds, maxdepth=MAX)
%mp_dirlist(path=&source,outds=work.&tempds,maxdepth=&copymax)
/* create folders and copy content */
data _null_;
@@ -78,4 +81,4 @@
proc sql;
drop table work.&tempds;
%mend mp_copyfolder;
%mend mp_copyfolder;

View File

@@ -30,6 +30,7 @@
@li mddl_dc_filtersummary.sas
@li mddl_dc_locktable.sas
@li mddl_dc_maxkeytable.sas
@li mf_getuniquename.sas
<h4> Related Macros </h4>
@li mp_filterstore.sas
@@ -46,7 +47,7 @@
%macro mp_coretable(table_ref,libds=0
)/*/STORE SOURCE*/;
%local outds ;
%let outds=%sysfunc(ifc(&libds=0,_data_,&libds));
%let outds=%sysfunc(ifc(&libds=0,%mf_getuniquename(),&libds));
proc sql;
%if &table_ref=DIFFTABLE %then %do;
%mddl_dc_difftable(libds=&outds)
@@ -65,7 +66,8 @@ proc sql;
%end;
%if &libds=0 %then %do;
proc sql;
describe table &syslast;
drop table &syslast;
%end;
%mend mp_coretable;
%mend mp_coretable;

View File

@@ -1,49 +1,13 @@
/**
@file mp_createwebservice.sas
@brief Create a web service in SAS 9 or Viya
@details Creates a SASJS ready Stored Process in SAS 9 or Job Execution
Service in SAS Viya
@brief Create a web service in SAS 9, Viya or SASjs Server
@details This is actually a wrapper for mx_createwebservice.sas, remaining
for legacy purposes. For new apps, use mx_createwebservice.sas.
Usage:
%* compile macros ;
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc;
%* write some code;
filename ft15f001 temp;
parmcards4;
%* fetch any data from frontend ;
%webout(FETCH)
data example1 example2;
set sashelp.class;
run;
%* send data back;
%webout(OPEN)
%webout(ARR,example1) * Array format, fast, suitable for large tables ;
%webout(OBJ,example2) * Object format, easier to work with ;
%webout(CLOSE)
;;;;
%mp_createwebservice(path=/Public/app/common,name=appInit,replace=YES)
<h4> SAS Macros </h4>
@li mf_getplatform.sas
@li mm_createwebservice.sas
@li mv_createwebservice.sas
@li mx_createwebservice.sas
@param [in,out] path= The full folder path where the service will be created
@param [in,out] name= Service name. Avoid spaces.
@param [in] desc= The description of the service (optional)
@param [in] precode= Space separated list of filerefs, pointing to the code
that needs to be attached to the beginning of the service (optional)
@param [in] code= (ft15f001) Space seperated fileref(s) of the actual code to
be added
@param [in] replace= (YES) Select YES to replace any existing service in that
location
@version 9.2
@author Allan Bowe
**/
@@ -53,33 +17,16 @@ Usage:
,code=ft15f001
,desc=This service was created by the mp_createwebservice macro
,replace=YES
,mdebug=0
)/*/STORE SOURCE*/;
%if &syscc ge 4 %then %do;
%put syscc=&syscc - &sysmacroname will not execute in this state;
%return;
%end;
%local platform; %let platform=%mf_getplatform();
%if &platform=SASVIYA %then %do;
%if "&path"="HOME" %then %let path=/Users/&sysuserid/My Folder;
%mv_createwebservice(path=&path
%mx_createwebservice(path=&path
,name=&name
,code=&code
,precode=&precode
,code=&code
,desc=&desc
,replace=&replace
,mdebug=&mdebug
)
%end;
%else %do;
%if "&path"="HOME" %then %let path=/User Folders/&sysuserid/My Folder;
%mm_createwebservice(path=&path
,name=&name
,code=&code
,precode=&precode
,desc=&desc
,replace=&replace
)
%end;
%mend mp_createwebservice;

View File

@@ -152,6 +152,7 @@ run;
data &out_ds;
set &out_ds;
length infoname infoval $60 fref $8;
if _n_=1 then call missing(fref);
rc=filename(fref,filepath);
drop rc infoname fid i close fref;
if file_or_folder='file' then do;

View File

@@ -11,7 +11,7 @@
Usage:
%mp_ds2cards(base_ds=sashelp.class
%mp_ds2cards(sashelp.class
, tgt_ds=work.class
, cards_file= "C:\temp\class.sas"
, showlog=NO
@@ -23,7 +23,7 @@
- explicity setting a unix LF
- constraints / indexes etc
@param [in] base_ds= Should be two level - eg work.blah. This is the table
@param [in] base_ds Should be two level - eg work.blah. This is the table
that is converted to a cards file.
@param [in] tgt_ds= Table that the generated cards file would create.
Optional - if omitted, will be same as BASE_DS.
@@ -48,9 +48,10 @@
@version 9.2
@author Allan Bowe
@cond
**/
%macro mp_ds2cards(base_ds=, tgt_ds=
%macro mp_ds2cards(base_ds, tgt_ds=
,cards_file="%sysfunc(pathname(work))/cardgen.sas"
,maxobs=max
,random_sample=NO
@@ -219,7 +220,8 @@ data _null_;
put ' @file';
put " @brief Datalines for %upcase(%scan(&base_ds,2)) dataset";
put " @details Generated by %nrstr(%%)mp_ds2cards()";
put " Available on github.com/sasjs/core";
put " Source: https://github.com/sasjs/core";
put ' @cond ';
put '**/';
put "data &tgt_ds &indexes;";
put "attrib ";
@@ -252,6 +254,7 @@ data _null_;
;
%end;
put ";";
put 'missing a b c d e f g h i j k l m n o p q r s t u v w x y z _;';
put "datalines4;";
end;
end;
@@ -264,6 +267,7 @@ data _null_;
if __lastobs then do;
put ';;;;';
put 'run;';
put '/** @endcond **/';
stop;
end;
run;
@@ -283,4 +287,5 @@ quit;
%put NOTE-;%put NOTE-;
%put NOTE- %sysfunc(dequote(&cards_file.));
%put NOTE-;%put NOTE-;
%mend mp_ds2cards;
%mend mp_ds2cards;
/** @endcond **/

View File

@@ -57,6 +57,11 @@
%local vars;
%let vars=%upcase(%mf_getvarlist(&libds));
%if %trim(X&vars)=X %then %do;
%put &sysmacroname: Table &libds has no columns!!;
%return;
%end;
/* create the header row */
data _null_;
file &outref;

View File

@@ -92,7 +92,36 @@ 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;
length reason_cd $4032 vtype $1 vnum dsid 8;
/* quick check to ensure column exists */
if upcase(VARIABLE_NM) not in
(%upcase(%mf_getvarlist(&targetds,dlm=%str(,),quote=SINGLE)))
then do;
REASON_CD="Variable "!!cats(variable_nm)!!" not in &targetds";
putlog REASON_CD= VARIABLE_NM=;
call symputx('reason_cd',reason_cd,'l');
call symputx('nobs',_n_,'l');
output;
return;
end;
/* need to open the dataset to get the column type */
dsid=open("&targetds","i");
if dsid>0 then do;
vnum=varnum(dsid,VARIABLE_NM);
if vnum<1 then do;
/* should not happen as was also tested for above */
REASON_CD=cats("Variable (",VARIABLE_NM,") not found in &targetds");
putlog REASON_CD= dsid=;
call symputx('reason_cd',reason_cd,'l');
call symputx('nobs',_n_,'l');
output;
return;
end;
/* now we can get the type */
else vtype=vartype(dsid,vnum);
end;
/* closed list checks */
if GROUP_LOGIC not in ('AND','OR') then do;
@@ -116,17 +145,8 @@ data &outds;
call symputx('nobs',_n_,'l');
output;
end;
if upcase(VARIABLE_NM) not in
(%upcase(%mf_getvarlist(&targetds,dlm=%str(,),quote=SINGLE)))
then do;
REASON_CD="Variable "!!cats(variable_nm)!!" not in &targetds";
putlog REASON_CD= VARIABLE_NM=;
call symputx('reason_cd',reason_cd,'l');
call symputx('nobs',_n_,'l');
output;
end;
if OPERATOR_NM not in
('=','>','<','<=','>=','BETWEEN','IN','NOT IN','NE','CONTAINS','GE','LE')
('=','>','<','<=','>=','NE','GE','LE','BETWEEN','IN','NOT IN','CONTAINS')
then do;
REASON_CD='Invalid OPERATOR_NM: '!!cats(OPERATOR_NM);
putlog REASON_CD= OPERATOR_NM=;
@@ -135,6 +155,18 @@ data &outds;
output;
end;
/* special missing logic */
if vtype='N'
and OPERATOR_NM in ('=','>','<','<=','>=','NE','GE','LE')
and cats(upcase(raw_value)) in (
'.','.A','.B','.C','.D','.E','.F','.G','.H','.I','.J','.K','.L','.M','.N'
'.N','.O','.P','.Q','.R','.S','.T','.U','.V','.W','.X','.Y','.Z','._'
)
then do;
/* valid numeric - exit data step loop */
return;
end;
/* special logic */
if OPERATOR_NM='BETWEEN' then raw_value1=tranwrd(raw_value,' AND ','');
else if OPERATOR_NM in ('IN','NOT IN') then do;

View File

@@ -84,6 +84,9 @@ filename &outref temp;
run;
%end;
%else %do;
proc sort data=&inds;
by SUBGROUP_ID;
run;
data _null_;
file &outref lrecl=32800;
set &inds end=last;

View File

@@ -40,6 +40,22 @@
%let lib=%upcase(&lib);
%let ds=%upcase(&ds);
/**
* Cater for environments where sashelp.vcncolu is not available
*/
%if %sysfunc(exist(sashelp.vcncolu,view))=0 %then %do;
proc sql;
create table &outds(
libref char(8)
,TABLE_NAME char(32)
,constraint_type char(8) label='Constraint Type'
,constraint_name char(32) label='Constraint Name'
,column_name char(32) label='Column'
,constraint_order num
);
%return;
%end;
/**
* Neither dictionary tables nor sashelp provides a constraint order column,
* however they DO arrive in the correct order. So, create the col.

View File

@@ -41,6 +41,7 @@
<h4> SAS Macros </h4>
@li mcf_length.sas
@li mf_getuniquename.sas
@li mf_getvarcount.sas
@li mf_getvarlist.sas
@li mf_getvartype.sas
@li mf_getvarformat.sas
@@ -60,7 +61,7 @@
,outds=work.mp_getmaxvarlengths
)/*/STORE SOURCE*/;
%local vars prefix x var fmt;
%local vars prefix x var fmt srcds;
%let vars=%mf_getvarlist(libds=&libds);
%let prefix=%substr(%mf_getuniquename(),1,25);
%let num2char=%upcase(&num2char);
@@ -70,6 +71,24 @@
%mcf_length(wrap=YES, insert_cmplib=YES)
%end;
%if &num2char=NO
and ("%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5")
and %mf_getvarcount(&libds,typefilter=N) gt 0
%then %do;
/* custom functions not supported in summary operations */
%let srcds=%mf_getuniquename();
data &srcds/view=&srcds;
set &libds;
%do x=1 %to %sysfunc(countw(&vars,%str( )));
%let var=%scan(&vars,&x);
%if %mf_getvartype(&libds,&var)=N %then %do;
&prefix.&x=mcf_length(&var);
%end;
%end;
run;
%end;
%else %let srcds=&libds;
proc sql;
create table &outds (rename=(
%do x=1 %to %sysfunc(countw(&vars,%str( )));
@@ -94,10 +113,15 @@ create table &outds (rename=(
%end;
%end;
%else %do;
max(mcf_length(&var)) as &prefix.&x
%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then %do;
max(&prefix.&x) as &prefix.&x
%end;
%else %do;
max(mcf_length(&var)) as &prefix.&x
%end;
%end;
%end;
from &libds;
from &srcds;
proc transpose data=&outds
out=&outds(rename=(_name_=NAME COL1=MAXLEN));

View File

@@ -55,7 +55,8 @@
)/*/STORE SOURCE*/;
%local engine schema ds1 ds2 ds3 dsn tabs1 tabs2 sum pk4sure pkdefault finalpks;
%local engine schema ds1 ds2 ds3 dsn tabs1 tabs2 sum pk4sure pkdefault finalpks
pkfromindex;
%let lib=%upcase(&lib);
%let ds=%upcase(&ds);
@@ -70,6 +71,7 @@
%let sum=%mf_getuniquename(prefix=getpk_sum);
%let pk4sure=%mf_getuniquename(prefix=getpk_pk4sure);
%let pkdefault=%mf_getuniquename(prefix=getpk_pkdefault);
%let pkfromindex=%mf_getuniquename(prefix=getpk_pkfromindex);
%let finalpks=%mf_getuniquename(prefix=getpk_finalpks);
%local dbg;
@@ -180,9 +182,23 @@ create table &ds1 as
and a.constraint_name=b.constraint_name
order by 1,2,3,4;
/* extract cols from the relevant unique INDEXES */
create table &pkfromindex as
select libname as libref
,memname as table_name
,indxname as constraint_name
,indxpos as constraint_order
,name
from dictionary.indexes
where nomiss='yes' and unique='yes' and upcase(libname)="&lib"
%if &ds ne 0 %then %do;
and upcase(memname)="&ds"
%end;
order by 1,2,3,4;
/* create one table */
data &finalpks;
set &pkdefault &pk4sure ;
set &pkdefault &pk4sure &pkfromindex;
pk_ind=1;
/* if there are multiple unique constraints, take the first */
by libref table_name constraint_name;
@@ -256,4 +272,4 @@ create table &outds as
iftrue=(&mdebug=0)
)
%mend mp_getpk;
%mend mp_getpk;

View File

@@ -48,7 +48,7 @@
outfile=0
)/*/STORE SOURCE*/;
%if "%substr(&sysver,1,4)"="V.04" %then %do;
%if "%substr(&sysver.XX,1,4)"="V.04" %then %do;
%put %str(ERR)OR: Viya 4 does not support the IO library in lua;
%return;
%end;

View File

@@ -1,8 +1,8 @@
/**
@file
@brief Returns a unique hash for a dataset
@details Ignores metadata attributes, used only to hash values. Compared
datasets must be in the same order.
@details Ignores metadata attributes, used only to hash values. If used to
compare datasets, they must have their columns and rows in the same order.
%mp_hashdataset(sashelp.class,outds=myhash)
@@ -17,7 +17,10 @@
@li mf_getattrn.sas
@li mf_getuniquename.sas
@li mf_getvarlist.sas
@li mf_getvartype.sas
@li mp_md5.sas
<h4> Related Files </h4>
@li mp_hashdataset.test.sas
@param [in] libds dataset to hash
@param [in] salt= Provide a salt (could be, for instance, the dataset name)
@@ -35,48 +38,53 @@
%macro mp_hashdataset(
libds,
outds=,
outds=work._data_,
salt=,
iftrue=%str(1=1)
)/*/STORE SOURCE*/;
%if not(%eval(%unquote(&iftrue))) %then %return;
%local keyvar /* roll up the md5 */
prevkeyvar /* retain prev record md5 */
lastvar /* last var in input ds */
cvars nvars;
%if %mf_getattrn(&libds,NLOBS)=0 %then %do;
%put %str(WARN)ING: Dataset &libds is empty, or is not a dataset;
%if not(%eval(%unquote(&iftrue))) %then %return;
/* avoid naming conflict for hash key vars */
%let keyvar=%mf_getuniquename();
%let prevkeyvar=%mf_getuniquename();
%let lastvar=%mf_getuniquename();
%if %mf_getattrn(&libds,NLOBS)=0 %then %do;
data &outds;
length hashkey $32;
hashkey=put(md5("&salt"),$hex32.);
output;
stop;
run;
%put &sysmacroname: Dataset &libds is empty, or is not a dataset;
%put &sysmacroname: hashkey of &outds is based on salt (&salt) only;
%end;
%else %if %mf_getattrn(&libds,NLOBS)<0 %then %do;
%put %str(ERR)OR: Dataset &libds is not a dataset;
%end;
%else %do;
data &outds(rename=(&keyvar=hashkey) keep=&keyvar)
%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;
/nonote2err
%end;
%else %if %mf_getattrn(&libds,NLOBS)<0 %then %do;
%put %str(ERR)OR: Dataset &libds is not a dataset;
%end;
%else %do;
%local keyvar /* roll up the md5 */
prevkeyvar /* retain prev record md5 */
lastvar /* last var in input ds */
varlist var i;
/* avoid naming conflict for hash key vars */
%let keyvar=%mf_getuniquename();
%let prevkeyvar=%mf_getuniquename();
%let lastvar=%mf_getuniquename();
%let varlist=%mf_getvarlist(&libds);
data &outds(rename=(&keyvar=hashkey) keep=&keyvar);
length &prevkeyvar &keyvar $32;
retain &prevkeyvar "%sysfunc(md5(%str(&salt)),$hex32.)";
set &libds end=&lastvar;
/* hash should include previous row */
&keyvar=put(md5(&prevkeyvar
/* loop every column, hashing every individual value */
%do i=1 %to %sysfunc(countw(&varlist));
%let var=%scan(&varlist,&i,%str( ));
%if %mf_getvartype(&libds,&var)=C %then %do;
!!put(md5(trim(&var)),$hex32.)
%end;
%else %do;
!!put(md5(trim(put(&var*1,binary64.))),$hex32.)
%end;
%end;
),$hex32.);
&prevkeyvar=&keyvar;
if &lastvar then output;
run;
%end;
%mend mp_hashdataset;
;
length &prevkeyvar &keyvar $32;
retain &prevkeyvar;
if _n_=1 then &prevkeyvar=put(md5("&salt"),$hex32.);
set &libds end=&lastvar;
/* hash should include previous row */
&keyvar=%mp_md5(
cvars=%mf_getvarlist(&libds,typefilter=C) &prevkeyvar,
nvars=%mf_getvarlist(&libds,typefilter=N)
);
&prevkeyvar=&keyvar;
if &lastvar then output;
run;
%end;
%mend mp_hashdataset;

View File

@@ -67,10 +67,10 @@ options
validvarname=V7 /* avoid special characters etc in variable names */
varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */
varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */
%if %substr(&sysver,1,1) ne 4 %then %do;
%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;
noautocorrect /* disallow misspelled procedure names */
dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */
%end;
;
%mend mp_init;
%mend mp_init;

View File

@@ -1,7 +1,12 @@
/**
@file mp_jsonout.sas
@brief Writes JSON in SASjs format to a fileref
@details PROC JSON is faster but will produce errs like the ones below if
@details This macro can be used to OPEN a JSON stream and send one or more
tables as arrays of rows, where each row can be an object or a nested array.
There are two engines available - DATASTEP or PROCJSON.
PROC JSON is fast but will produce errs like the ones below if
special chars are encountered.
> (ERR)OR: Some code points did not transcode.
@@ -12,6 +17,10 @@
If this happens, try running with ENGINE=DATASTEP.
The DATASTEP engine is used to handle special SAS missing numerics, and
can also convert entire datasets to formatted values. Output JSON is always
in UTF-8.
Usage:
filename tmp temp;
@@ -19,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_;
@@ -46,10 +55,12 @@
@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>
@li mp_ds2fmtds.sas
@@ -63,21 +74,37 @@
%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 stmt_obs;
%let numcols=0;
%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);
%if &action=OPEN %then %do;
options nobomfile;
data _null_;file &jref encoding='utf-8' ;
data _null_;file &jref encoding='utf-8' lrecl=200;
put '{"PROCESSED_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '"';
run;
%end;
%else %if (&action=ARR or &action=OBJ) %then %do;
/* force variable names to always be uppercase in the JSON */
options validvarname=upcase;
data _null_; file &jref encoding='utf-8' mod;
/* To avoid issues with _webout on EBI - such as encoding diffs and truncation
(https://support.sas.com/kb/49/325.html) we use temporary files */
filename _sjs1 temp lrecl=200 ;
data _null_; file _sjs1 encoding='utf-8';
put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";
run;
/* now write to _webout 1 char at a time */
data _null_;
infile _sjs1 lrecl=1 recfm=n;
file &jref mod lrecl=1 recfm=n;
input sourcechar $char1. @@;
format sourcechar hex2.;
put sourcechar char1. @@;
run;
filename _sjs1 clear;
/* grab col defs */
proc contents noprint data=&ds
@@ -131,13 +158,25 @@
%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 */
proc json out=&jref pretty
filename _sjs2 temp lrecl=131068 encoding='utf-8';
proc json out=_sjs2 pretty
%if &action=ARR %then nokeys ;
;export &tempds / nosastags fmtnumeric;
run;
/* send back to webout */
data _null_;
infile _sjs2 lrecl=1 recfm=n;
file &jref mod lrecl=1 recfm=n;
input sourcechar $char1. @@;
format sourcechar hex2.;
put sourcechar char1. @@;
run;
filename _sjs2 clear;
%end;
%else %if &engine=DATASTEP %then %do;
%datastep:
@@ -196,23 +235,34 @@
%else %do;
set &ds;
%end;
&stmt_obs;
format _numeric_ bart.;
%do i=1 %to &numcols;
%if &&typelong&i=char or &fmt=Y %then %do;
&&name&i='"'!!trim(prxchange('s/"/\"/',-1,
prxchange('s/'!!'0A'x!!'/\n/',-1,
prxchange('s/'!!'0D'x!!'/\r/',-1,
prxchange('s/'!!'09'x!!'/\t/',-1,
prxchange('s/\\/\\\\/',-1,&&name&i)
)))))!!'"';
if findc(&&name&i,'"\'!!'0A0D09000E0F01021011'x) then do;
&&name&i='"'!!trim(
prxchange('s/"/\\"/',-1, /* double quote */
prxchange('s/\x0A/\n/',-1, /* new line */
prxchange('s/\x0D/\r/',-1, /* carriage return */
prxchange('s/\x09/\\t/',-1, /* tab */
prxchange('s/\x00/\\u0000/',-1, /* NUL */
prxchange('s/\x0E/\\u000E/',-1, /* SS */
prxchange('s/\x0F/\\u000F/',-1, /* SF */
prxchange('s/\x01/\\u0001/',-1, /* SOH */
prxchange('s/\x02/\\u0002/',-1, /* STX */
prxchange('s/\x10/\\u0010/',-1, /* DLE */
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
prxchange('s/\\/\\\\/',-1,&&name&i)
))))))))))))!!'"';
end;
else &&name&i=quote(cats(&&name&i));
%end;
%end;
run;
/* write to temp loc to avoid _webout truncation
- https://support.sas.com/kb/49/325.html */
filename _sjs temp lrecl=131068 encoding='utf-8';
data _null_; file _sjs lrecl=131068 encoding='utf-8' mod ;
filename _sjs3 temp lrecl=131068 ;
data _null_;
file _sjs3 encoding='utf-8';
if _n_=1 then put "[";
set &tempds;
if _n_>1 then put "," @; put
@@ -220,39 +270,37 @@
%do i=1 %to &numcols;
%if &i>1 %then "," ;
%if &action=OBJ %then """&&name&i"":" ;
&&name&i
"&&name&i"n /* name literal for reserved variable names */
%end;
%if &action=ARR %then "]" ; %else "}" ; ;
/* now write the long strings to _webout 1 byte at a time */
/* close out the table */
data _null_;
length filein 8 fileid 8;
filein=fopen("_sjs",'I',1,'B');
fileid=fopen("&jref",'A',1,'B');
rec='20'x;
do while(fread(filein)=0);
rc=fget(filein,rec,1);
rc=fput(fileid, rec);
rc=fwrite(fileid);
end;
/* close out the table */
rc=fput(fileid, "]");
rc=fwrite(fileid);
rc=fclose(filein);
rc=fclose(fileid);
file _sjs3 mod encoding='utf-8';
put ']';
run;
filename _sjs clear;
data _null_;
infile _sjs3 lrecl=1 recfm=n;
file &jref mod lrecl=1 recfm=n;
input sourcechar $char1. @@;
format sourcechar hex2.;
put sourcechar char1. @@;
run;
filename _sjs3 clear;
%end;
proc sql;
drop table &colinfo, &tempds;
%if &showmeta=YES %then %do;
data _null_; file &jref encoding='utf-8' mod;
%if %substr(&showmeta,1,1)=Y %then %do;
filename _sjs4 temp lrecl=131068 encoding='utf-8';
data _null_;
file _sjs4;
put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";
do i=1 to &numcols;
name=quote(trim(symget(cats('name',i))));
format=quote(trim(symget(cats('fmt',i))));
label=quote(trim(symget(cats('label',i))));
label=quote(prxchange('s/\\/\\\\/',-1,trim(symget(cats('label',i)))));
length=quote(trim(symget(cats('length',i))));
type=quote(trim(symget(cats('typelong',i))));
if i>1 then put "," @@;
@@ -261,6 +309,15 @@
end;
put '}}';
run;
/* send back to webout */
data _null_;
infile _sjs4 lrecl=1 recfm=n;
file &jref mod lrecl=1 recfm=n;
input sourcechar $char1. @@;
format sourcechar hex2.;
put sourcechar char1. @@;
run;
filename _sjs4 clear;
%end;
%end;

View File

@@ -22,6 +22,7 @@
@param [in] loop_secs= (1) Seconds to wait between each lock attempt
<h4> SAS Macros </h4>
@li mf_fmtdttm.sas
@li mp_abort.sas
@li mp_lockfilecheck.sas
@li mf_getuser.sas
@@ -111,7 +112,7 @@ run;
LOCK_LIB ="&lib";
LOCK_DS="&ds";
LOCK_STATUS_CD='LOCKED';
LOCK_START_DTTM="%sysfunc(datetime(),E8601DT26.6)"dt;
LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt;
LOCK_USER_NM="&user";
LOCK_PID="&sysjobid";
LOCK_REF="&ref";
@@ -131,7 +132,7 @@ run;
proc sql;
update &ctl_ds
set LOCK_STATUS_CD='LOCKED'
, LOCK_START_DTTM="%sysfunc(datetime(),E8601DT26.6)"dt
, LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt
, LOCK_USER_NM="&user"
, LOCK_PID="&sysjobid"
, LOCK_REF="&ref"
@@ -206,7 +207,7 @@ run;
proc sql;
update &ctl_ds
set LOCK_STATUS_CD='UNLOCKED'
, LOCK_END_DTTM="%sysfunc(datetime(),E8601DT26.6)"dt
, LOCK_END_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt
, LOCK_USER_NM="&user"
, LOCK_PID="&sysjobid"
, LOCK_REF="&ref"

View File

@@ -35,7 +35,7 @@
@param infile The QUOTED path to the file on which to perform the substitution
@param findvar= Macro variable NAME containing the string to search for
@param replacevar= Macro variable NAME containing the replacement string
@param outfile= (0) Optional QUOTED path to an the adjusted output file (to
@param outfile= (0) Optional QUOTED path to the adjusted output file (to
avoid overwriting the first file).
<h4> SAS Macros </h4>
@@ -43,7 +43,9 @@
@li mf_getuniquename.sas
<h4> Related Macros </h4>
@li mp_gsubfile.test.sas
@li mp_chop.sas
@li mp_gsubfile.sas
@li mp_replace.test.sas
@version 9.4
@author Bartosz Jabłoński

View File

@@ -21,15 +21,19 @@ https://blogs.sas.com/content/sastraining/2012/08/14/jedi-sas-tricks-reset-sas-s
%macro mp_resetoption(option /* the option to reset */
)/*/STORE SOURCE*/;
data _null_;
length code $1500;
startup=getoption("&option",'startupvalue');
current=getoption("&option");
if startup ne current then do;
code =cat('OPTIONS ',getoption("&option",'keyword','startupvalue'),';');
putlog "NOTE: Resetting system option: " code ;
call execute(code );
end;
run;
%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;
data _null_;
length code $1500;
startup=getoption("&option",'startupvalue');
current=getoption("&option");
if startup ne current then do;
code =cat('OPTIONS ',getoption("&option",'keyword','startupvalue'),';');
putlog "NOTE: Resetting system option: " code ;
call execute(code );
end;
run;
%end;
%else %do;
%put &sysmacroname: reset option feature unavailable on &sysvlong;
%end;
%mend mp_resetoption;

View File

@@ -58,6 +58,7 @@
<h4> SAS Macros </h4>
@li mf_existvar.sas
@li mf_fmtdttm.sas
@li mf_getquotedstr.sas
@li mf_getuniquename.sas
@li mf_nobs.sas
@@ -217,12 +218,12 @@ quit;
set keytable="&base_libds"
,keycolumn="&retained_key"
,max_key=%eval(&maxkey+&newkey_cnt)
,processed_dttm="%sysfunc(datetime(),E8601DT26.6)"dt;
,processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt;
%end;
%else %do;
update &maxkeytable
set max_key=%eval(&maxkey+&newkey_cnt)
,processed_dttm="%sysfunc(datetime(),E8601DT26.6)"dt
,processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt
where keytable="&base_libds";
%end;
%mp_lockanytable(UNLOCK

View File

@@ -125,7 +125,7 @@
data &ds1;
set &dslist indsname=&inds_auto;
&hashkey=put(md5(catx('|',%mf_getquotedstr(&key,quote=N))),$hex32.);
&inds_keep=&inds_auto;
&inds_keep=upcase(&inds_auto);
proc sort;
by &inds_keep &hashkey;
run;
@@ -160,8 +160,8 @@ data &ds4;
tgtvar_nm=upcase(tgtvar_nm);
if tgtvar_nm in (%upcase(&vlist));
if &inds_auto="&ds2" then tgtvar_type='N';
else if &inds_auto="&ds3" then tgtvar_type='C';
if upcase(&inds_auto)="&ds2" then tgtvar_type='N';
else if upcase(&inds_auto)="&ds3" then tgtvar_type='C';
else do;
putlog "%str(ERR)OR: unidentified vartype input!" &inds_auto;
call symputx('syscc',98);

View File

@@ -1,8 +1,9 @@
/**
@file
@brief Streams a file to _webout according to content type
@details Will set headers using appropriate functions (SAS 9 vs Viya) and send
content as a binary stream.
@details Will set headers using appropriate functions per the server type
(Viya, EBI, [SASjs Server](https://github.com/sasjs/server)) and stream
content using mp_binarycopy().
Usage:
@@ -12,7 +13,14 @@
%mp_streamfile(contenttype=csv,inloc=/some/where.txt,outname=myfile.txt)
@param [in] contenttype= (TEXT) Either TEXT, ZIP, CSV, EXCEL
@param [in] contenttype= (TEXT) Supported:
@li CSV
@li EXCEL
@li MARKDOWN
@li TEXT
@li ZIP
Feel free to submit PRs to support more mime types! The official list is
here: https://www.iana.org/assignments/media-types/media-types.xhtml
@param [in] inloc= /path/to/file.ext to be sent
@param [in] inref= fileref of file to be sent (if provided, overrides `inloc`)
@param [in] iftrue= (1=1) Provide a condition under which to execute.
@@ -58,7 +66,7 @@ run;
%if &contentype=CSV %then %do;
%if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_;
rc=stpsrv_header('Content-type','application/csv');
rc=stpsrv_header('Content-Type','application/csv');
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
run;
%end;
@@ -68,7 +76,7 @@ run;
contentdisp="attachment; filename=&outname";
%end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,application/csv)
%mfs_httpheader(Content-Type,application/csv)
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
%end;
%end;
@@ -76,7 +84,7 @@ run;
/* suitable for XLS format */
%if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_;
rc=stpsrv_header('Content-type','application/vnd.ms-excel');
rc=stpsrv_header('Content-Type','application/vnd.ms-excel');
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
run;
%end;
@@ -86,14 +94,14 @@ run;
contentdisp="attachment; filename=&outname";
%end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,application/vnd.ms-excel)
%mfs_httpheader(Content-Type,application/vnd.ms-excel)
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
%end;
%end;
%else %if &contentype=GIF or &contentype=JPEG or &contentype=PNG %then %do;
%if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_;
rc=stpsrv_header('Content-type',"image/%lowcase(&contenttype)");
rc=stpsrv_header('Content-Type',"image/%lowcase(&contenttype)");
run;
%end;
%else %if &platform=SASVIYA %then %do;
@@ -101,22 +109,30 @@ run;
contenttype="image/%lowcase(&contenttype)";
%end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,image/%lowcase(&contenttype))
%mfs_httpheader(Content-Type,image/%lowcase(&contenttype))
%end;
%end;
%else %if &contentype=HTML %then %do;
%if &platform=SASVIYA %then %do;
%else %if &contentype=HTML or &contenttype=MARKDOWN %then %do;
%if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_;
rc=stpsrv_header('Content-Type',"text/%lowcase(&contenttype)");
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
run;
%end;
%else %if &platform=SASVIYA %then %do;
filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json"
contenttype="text/html";
contenttype="text/%lowcase(&contenttype)"
contentdisp="attachment; filename=&outname";
%end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,text/html)
%mfs_httpheader(Content-Type,text/%lowcase(&contenttype))
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
%end;
%end;
%else %if &contentype=TEXT %then %do;
%if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_;
rc=stpsrv_header('Content-type','application/text');
rc=stpsrv_header('Content-Type','application/text');
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
run;
%end;
@@ -126,14 +142,14 @@ run;
contentdisp="attachment; filename=&outname";
%end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,application/text)
%mfs_httpheader(Content-Type,application/text)
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
%end;
%end;
%else %if &contentype=WOFF or &contentype=WOFF2 or &contentype=TTF %then %do;
%if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_;
rc=stpsrv_header('Content-type',"font/%lowcase(&contenttype)");
rc=stpsrv_header('Content-Type',"font/%lowcase(&contenttype)");
run;
%end;
%else %if &platform=SASVIYA %then %do;
@@ -141,13 +157,13 @@ run;
contenttype="font/%lowcase(&contenttype)";
%end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,font/%lowcase(&contenttype))
%mfs_httpheader(Content-Type,font/%lowcase(&contenttype))
%end;
%end;
%else %if &contentype=XLSX %then %do;
%if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_;
rc=stpsrv_header('Content-type',
rc=stpsrv_header('Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
run;
@@ -159,7 +175,7 @@ run;
contentdisp="attachment; filename=&outname";
%end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type
%mfs_httpheader(Content-Type
,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
)
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
@@ -168,7 +184,7 @@ run;
%else %if &contentype=ZIP %then %do;
%if (&platform=SASMETA and &streamweb=1) %then %do;
data _null_;
rc=stpsrv_header('Content-type','application/zip');
rc=stpsrv_header('Content-Type','application/zip');
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
run;
%end;
@@ -178,7 +194,7 @@ run;
contentdisp="attachment; filename=&outname";
%end;
%else %if &platform=SASJS %then %do;
%mfs_httpheader(Content-type,application/zip)
%mfs_httpheader(Content-Type,application/zip)
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
%end;
%end;

View File

@@ -1,51 +1,11 @@
/**
@file
@brief Will execute a SASjs web service on SAS 9 or Viya
@details Prepares the input files and retrieves the resulting datasets from
the response JSON.
Note - the _webout fileref should NOT be assigned prior to running this macro.
@param [in] program The _PROGRAM endpoint to test
@param [in] inputfiles=(0) A list of space seperated fileref:filename pairs as
follows:
inputfiles=inref:filename inref2:filename2
@param [in] inputdatasets= (0) All datasets in this space seperated list are
converted into SASJS-formatted CSVs (see mp_ds2csv.sas) files and added to
the list of `inputfiles` for ingestion. The dataset will be sent with the
same name (no need for a colon modifier).
@param [in] inputparams=(0) A dataset containing name/value pairs in the
following format:
|name:$32|value:$1000|
|---|---|
|stpmacname|some value|
|mustbevalidname|can be anything, oops, %abort!!|
@param [in] debug= (log) Provide the _debug value
@param [in] mdebug= (0) Set to 1 to provide macro debugging
@param [in] viyaresult= (WEBOUT_JSON) The Viya result type to return. For
more info, see mv_getjobresult.sas
@param [in] viyacontext= (SAS Job Execution compute context) The Viya compute
context on which to run the service
@param [out] outlib= (0) Output libref to contain the final tables. Set to
0 if the service output is not in JSON format.
@param [out] outref= (0) Output fileref to create, to contain the full _webout
response.
@brief To be deprecated. Will execute a SASjs web service on SAS 9 or Viya
@details Use the mx_testservice.sas macro instead (documentation can be
found there)
<h4> SAS Macros </h4>
@li mf_getplatform.sas
@li mf_getuniquefileref.sas
@li mf_getuniquename.sas
@li mp_abort.sas
@li mp_binarycopy.sas
@li mp_ds2csv.sas
@li mv_getjobresult.sas
@li mv_jobflow.sas
<h4> Related Programs </h4>
@li mp_testservice.test.sas
@li mx_testservice.sas
@version 9.4
@author Allan Bowe
@@ -63,216 +23,17 @@
viyaresult=WEBOUT_JSON,
viyacontext=SAS Job Execution compute context
)/*/STORE SOURCE*/;
%local dbg pcnt fref1 webref i webcount var platform;
%if &mdebug=1 %then %do;
%put &sysmacroname entry vars:;
%put _local_;
%end;
%else %let dbg=*;
/* sanitise inputparams */
%let pcnt=0;
%if &inputparams ne 0 %then %do;
data _null_;
set &inputparams;
if not nvalid(name,'v7') then putlog (_all_)(=);
else if name in (
'program','inputfiles','inputparams','debug','outlib','outref'
) then putlog (_all_)(=);
else do;
x+1;
call symputx(name,quote(cats(value)),'l');
call symputx(cats('pval',x),name,'l');
call symputx('pcnt',x,'l');
end;
run;
%mp_abort(iftrue= (%mf_nobs(&inputparams) ne &pcnt)
,mac=&sysmacroname
,msg=%str(Invalid values in &inputparams)
)
%end;
%mx_testservice(&program,
inputfiles=&inputfiles,
inputdatasets=&inputdatasets,
inputparams=&inputparams,
debug=&debug,
mdebug=&mdebug,
outlib=&outlib,
outref=&outref,
viyaresult=&viyaresult,
viyacontext=&viyacontext
)
/* convert inputdatasets to filerefs */
%if "&inputdatasets" ne "0" %then %do;
%if %quote(&inputfiles)=0 %then %let inputfiles=;
%do i=1 %to %sysfunc(countw(&inputdatasets,%str( )));
%let var=%scan(&inputdatasets,&i,%str( ));
%local dsref&i;
%let dsref&i=%mf_getuniquefileref();
%mp_ds2csv(&var,outref=&&dsref&i,headerformat=SASJS)
%let inputfiles=&inputfiles &&dsref&i:%scan(&var,-1,.);
%end;
%end;
%let fref1=%mf_getuniquefileref();
%let webref=%mf_getuniquefileref();
%let platform=%mf_getplatform();
%if &platform=SASMETA %then %do;
/* parse the input files */
%if %quote(&inputfiles) ne 0 %then %do;
%let webcount=%sysfunc(countw(&inputfiles));
%put &=webcount;
%do i=1 %to &webcount;
%let var=%scan(&inputfiles,&i,%str( ));
%local webfref&i webname&i;
%let webref&i=%scan(&var,1,%str(:));
%let webname&i=%scan(&var,2,%str(:));
%put webref&i=&&webref&i;
%put webname&i=&&webname&i;
%end;
%end;
%else %let webcount=0;
proc stp program="&program";
inputparam _program="&program"
%do i=1 %to &webcount;
%if &webcount=1 %then %do;
_webin_fileref="&&webref&i"
_webin_name="&&webname&i"
%end;
%else %do;
_webin_fileref&i="&&webref&i"
_webin_name&i="&&webname&i"
%end;
%end;
_webin_file_count="&webcount"
_debug="&debug"
%do i=1 %to &pcnt;
/* resolve name only, proc stp fetches value */
&&pval&i=&&&&&&pval&i
%end;
;
%do i=1 %to &webcount;
inputfile &&webref&i;
%end;
outputfile _webout=&webref;
run;
data _null_;
infile &webref;
file &fref1;
input;
length line $10000;
if index(_infile_,'>>weboutBEGIN<<') then do;
line=tranwrd(_infile_,'>>weboutBEGIN<<','');
put line;
end;
else if index(_infile_,'>>weboutEND<<') then do;
line=tranwrd(_infile_,'>>weboutEND<<','');
put line;
stop;
end;
else put _infile_;
run;
data _null_;
infile &fref1;
input;
put _infile_;
run;
%if &outlib ne 0 %then %do;
libname &outlib json (&fref1);
%end;
%if &outref ne 0 %then %do;
filename &outref temp;
%mp_binarycopy(inref=&webref,outref=&outref)
%end;
%end;
%else %if &platform=SASVIYA %then %do;
/* prepare inputparams */
%local ds1;
%let ds1=%mf_getuniquename();
%if "&inputparams" ne "0" %then %do;
proc transpose data=&inputparams out=&ds1;
id name;
var value;
run;
%end;
%else %do;
data &ds1;run;
%end;
/* parse the input files - convert to sasjs params */
%local webcount i var sasjs_tables;
%if %quote(&inputfiles) ne 0 %then %do;
%let webcount=%sysfunc(countw(&inputfiles));
%put &=webcount;
%do i=1 %to &webcount;
%let var=%scan(&inputfiles,&i,%str( ));
%local webfref&i webname&i sasjs&i.data;
%let webref&i=%scan(&var,1,%str(:));
%let webname&i=%scan(&var,2,%str(:));
%put webref&i=&&webref&i;
%put webname&i=&&webname&i;
%let sasjs_tables=&sasjs_tables &&webname&i;
data _null_;
infile &&webref&i lrecl=32767;
input;
if _n_=1 then call symputx("sasjs&i.data",_infile_);
else call symputx(
"sasjs&i.data",cats(symget("sasjs&i.data"),'0D0A'x,_infile_)
);
putlog "&sysmacroname infile: " _infile_;
run;
data &ds1;
set &ds1;
length sasjs&i.data $32767 sasjs_tables $1000;
sasjs&i.data=symget("sasjs&i.data");
sasjs_tables=symget("sasjs_tables");
run;
%end;
%end;
%else %let webcount=0;
data &ds1;
retain _program "&program";
retain _contextname "&viyacontext";
set &ds1;
putlog "&sysmacroname inputparams:";
putlog (_all_)(=);
run;
%mv_jobflow(inds=&ds1
,maxconcurrency=1
,outds=work.results
,outref=&fref1
,mdebug=&mdebug
)
/* show the log */
data _null_;
infile &fref1;
input;
putlog _infile_;
run;
/* get the uri to fetch results */
data _null_;
set work.results;
call symputx('uri',uri);
putlog "&sysmacroname: fetching results for " uri;
run;
/* fetch results from webout.json */
%mv_getjobresult(uri=&uri,
result=&viyaresult,
outref=&outref,
outlib=&outlib,
mdebug=&mdebug
)
%end;
%else %do;
%put %str(ERR)OR: Unrecognised platform: &platform;
%end;
%if &mdebug=0 %then %do;
filename &webref clear;
%end;
%else %do;
%put &sysmacroname exit vars:;
%put _local_;
%end;
%mend mp_testservice;
%mend mp_testservice;

View File

@@ -4,8 +4,8 @@ from pathlib import Path
# Prepare Lua Macros
files = [f for f in Path('lua').iterdir() if f.match("*.lua")]
for file in files:
basename=os.path.basename(file)
name='ml_' + os.path.splitext(basename)[0]
basename = os.path.basename(file)
name = 'ml_' + os.path.splitext(basename)[0]
ml = open('lua/' + name + '.sas', "w")
ml.write("/**\n")
ml.write(" @file " + name + '.sas\n')
@@ -20,50 +20,68 @@ for file in files:
ml.write(" file \"%sysfunc(pathname(work))/" + name + ".lua\";\n")
with open(file) as infile:
for line in infile:
ml.write(" put '" + line.rstrip().replace("'","''") + " ';\n")
ml.write(" put '" + line.rstrip().replace("'", "''") + " ';\n")
ml.write("run;\n\n")
ml.write("%inc \"%sysfunc(pathname(work))/" + name + ".lua\" /source2;\n\n")
ml.write("/* ensure big enough lrecl to avoid lua compilation issues */\n")
ml.write("%local optval;\n")
ml.write("%let optval=%sysfunc(getoption(lrecl));\n")
ml.write("options lrecl=1024;\n\n")
ml.write("/* execute the lua code by using a .lua extension */\n")
ml.write("%inc \"%sysfunc(pathname(work))/" +
name + ".lua\" /source2;\n\n")
ml.write("options lrecl=&optval;\n\n")
ml.write("%mend " + name + ";\n")
ml.close()
# prepare web files
files=['viya/mv_createwebservice.sas','meta/mm_createwebservice.sas']
files = ['viya/mv_createwebservice.sas',
'meta/mm_createwebservice.sas', 'server/ms_createwebservice.sas']
for file in files:
webout0=open('base/mp_jsonout.sas','r')
if file=='viya/mv_createwebservice.sas':
webout1=open('viya/mv_webout.sas',"r")
webout0 = open('base/mp_jsonout.sas', 'r')
webout1 = open('base/mf_getuser.sas', 'r')
if file == 'viya/mv_createwebservice.sas':
webout2 = open('viya/mv_webout.sas', "r")
weboutfiles = [webout0, webout1, webout2]
elif file == 'server/ms_createwebservice.sas':
webout2 = open('server/ms_webout.sas', "r")
webout3 = open('server/mfs_httpheader.sas', 'r')
weboutfiles = [webout0, webout1, webout2, webout3]
else:
webout1=open('meta/mm_webout.sas','r')
webout2=open('base/mf_getuser.sas','r')
outfile=open(file + 'TEMP','w')
infile=open(file,'r')
delrow=0
webout2 = open('meta/mm_webout.sas', 'r')
weboutfiles = [webout0, webout1, webout2]
outfile = open(file + 'TEMP', 'w')
infile = open(file, 'r')
delrow = 0
for line in infile:
if line=='/* WEBOUT BEGIN */\n':
delrow=1
if line == '/* WEBOUT BEGIN */\n':
delrow = 1
outfile.write('/* WEBOUT BEGIN */\n')
weboutfiles=[webout0,webout1,webout2]
for weboutfile in weboutfiles:
stripcomment=1
stripcomment = 1
for w in weboutfile:
if w=='**/\n': stripcomment=0
elif stripcomment==0:
outfile.write(" put '" + w.rstrip().replace("'","''") + " ';\n")
elif delrow==1 and line=='/* WEBOUT END */\n':
delrow=0
outfile.write('/* WEBOUT END */\n')
elif delrow==0:
if w == '**/\n':
stripcomment = 0
elif stripcomment == 0:
outfile.write(
" put '" + w.rstrip().replace("'", "''") + " ';\n")
elif delrow == 1 and line == '/* WEBOUT END */\n':
delrow = 0
outfile.write('/* WEBOUT END */\n')
elif delrow == 0:
outfile.write(line.rstrip() + "\n")
webout0.close()
webout1.close()
webout2.close()
outfile.close()
infile.close()
os.remove(file)
os.rename(file + 'TEMP',file)
os.rename(file + 'TEMP', file)
# Concatenate all macros into a single file
header="""
header = """
/**
@file
@brief Auto-generated file
@@ -84,14 +102,15 @@ options noquotelenmax;
"""
f = open('all.sas', "w") # r / r+ / rb / rb+ / w / wb
f.write(header)
folders=['base','ddl','meta','metax','server','viya','lua','fcmp']
folders = ['base', 'ddl', 'meta', 'metax', 'server', 'viya', 'lua', 'fcmp', 'xplatform']
for folder in folders:
filenames = [fn for fn in Path('./' + folder).iterdir() if fn.match("*.sas")]
filenames = [fn for fn in Path(
'./' + folder).iterdir() if fn.match("*.sas")]
filenames.sort()
with open('mc_' + folder + '.sas', 'w') as outfile:
for fname in filenames:
with open(fname) as infile:
outfile.write(infile.read())
with open('mc_' + folder + '.sas','r') as c:
with open('mc_' + folder + '.sas', 'r') as c:
f.write(c.read())
f.close()

View File

@@ -26,9 +26,19 @@
oldval_num num format=best32. label='Old (numeric) value',
newval_num num format=best32. label='New (numeric) value',
oldval_char char(32765) label='Old (character) value',
newval_char char(32765) label='New (character) value',
constraint pk_mpe_audit
primary key(load_ref,libref,dsn,key_hash,tgtvar_nm)
newval_char char(32765) label='New (character) value'
);
%local lib;
%let libds=%upcase(&libds);
%if %index(&libds,.)=0 %then %let lib=WORK;
%else %let lib=%scan(&libds,1,.);
proc datasets lib=&lib noprint;
modify %scan(&libds,-1,.);
index create
pk_mpe_audit=(load_ref libref dsn key_hash tgtvar_nm)
/nomiss unique;
quit;
%mend mddl_dc_difftable;

View File

@@ -9,19 +9,32 @@
%macro mddl_dc_filterdetail(libds=WORK.FILTER_DETAIL);
%local nn lib;
%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;
%let nn=not null;
%end;
%else %let nn=;
proc sql;
create table &libds(
filter_hash char(32) not null,
filter_line num not null,
group_logic char(3) not null,
subgroup_logic char(3) not null,
subgroup_id num not null,
variable_nm varchar(32) not null,
operator_nm varchar(12) not null,
raw_value varchar(4000) not null,
processed_dttm num not null format=E8601DT26.6,
constraint pk_mpe_filteranytable
primary key(filter_hash,filter_line)
filter_hash char(32) &nn,
filter_line num &nn,
group_logic char(3) &nn,
subgroup_logic char(3) &nn,
subgroup_id num &nn,
variable_nm varchar(32) &nn,
operator_nm varchar(12) &nn,
raw_value varchar(4000) &nn,
processed_dttm num &nn format=E8601DT26.6
);
%let libds=%upcase(&libds);
%if %index(&libds,.)=0 %then %let lib=WORK;
%else %let lib=%scan(&libds,1,.);
proc datasets lib=&lib noprint;
modify %scan(&libds,-1,.);
index create pk_mpe_filterdetail=(filter_hash filter_line)/nomiss unique;
quit;
%mend mddl_dc_filterdetail;

View File

@@ -9,14 +9,27 @@
%macro mddl_dc_filtersummary(libds=WORK.FILTER_SUMMARY);
%local nn lib;
%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;
%let nn=not null;
%end;
%else %let nn=;
proc sql;
create table &libds(
filter_rk num not null,
filter_hash char(32) not null,
filter_table char(41) not null,
processed_dttm num not null format=E8601DT26.6,
constraint pk_mpe_filteranytable
primary key(filter_rk)
filter_rk num &nn,
filter_hash char(32) &nn,
filter_table char(41) &nn,
processed_dttm num &nn format=E8601DT26.6
);
%let libds=%upcase(&libds);
%if %index(&libds,.)=0 %then %let lib=WORK;
%else %let lib=%scan(&libds,1,.);
proc datasets lib=&lib noprint;
modify %scan(&libds,-1,.);
index create filter_rk /nomiss unique;
quit;
%mend mddl_dc_filtersummary;

View File

@@ -9,17 +9,33 @@
%macro mddl_dc_locktable(libds=WORK.LOCKTABLE);
%local nn lib;
%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;
%let nn=not null;
%end;
%else %let nn=;
proc sql;
create table &libds(
lock_lib char(8),
lock_ds char(32),
lock_status_cd char(10) not null,
lock_user_nm char(100) not null ,
lock_status_cd char(10) &nn,
lock_user_nm char(100) &nn ,
lock_ref char(200),
lock_pid char(10),
lock_start_dttm num format=E8601DT26.6,
lock_end_dttm num format=E8601DT26.6,
constraint pk_mp_lockanytable primary key(lock_lib,lock_ds)
lock_end_dttm num format=E8601DT26.6
);
%let libds=%upcase(&libds);
%if %index(&libds,.)=0 %then %let lib=WORK;
%else %let lib=%scan(&libds,1,.);
proc datasets lib=&lib noprint;
modify %scan(&libds,-1,.);
index create
pk_mp_lockanytable=(lock_lib lock_ds)
/nomiss unique;
quit;
%mend mddl_dc_locktable;

View File

@@ -17,8 +17,17 @@
max_key num label=
'Integer representing current max RK or SK value in the KEYTABLE',
processed_dttm num format=E8601DT26.6
label='Datetime this value was last updated',
constraint pk_mpe_maxkeyvalues
primary key(keytable));
label='Datetime this value was last updated'
);
%local lib;
%let libds=%upcase(&libds);
%if %index(&libds,.)=0 %then %let lib=WORK;
%else %let lib=%scan(&libds,1,.);
proc datasets lib=&lib noprint;
modify %scan(&libds,-1,.);
index create keytable /nomiss unique;
quit;
%mend mddl_dc_maxkeytable;

View File

@@ -14,8 +14,8 @@ proc sql;
create table &libds(
FMTNAME char(32) label='Format name'
/*
to accomodate larger START values, mp_loadformat.sas will need the
SQL dependency removed (proc sql needs to accomodate 3 index values in
to accommodate larger START values, mp_loadformat.sas will need the
SQL dependency removed (proc sql needs to accommodate 3 index values in
a 32767 ibufsize limit)
*/
,START char(10000) label='Starting value for format'
@@ -40,4 +40,4 @@ create table &libds(
,LANGUAGE char(8) label='Language for date strings'
);
%mend mddl_sas_cntlout;
%mend mddl_sas_cntlout;

View File

@@ -39,6 +39,14 @@ data _null_;
put 'io.close(file) ';
run;
/* ensure big enough lrecl to avoid lua compilation issues */
%local optval;
%let optval=%sysfunc(getoption(lrecl));
options lrecl=1024;
/* execute the lua code by using a .lua extension */
%inc "%sysfunc(pathname(work))/ml_gsubfile.lua" /source2;
options lrecl=&optval;
%mend ml_gsubfile;

View File

@@ -389,6 +389,14 @@ data _null_;
put '-- JSON.LUA ENDS HERE ';
run;
/* ensure big enough lrecl to avoid lua compilation issues */
%local optval;
%let optval=%sysfunc(getoption(lrecl));
options lrecl=1024;
/* execute the lua code by using a .lua extension */
%inc "%sysfunc(pathname(work))/ml_json.lua" /source2;
options lrecl=&optval;
%mend ml_json;

View File

@@ -18,7 +18,17 @@
statements. Those starting `mp_` are macro _procedures_, which generate
SAS statements, and must therefore be applied accordingly.
*/
*/
/*! \dir ddl
* \brief Data Definition Language files
* \details Provides templates for commonly used tables in sasjs/core.
Attributes:
* OS independent
* No X command
* Prefixes: _mddl_
*/
/*! \dir fcmp
* \brief Macros for generating FCMP functions
@@ -92,4 +102,39 @@
* Auto-generated from the plain source `.lua` files in the same directory
* Prefixes: _ml_
*/
*/
/*! \dir tests/base
* \brief Tests for Base macros
*/
/*! \dir tests/ddlonly
* \brief Tests for DDL macros
*/
/*! \dir tests/sas9only
* \brief Tests for SAS Metadata macros
*/
/*! \dir tests/serveronly
* \brief Tests for SASjs Server macros
*/
/*! \dir tests/viyaonly
* \brief Tests for Viya macros
*/
/*! \dir tests/x-platform
* \brief Tests for cross-platform macros
*/
/*! \dir xplatform
* \brief Cross Platform, works on all SAS servers (Viya, EBI, SASjs)
* \details Useful when you need to run a single piece of code against Viya,
SAS 9 with metadata, or SASjs on Base SAS.
* OS independent
* No X command
* Prefixes: _mx_
*/

View File

@@ -2,7 +2,10 @@
@file mm_adduser2group.sas
@brief Adds a user to a group
@details Adds a user to a metadata group. The macro first checks whether the
user is in that group, and if not, the user is added.
user is in that group, and if not, the user is added.
Note that the macro does not check inherited group memberships - it looks at
direct members only.
Usage:
@@ -12,10 +15,10 @@
@param user= the user name (not displayname)
@param group= the group to which to add the user
@param mdebug= set to 1 to show debug info in log
@param mdebug= (0) set to 1 to show debug info in log
@warning the macro does not check inherited group memberships - it looks at
direct members only
<h4> Related Files </h4>
@li ms_adduser2group.sas
@version 9.3
@author Allan Bowe

View File

@@ -13,8 +13,8 @@
<h4> SAS Macros </h4>
@li mp_abort.sas
@param libref the libref (not name) of the metadata library
@param mAbort= If not assigned, HARD will call %mp_abort(), SOFT will
@param [in] libref The libref (not name) of the metadata library
@param [in] mAbort= If not assigned, HARD will call %mp_abort(), SOFT will
silently return
@returns libname statement
@@ -28,11 +28,11 @@
libref
,mAbort=HARD
)/*/STORE SOURCE*/;
%local mp_abort msg;
%let mp_abort=0;
%if %sysfunc(libref(&libref)) %then %do;
%local mp_abort msg; %let mp_abort=0;
data _null_;
length liburi LibName $200;
length liburi LibName msg $200;
call missing(of _all_);
nobj=metadata_getnobj("omsobj:SASLibrary?@Libref='&libref'",1,liburi);
if nobj=1 then do;
@@ -46,7 +46,24 @@
* not always helpful though. One example, previously received:
* NOTE: Libref XX refers to the same library metadata as libref XX.
*/
call symputx('msg',sysmsg(),'l');
msg=sysmsg();
if msg=:'ERROR: Libref SAVE is not assigned.' then do;
msg=catx(" ",
"Could not assign %upcase(&libref).",
"Please check metadata permissions! Libname:",libname,
"Liburi:",liburi
);
end;
else if msg="ERROR: User does not have appropriate authorization "!!
"level for library SAVE."
then do;
msg=catx(" ",
"ERROR: User does not have appropriate authorization level",
"for library %upcase(&libref), libname:",libname,
"Liburi:",liburi
);
end;
call symputx('msg',msg,'l');
if "&mabort"='HARD' then call symputx('mp_abort',1,'l');
end;
else do;
@@ -65,20 +82,16 @@
end;
run;
%if &mp_abort=1 %then %do;
%mp_abort(iftrue= (&mp_abort=1)
,mac=mm_assignlib.sas
,msg=&msg
)
%return;
%end;
%else %if %length(&msg)>2 %then %do;
%put NOTE: &msg;
%return;
%end;
%put NOTE: &msg;
%end;
%else %do;
%put NOTE: Library &libref is already assigned;
%end;
%mp_abort(iftrue= (&mp_abort=1)
,mac=mm_assignlib.sas
,msg=%superq(msg)
)
%mend mm_assignlib;

View File

@@ -1,22 +1,21 @@
/**
@file mm_createdataset.sas
@brief Create a dataset from a metadata definition
@file
@brief Create an empty dataset from a metadata definition
@details This macro was built to support viewing empty tables in
https://datacontroller.io - a free evaluation copy is available by
contacting the author (Allan Bowe).
https://datacontroller.io
The table can be retrieved using LIBRARY.DATASET reference, or directly
using the metadata URI.
The table can be retrieved using LIBRARY.DATASET reference, or directly
using the metadata URI.
The dataset is written to the WORK library.
The dataset is written to the WORK library.
usage:
Usage:
%mm_createdataset(libds=metlib.some_dataset)
%mm_createdataset(libds=metlib.some_dataset)
or
or
%mm_createdataset(tableuri=G5X8AFW1.BE00015Y)
%mm_createdataset(tableuri=G5X8AFW1.BE00015Y)
<h4> SAS Macros </h4>
@li mm_getlibs.sas
@@ -26,9 +25,9 @@
@param libds= library.dataset metadata source. Note - table names in metadata
can be longer than 32 chars (just fyi, not an issue here)
@param tableuri= Metadata URI of the table to be created
@param outds= The dataset to create, default is `work.mm_createdataset`.
The table name needs to be 32 chars or less as per SAS naming rules.
@param mdebug= set DBG to 1 to disable DEBUG messages
@param outds= (work.mm_createdataset) The dataset to create. The table name
needs to be 32 chars or less as per SAS naming rules.
@param mdebug= (0) Set to 1 to enable DEBUG messages
@version 9.4
@author Allan Bowe
@@ -54,14 +53,23 @@
%mm_gettables(uri=&liburi,outds=&tempds2)
data _null_;
set &tempds2;
if upcase(tablename)="%upcase(%scan(&libds,2,.))";
where upcase(tablename)="%upcase(%scan(&libds,2,.))";
&dbg putlog tableuri=;
call symputx('tableuri',tableuri);
run;
%end;
data;run;%let tempds3=&syslast;
data;run;
%let tempds3=&syslast;
%mm_getcols(tableuri=&tableuri,outds=&tempds3)
%if %mf_nobs(&tempds3)=0 %then %do;
%put &libds (&tableuri) has no columns defined!!;
data &outds;
run;
%return;
%end;
data _null_;
set &tempds3 end=last;
if _n_=1 then call execute('data &outds;');

View File

@@ -17,7 +17,7 @@
%mm_createfolder(path=/some/meta/folder)
@param [in] path= Name of the folder to create.
@param [in] mdebug= set DBG to 1 to disable DEBUG messages
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
@version 9.4

View File

@@ -2,7 +2,8 @@
@file mm_createwebservice.sas
@brief Create a Web Ready Stored Process
@details This macro creates a Type 2 Stored Process with the mm_webout macro
included as pre-code.
(and dependencies) included as pre-code.
Usage:
%* compile macros ;
@@ -96,21 +97,37 @@ data _null_;
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 stmt_obs; ';
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; ';
put ' data _null_;file &jref encoding=''utf-8'' ; ';
put ' data _null_;file &jref encoding=''utf-8'' lrecl=200; ';
put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
put ' run; ';
put '%end; ';
put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
put ' /* force variable names to always be uppercase in the JSON */ ';
put ' options validvarname=upcase; ';
put ' data _null_; file &jref encoding=''utf-8'' mod; ';
put ' /* To avoid issues with _webout on EBI - such as encoding diffs and truncation ';
put ' (https://support.sas.com/kb/49/325.html) we use temporary files */ ';
put ' filename _sjs1 temp lrecl=200 ; ';
put ' data _null_; file _sjs1 encoding=''utf-8''; ';
put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; ';
put ' run; ';
put ' /* now write to _webout 1 char at a time */ ';
put ' data _null_; ';
put ' infile _sjs1 lrecl=1 recfm=n; ';
put ' file &jref mod lrecl=1 recfm=n; ';
put ' input sourcechar $char1. @@; ';
put ' format sourcechar hex2.; ';
put ' put sourcechar char1. @@; ';
put ' run; ';
put ' filename _sjs1 clear; ';
put ' ';
put ' /* grab col defs */ ';
put ' proc contents noprint data=&ds ';
@@ -164,13 +181,25 @@ 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 ' proc json out=&jref pretty ';
put ' filename _sjs2 temp lrecl=131068 encoding=''utf-8''; ';
put ' proc json out=_sjs2 pretty ';
put ' %if &action=ARR %then nokeys ; ';
put ' ;export &tempds / nosastags fmtnumeric; ';
put ' run; ';
put ' /* send back to webout */ ';
put ' data _null_; ';
put ' infile _sjs2 lrecl=1 recfm=n; ';
put ' file &jref mod lrecl=1 recfm=n; ';
put ' input sourcechar $char1. @@; ';
put ' format sourcechar hex2.; ';
put ' put sourcechar char1. @@; ';
put ' run; ';
put ' filename _sjs2 clear; ';
put ' %end; ';
put ' %else %if &engine=DATASTEP %then %do; ';
put ' %datastep: ';
@@ -229,23 +258,34 @@ 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 ' &&name&i=''"''!!trim(prxchange(''s/"/\"/'',-1, ';
put ' prxchange(''s/''!!''0A''x!!''/\n/'',-1, ';
put ' prxchange(''s/''!!''0D''x!!''/\r/'',-1, ';
put ' prxchange(''s/''!!''09''x!!''/\t/'',-1, ';
put ' prxchange(''s/\\/\\\\/'',-1,&&name&i) ';
put ' )))))!!''"''; ';
put ' if findc(&&name&i,''"\''!!''0A0D09000E0F01021011''x) then do; ';
put ' &&name&i=''"''!!trim( ';
put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ ';
put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ ';
put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ ';
put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ ';
put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ ';
put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ ';
put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
put ' prxchange(''s/\\/\\\\/'',-1,&&name&i) ';
put ' ))))))))))))!!''"''; ';
put ' end; ';
put ' else &&name&i=quote(cats(&&name&i)); ';
put ' %end; ';
put ' %end; ';
put ' run; ';
put ' ';
put ' /* write to temp loc to avoid _webout truncation ';
put ' - https://support.sas.com/kb/49/325.html */ ';
put ' filename _sjs temp lrecl=131068 encoding=''utf-8''; ';
put ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod ; ';
put ' filename _sjs3 temp lrecl=131068 ; ';
put ' data _null_; ';
put ' file _sjs3 encoding=''utf-8''; ';
put ' if _n_=1 then put "["; ';
put ' set &tempds; ';
put ' if _n_>1 then put "," @; put ';
@@ -253,39 +293,37 @@ data _null_;
put ' %do i=1 %to &numcols; ';
put ' %if &i>1 %then "," ; ';
put ' %if &action=OBJ %then """&&name&i"":" ; ';
put ' &&name&i ';
put ' "&&name&i"n /* name literal for reserved variable names */ ';
put ' %end; ';
put ' %if &action=ARR %then "]" ; %else "}" ; ; ';
put ' /* now write the long strings to _webout 1 byte at a time */ ';
put ' ';
put ' /* close out the table */ ';
put ' data _null_; ';
put ' length filein 8 fileid 8; ';
put ' filein=fopen("_sjs",''I'',1,''B''); ';
put ' fileid=fopen("&jref",''A'',1,''B''); ';
put ' rec=''20''x; ';
put ' do while(fread(filein)=0); ';
put ' rc=fget(filein,rec,1); ';
put ' rc=fput(fileid, rec); ';
put ' rc=fwrite(fileid); ';
put ' end; ';
put ' /* close out the table */ ';
put ' rc=fput(fileid, "]"); ';
put ' rc=fwrite(fileid); ';
put ' rc=fclose(filein); ';
put ' rc=fclose(fileid); ';
put ' file _sjs3 mod encoding=''utf-8''; ';
put ' put '']''; ';
put ' run; ';
put ' filename _sjs clear; ';
put ' data _null_; ';
put ' infile _sjs3 lrecl=1 recfm=n; ';
put ' file &jref mod lrecl=1 recfm=n; ';
put ' input sourcechar $char1. @@; ';
put ' format sourcechar hex2.; ';
put ' put sourcechar char1. @@; ';
put ' run; ';
put ' filename _sjs3 clear; ';
put ' %end; ';
put ' ';
put ' proc sql; ';
put ' drop table &colinfo, &tempds; ';
put ' ';
put ' %if &showmeta=YES %then %do; ';
put ' data _null_; file &jref encoding=''utf-8'' mod; ';
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 ' put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{"; ';
put ' do i=1 to &numcols; ';
put ' name=quote(trim(symget(cats(''name'',i)))); ';
put ' format=quote(trim(symget(cats(''fmt'',i)))); ';
put ' label=quote(trim(symget(cats(''label'',i)))); ';
put ' label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i))))); ';
put ' length=quote(trim(symget(cats(''length'',i)))); ';
put ' type=quote(trim(symget(cats(''typelong'',i)))); ';
put ' if i>1 then put "," @@; ';
@@ -294,6 +332,15 @@ data _null_;
put ' end; ';
put ' put ''}}''; ';
put ' run; ';
put ' /* send back to webout */ ';
put ' data _null_; ';
put ' infile _sjs4 lrecl=1 recfm=n; ';
put ' file &jref mod lrecl=1 recfm=n; ';
put ' input sourcechar $char1. @@; ';
put ' format sourcechar hex2.; ';
put ' put sourcechar char1. @@; ';
put ' run; ';
put ' filename _sjs4 clear; ';
put ' %end; ';
put '%end; ';
put ' ';
@@ -303,8 +350,28 @@ data _null_;
put ' run; ';
put '%end; ';
put '%mend mp_jsonout; ';
put ' ';
put '%macro mf_getuser( ';
put ')/*/STORE SOURCE*/; ';
put ' %local user; ';
put ' ';
put ' %if %symexist(_sasjs_username) %then %let user=&_sasjs_username; ';
put ' %else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do; ';
put ' %let user=&SYS_COMPUTE_SESSION_OWNER; ';
put ' %end; ';
put ' %else %if %symexist(_metaperson) %then %do; ';
put ' %if %length(&_metaperson)=0 %then %let user=&sysuserid; ';
put ' /* sometimes SAS will add @domain extension - remove for consistency */ ';
put ' /* but be sure to quote in case of usernames with commas */ ';
put ' %else %let user=%unquote(%scan(%quote(&_metaperson),1,@)); ';
put ' %end; ';
put ' %else %let user=&sysuserid; ';
put ' ';
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 ' ,showmeta=N ';
put '); ';
put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug ';
put ' sasjs_tables; ';
@@ -375,9 +442,10 @@ data _null_;
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 ' data;run;%let tempds=%scan(&syslast,2,.); ';
put ' ods output Members=&tempds; ';
put ' proc datasets library=WORK memtype=data; ';
@@ -388,11 +456,11 @@ data _null_;
put ' i+1; ';
put ' call symputx(cats(''wt'',i),name,''l''); ';
put ' call symputx(''wtcnt'',i,''l''); ';
put ' data _null_; file &fref mod encoding=''utf-8''; ';
put ' data _null_; file _sjsref mod encoding=''utf-8''; ';
put ' put ",""WORK"":{"; ';
put ' %do i=1 %to &wtcnt; ';
put ' %let wt=&&wt&i; ';
put ' data _null_; file &fref mod encoding=''utf-8''; ';
put ' data _null_; file _sjsref mod encoding=''utf-8''; ';
put ' dsid=open("WORK.&wt",''is''); ';
put ' nlobs=attrn(dsid,''NLOBS''); ';
put ' nvars=attrn(dsid,''NVARS''); ';
@@ -401,16 +469,16 @@ 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 ' data _null_; file &fref mod encoding=''utf-8''; ';
put ' %mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y,maxobs=10) ';
put ' data _null_; file _sjsref mod encoding=''utf-8''; ';
put ' put "}"; ';
put ' %end; ';
put ' data _null_; file &fref mod encoding=''utf-8''; ';
put ' data _null_; file _sjsref mod encoding=''utf-8''; ';
put ' put "}"; ';
put ' run; ';
put ' %end; ';
put ' /* close off json */ ';
put ' data _null_;file &fref mod encoding=''utf-8''; ';
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()"" "; ';
@@ -421,14 +489,17 @@ data _null_;
put ' put '',"_METAPERSON": '' _METAPERSON; ';
put ' put '',"_PROGRAM" : '' _PROGRAM ; ';
put ' put ",""SYSCC"" : ""&syscc"" "; ';
put ' put ",""SYSERRORTEXT"" : ""&syserrortext"" "; ';
put ' put ",""SYSENCODING"" : ""&sysencoding"" "; ';
put ' syserrortext=quote(cats(symget(''SYSERRORTEXT''))); ';
put ' put '',"SYSERRORTEXT" : '' syserrortext; ';
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
put ' put ",""SYSSCPL"" : ""&sysscpl"" "; ';
put ' put ",""SYSSITE"" : ""&syssite"" "; ';
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
put ' put '',"SYSVLONG" : '' sysvlong; ';
put ' put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" "; ';
put ' syswarningtext=quote(cats(symget(''SYSWARNINGTEXT''))); ';
put ' put '',"SYSWARNINGTEXT" : '' syswarningtext; ';
put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" ''; ';
put ' length memsize $32; ';
put ' memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)"; ';
@@ -439,28 +510,19 @@ data _null_;
put ' put ''>>weboutEND<<''; ';
put ' %end; ';
put ' run; ';
put ' /* now write to _webout 1 char at a time */ ';
put ' data _null_; ';
put ' infile _sjsref lrecl=1 recfm=n; ';
put ' file &fref mod lrecl=1 recfm=n; ';
put ' input sourcechar $char1. @@; ';
put ' format sourcechar hex2.; ';
put ' put sourcechar char1. @@; ';
put ' run; ';
put ' filename _sjsref clear; ';
put ' ';
put '%end; ';
put ' ';
put '%mend mm_webout; ';
put ' ';
put '%macro mf_getuser(type=META ';
put ')/*/STORE SOURCE*/; ';
put ' %local user metavar; ';
put ' %if &type=OS %then %let metavar=_secureusername; ';
put ' %else %let metavar=_metaperson; ';
put ' ';
put ' %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER; ';
put ' %else %if %symexist(&metavar) %then %do; ';
put ' %if %length(&&&metavar)=0 %then %let user=&sysuserid; ';
put ' /* sometimes SAS will add @domain extension - remove for consistency */ ';
put ' /* but be sure to quote in case of usernames with commas */ ';
put ' %else %let user=%unquote(%scan(%quote(&&&metavar),1,@)); ';
put ' %end; ';
put ' %else %let user=&sysuserid; ';
put ' ';
put ' %quote(&user) ';
put ' ';
put '%mend mf_getuser; ';
/* WEBOUT END */
put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO);';
put ' %mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt,missing=&missing';
@@ -479,7 +541,7 @@ run;
%if &x>1 %then %let mod=mod;
%let fref=%scan(&freflist,&x);
%put &sysmacroname: adding &fref;
%&mD.put &sysmacroname: adding &fref;
data _null_;
file "&work/&tmpfile" lrecl=3000 &mod;
infile &fref;
@@ -515,12 +577,10 @@ data _null_;
if rc=0 then call symputx('url',url,'l');
run;
%put ;%put ;%put ;%put ;%put ;%put ;
%put &sysmacroname: STP &name successfully created in &path;
%put ;%put ;%put ;
%put Check it out here:;
%put ;%put ;%put ;
%put &url?_PROGRAM=&path/&name;
%put ;%put ;%put ;%put ;%put ;%put ;
%put ;%put ;%put ;
%mend mm_createwebservice;

View File

@@ -8,17 +8,15 @@
Usage:
options ps=max nonotes nosource;
%mm_getfoldertree(root=/My/Meta/Path, outds=iwantthisdataset)
options notes source;
options ps=max nonotes nosource;
%mm_getfoldertree(root=/My/Meta/Path, outds=iwantthisdataset)
options notes source;
@param [in] root= the parent folder under which to return all contents
@param [out] outds= the dataset to create that contains the list of
directories
@param [in] mDebug= set to 1 to show debug messages in the log
<h4> SAS Macros </h4>
@version 9.4
@author Allan Bowe

View File

@@ -21,6 +21,7 @@
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mp_abort.sas
@author Allan Bowe
@@ -57,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);
@@ -70,11 +71,10 @@ data _null_;
else put (_all_)(=);
run;
%if &tsuri=stopifempty %then %do;
%put %str(WARN)ING: &tree&name.(StoredProcess) not found!;
%return;
%end;
%mp_abort(iftrue= (&tsuri=stopifempty)
,mac=mm_getstpcode
,msg=%str(&tree&name.(StoredProcess) not found!)
)
/**
* Now we can extract the textstore

View File

@@ -8,17 +8,18 @@
%mm_getusers()
Optionally, filter for a user (useful to get the uri):
%mm_getusers(user=&_metaperson)
@param outds the dataset to create that contains the list of libraries
@returns outds dataset containing all users, with the following columns:
- uri
- name
@warning The following filenames are created and then de-assigned:
filename sxlemap clear;
filename response clear;
libname _XML_ clear;
@param user= (0) Set to a metadata user to filter on that user
@param outds= (work.mm_getusers) The output table to create
@version 9.3
@author Allan Bowe
@@ -26,23 +27,49 @@
**/
%macro mm_getusers(
outds=work.mm_getusers
outds=work.mm_getusers,
user=0
)/*/STORE SOURCE*/;
filename response temp;
proc metadata in= '<GetMetadataObjects>
<Reposid>$METAREPOSITORY</Reposid>
<Type>Person</Type>
<NS>SAS</NS>
<Flags>0</Flags>
<Options>
<Templates>
<Person Name=""/>
</Templates>
</Options>
</GetMetadataObjects>'
out=response;
run;
%if %superq(user)=0 %then %do;
proc metadata in= '<GetMetadataObjects>
<Reposid>$METAREPOSITORY</Reposid>
<Type>Person</Type>
<NS>SAS</NS>
<Flags>0</Flags>
<Options>
<Templates>
<Person Name=""/>
</Templates>
</Options>
</GetMetadataObjects>'
out=response;
run;
%end;
%else %do;
filename inref temp;
data _null_;
file inref;
put "<GetMetadataObjects>";
put "<Reposid>$METAREPOSITORY</Reposid>";
put "<Type>Person</Type>";
put "<NS>SAS</NS>";
put "<!-- Specify the OMI_XMLSELECT (128) flag -->";
put "<Flags>128</Flags>";
put "<Options>";
put "<Templates>";
put '<Person Name=""/>';
put "</Templates>";
length string $10000;
string=cats('<XMLSELECT search="Person[@Name=',"'&user'",']"/>');
put string;
put "</Options>";
put "</GetMetadataObjects>";
run;
proc metadata in=inref out=response;
run;
%end;
filename sxlemap temp;
data _null_;

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

@@ -4,23 +4,23 @@
@details This macro should be added to the start of each Stored Process,
**immediately** followed by a call to:
%mm_webout(FETCH)
%mm_webout(FETCH)
This will read all the input data and create same-named SAS datasets in the
WORK library. You can then insert your code, and send data back using the
following syntax:
This will read all the input data and create same-named SAS datasets in the
WORK library. You can then insert your code, and send data back using the
following syntax:
data some datasets; * make some data ;
retain some columns;
run;
data some datasets; * make some data ;
retain some columns;
run;
%mm_webout(OPEN)
%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;
%mm_webout(OBJ,datasets) * Object format, easier to work with ;
%mm_webout(OPEN)
%mm_webout(ARR,some) * Array format, fast, suitable for large tables ;
%mm_webout(OBJ,datasets) * Object format, easier to work with ;
Finally, wrap everything up send some helpful system variables too
Finally, wrap everything up send some helpful system variables too
%mm_webout(CLOSE)
%mm_webout(CLOSE)
@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE
@@ -30,17 +30,21 @@
@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"}}`
<h4> SAS Macros </h4>
@li mp_jsonout.sas
@version 9.3
@author Allan Bowe
**/
%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL
,showmeta=NO
,showmeta=N
);
%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug
sasjs_tables;
@@ -111,9 +115,10 @@
)
%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;
data;run;%let tempds=%scan(&syslast,2,.);
ods output Members=&tempds;
proc datasets library=WORK memtype=data;
@@ -124,11 +129,11 @@
i+1;
call symputx(cats('wt',i),name,'l');
call symputx('wtcnt',i,'l');
data _null_; file &fref mod encoding='utf-8';
data _null_; file _sjsref mod encoding='utf-8';
put ",""WORK"":{";
%do i=1 %to &wtcnt;
%let wt=&&wt&i;
data _null_; file &fref mod encoding='utf-8';
data _null_; file _sjsref mod encoding='utf-8';
dsid=open("WORK.&wt",'is');
nlobs=attrn(dsid,'NLOBS');
nvars=attrn(dsid,'NVARS');
@@ -137,16 +142,16 @@
put " ""&wt"" : {";
put '"nlobs":' nlobs;
put ',"nvars":' nvars;
%mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=YES)
data _null_; file &fref mod encoding='utf-8';
%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y,maxobs=10)
data _null_; file _sjsref mod encoding='utf-8';
put "}";
%end;
data _null_; file &fref mod encoding='utf-8';
data _null_; file _sjsref mod encoding='utf-8';
put "}";
run;
%end;
/* close off json */
data _null_;file &fref mod encoding='utf-8';
data _null_;file _sjsref mod encoding='utf-8';
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
put ",""SYSUSERID"" : ""&sysuserid"" ";
put ",""MF_GETUSER"" : ""%mf_getuser()"" ";
@@ -157,14 +162,17 @@
put ',"_METAPERSON": ' _METAPERSON;
put ',"_PROGRAM" : ' _PROGRAM ;
put ",""SYSCC"" : ""&syscc"" ";
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
put ",""SYSENCODING"" : ""&sysencoding"" ";
syserrortext=quote(cats(symget('SYSERRORTEXT')));
put ',"SYSERRORTEXT" : ' syserrortext;
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
put ",""SYSJOBID"" : ""&sysjobid"" ";
put ",""SYSSCPL"" : ""&sysscpl"" ";
put ",""SYSSITE"" : ""&syssite"" ";
sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong;
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" ";
syswarningtext=quote(cats(symget('SYSWARNINGTEXT')));
put ',"SYSWARNINGTEXT" : ' syswarningtext;
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
length memsize $32;
memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";
@@ -175,6 +183,16 @@
put '>>weboutEND<<';
%end;
run;
/* now write to _webout 1 char at a time */
data _null_;
infile _sjsref lrecl=1 recfm=n;
file &fref mod lrecl=1 recfm=n;
input sourcechar $char1. @@;
format sourcechar hex2.;
put sourcechar char1. @@;
run;
filename _sjsref clear;
%end;
%mend mm_webout;

2443
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,14 +26,13 @@
"homepage": "https://core.sasjs.io",
"main": "index.js",
"scripts": {
"build": "sasjs cbd -t viya",
"build": "sasjs cbd -t server",
"docs": "sasjs doc -t docsonly && ./sasjs/utils/build.sh",
"test": "sasjs test -t viya",
"test": "sasjs test -t server",
"lint": "sasjs lint",
"prepare": "git rev-parse --git-dir && git config core.hooksPath ./.git-hooks || true"
},
"devDependencies": {
"@sasjs/cli": "3.6.0",
"@sasjs/core": "4.4.7"
"@sasjs/cli": "3.13.0"
}
}

View File

@@ -4,13 +4,12 @@
"base",
"ddl",
"fcmp",
"meta",
"metax",
"server",
"viya",
"lua",
"tests/crossplatform",
"tests/ddl"
"server",
"xplatform",
"tests/base",
"tests/ddlonly",
"tests/x-platform"
],
"docConfig": {
"displayMacroCore": false,
@@ -28,36 +27,33 @@
"mcTestAppLoc": "/Public/temp/macrocore"
}
},
"defaultTarget": "viya",
"defaultTarget": "server",
"targets": [
{
"name": "viya",
"serverUrl": "https://sas.analytium.co.uk",
"serverUrl": "",
"serverType": "SASVIYA",
"httpsAgentOptions": {
"allowInsecureRequests": false
},
"appLoc": "/Public/temp/macrocore",
"appLoc": "/Public/app/macrocore",
"macroFolders": [
"viya",
"tests/viyaonly"
],
"programFolders": [],
"binaryFolders": [],
"deployConfig": {
"deployServicePack": true,
"deployScripts": []
},
"contextName": "SAS Job Execution compute context"
},
{
"name": "sas9",
"serverUrl": "https://sas.analytium.co.uk:8343",
"serverUrl": "",
"serverType": "SAS9",
"httpsAgentOptions": {
"allowInsecureRequests": false
},
"appLoc": "/Shared Data/temp/macrocore",
"macroFolders": [
"meta",
"metax",
"tests/sas9only"
],
"programFolders": [],
@@ -71,28 +67,28 @@
},
{
"name": "server",
"serverUrl": "https://sas.analytium.co.uk:5000",
"serverUrl": "",
"serverType": "SASJS",
"httpsAgentOptions": {
"allowInsecureRequests": false
},
"appLoc": "/sasjs/core",
"macroFolders": [
"server",
"tests/serveronly"
],
"programFolders": [],
"binaryFolders": [],
"deployConfig": {
"deployServicePack": true,
"deployScripts": []
}
]
},
{
"name": "docsonly",
"serverType": "SAS9",
"serverType": "SASJS",
"appLoc": "dummy",
"macroFolders": [
"meta",
"metax",
"server",
"viya",
"tests/sas9only",
"tests/serveronly",
"tests/viyaonly"
]
},
@@ -102,6 +98,7 @@
"serverType": "SASVIYA",
"appLoc": "/Public/temp/macrocore",
"macroFolders": [
"viya",
"tests/viyaonly"
],
"deployConfig": {
@@ -110,4 +107,4 @@
"contextName": "SAS Job Execution compute context"
}
]
}
}

View File

@@ -12,7 +12,7 @@
Usage:
%mfs_httpheader(Content-type,application/csv)
%mfs_httpheader(Content-Type,application/csv)
@param [in] header_name Name of the http header to set
@param [in] header_value Value of the http header to set
@@ -28,6 +28,7 @@
%macro mfs_httpheader(header_name
,header_value
)/*/STORE SOURCE*/;
%global sasjs_stpsrv_header_loc;
%local fref fid i;
%if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc)) ne 0 %then %do;

122
server/ms_adduser2group.sas Normal file
View File

@@ -0,0 +1,122 @@
/**
@file
@brief Adds a user to a group on SASjs Server
@details Adds a user to a group based on userid and groupid. Both user and
group must already exist.
Examples:
%ms_adduser2group(uid=1,gid=1)
@param [in] uid= (0) The User ID to be added
@param [in] gid= (0) The Group ID to contain the new user
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
@param [out] outds= (work.ms_adduser2group) This output dataset will contain
the new list of group members, eg:
|DISPLAYNAME:$18.|USERNAME:$10.|ID:best.|
|---|---|---|
|`Super Admin `|`secretuser `|`1`|
|`Sabir Hassan`|`sabir`|`2`|
|`Mihajlo Medjedovic `|`mihajlo `|`3`|
|`Ivor Townsend `|`ivor `|`4`|
|`New User `|`newuser `|`5`|
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mf_getuniquelibref.sas
@li mp_abort.sas
<h4> Related Files </h4>
@li ms_creategroup.sas
@li ms_createuser.sas
**/
%macro ms_adduser2group(uid=0
,gid=0
,outds=work.ms_adduser2group
,mdebug=0
);
%mp_abort(
iftrue=(&syscc ne 0)
,mac=ms_adduser2group.sas
,msg=%str(syscc=&syscc on macro entry)
)
%local fref0 fref1 fref2 libref optval rc msg;
%let fref0=%mf_getuniquefileref();
%let fref1=%mf_getuniquefileref();
%let libref=%mf_getuniquelibref();
/* avoid sending bom marker to API */
%let optval=%sysfunc(getoption(bomfile));
options nobomfile;
data _null_;
file &fref0 lrecl=1000;
infile "&_sasjs_tokenfile" lrecl=1000;
input;
if _n_=1 then put "accept: application/json";
put _infile_;
run;
%if &mdebug=1 %then %do;
%put _local_;
data _null_;
infile &fref0;
input;
put _infile_;
run;
%end;
proc http method='POST' headerin=&fref0 out=&fref1
url="&_sasjs_apiserverurl/SASjsApi/group/&gid/&uid";
%if &mdebug=1 %then %do;
debug level=1;
%end;
run;
%mp_abort(
iftrue=(&syscc ne 0)
,mac=ms_adduser2group.sas
,msg=%str(Issue submitting query to SASjsApi/group)
)
libname &libref JSON fileref=&fref1;
data &outds;
set &libref..users;
drop ordinal_root ordinal_users;
%if &mdebug=1 %then %do;
putlog _all_;
%end;
run;
%mp_abort(
iftrue=(&syscc ne 0)
,mac=ms_creategroup.sas
,msg=%str(Issue reading response JSON)
)
/* reset options */
options &optval;
%if &mdebug=0 %then %do;
filename &fref0 clear;
filename &fref1 clear;
libname &libref clear;
%end;
%else %do;
data _null_;
infile &fref1;
input;
putlog _infile_;
run;
%end;
%mend ms_adduser2group;

View File

@@ -21,6 +21,7 @@
@li mf_getuniquefileref.sas
@li mf_getuniquename.sas
@li mp_abort.sas
@li ms_deletefile.sas
**/
@@ -29,15 +30,22 @@
,mdebug=0
);
%local fname0 fname1 fname2 boundary fname statcd msg;
/* first, delete in case it exists */
%ms_deletefile(&driveloc,mdebug=&mdebug)
%local fname0 fname1 fname2 boundary fname statcd msg optval;
%let fname0=%mf_getuniquefileref();
%let fname1=%mf_getuniquefileref();
%let fname2=%mf_getuniquefileref();
%let boundary=%mf_getuniquename();
/* avoid sending bom marker to API */
%let optval=%sysfunc(getoption(bomfile));
options nobomfile;
data _null_;
file &fname0 termstr=crlf;
infile &inref end=eof;
file &fname0 termstr=crlf lrecl=32767;
infile &inref end=eof lrecl=32767;
if _n_ = 1 then do;
put "--&boundary.";
put 'Content-Disposition: form-data; name="filePath"';
@@ -57,17 +65,20 @@ data _null_;
run;
data _null_;
file &fname1;
put "Content-Type: multipart/form-data; boundary=&boundary";
file &fname1 lrecl=1000;
infile "&_sasjs_tokenfile" lrecl=1000;
input;
if _n_=1 then put "Content-Type: multipart/form-data; boundary=&boundary";
put _infile_;
run;
%if &mdebug=1 %then %do;
data _null_;
infile &fname0;
infile &fname0 lrecl=32767;
input;
put _infile_;
data _null_;
infile &fname1;
infile &fname1 lrecl=32767;
input;
put _infile_;
run;
@@ -95,4 +106,7 @@ run;
,msg=%superq(msg)
)
/* reset options */
options &optval;
%mend ms_createfile;

149
server/ms_creategroup.sas Normal file
View File

@@ -0,0 +1,149 @@
/**
@file
@brief Creates a group on SASjs Server
@details Creates a group on SASjs Server with the following attributes:
@li name
@li description
@li isActive
Examples:
%ms_creategroup(mynewgroup)
%ms_creategroup(mynewergroup, desc=The group description)
@param [in] groupname The group name to create. No spaces or special chars.
@param [in] desc= (0) If no description provided, group name will be used.
@param [in] isactive= (true) Set to false to create an inactive group.
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
@param [out] outds= (work.ms_creategroup) This output dataset will contain the
values from the JSON response (such as the id of the new group)
|DESCRIPTION:$1.|GROUPID:best.|ISACTIVE:best.|NAME:$11.|
|---|---|---|---|
|`The group description`|`2 `|`1 `|`mynewergroup `|
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mf_getuniquelibref.sas
@li mp_abort.sas
<h4> Related Files </h4>
@li ms_creategroup.test.sas
@li ms_getgroups.sas
**/
%macro ms_creategroup(groupname
,desc=0
,isactive=true
,outds=work.ms_creategroup
,mdebug=0
);
%mp_abort(
iftrue=(&syscc ne 0)
,mac=ms_creategroup.sas
,msg=%str(syscc=&syscc on macro entry)
)
%local fref0 fref1 fref2 libref optval rc msg;
%let fref0=%mf_getuniquefileref();
%let fref1=%mf_getuniquefileref();
%let fref2=%mf_getuniquefileref();
%let libref=%mf_getuniquelibref();
/* avoid sending bom marker to API */
%let optval=%sysfunc(getoption(bomfile));
options nobomfile;
data _null_;
file &fref0 termstr=crlf;
name=quote(cats(symget('groupname')));
description=quote(cats(symget('desc')));
if cats(description)='"0"' then description=name;
isactive=symget('isactive');
%if &mdebug=1 %then %do;
putlog _all_;
%end;
put '{'@;
put '"name":' name @;
put ',"description":' description @;
put ',"isActive":' isactive @;
put '}';
run;
data _null_;
file &fref1 lrecl=1000;
infile "&_sasjs_tokenfile" lrecl=1000;
input;
if _n_=1 then do;
put "Content-Type: application/json";
put "accept: application/json";
end;
put _infile_;
run;
%if &mdebug=1 %then %do;
data _null_;
infile &fref0;
input;
put _infile_;
data _null_;
infile &fref1;
input;
put _infile_;
run;
%end;
proc http method='POST' in=&fref0 headerin=&fref1 out=&fref2
url="&_sasjs_apiserverurl/SASjsApi/group";
%if &mdebug=1 %then %do;
debug level=1;
%end;
run;
%mp_abort(
iftrue=(&syscc ne 0)
,mac=ms_creategroup.sas
,msg=%str(Issue submitting query to SASjsApi/group)
)
libname &libref JSON fileref=&fref2;
data &outds;
set &libref..root;
drop ordinal_root;
%if &mdebug=1 %then %do;
putlog _all_;
%end;
run;
%mp_abort(
iftrue=(&syscc ne 0)
,mac=ms_creategroup.sas
,msg=%str(Issue reading response JSON)
)
/* reset options */
options &optval;
%if &mdebug=0 %then %do;
filename &fref0 clear;
filename &fref1 clear;
filename &fref2 clear;
libname &libref clear;
%end;
%else %do;
data _null_;
infile &fref2;
input;
putlog _infile_;
run;
%end;
%mend ms_creategroup;

146
server/ms_createuser.sas Normal file
View File

@@ -0,0 +1,146 @@
/**
@file
@brief Creates a user on SASjs Server
@details Creates a user on SASjs Server with the following attributes:
@li UserName
@li Password
@li isAdmin
@li displayName
The userid is created by sasjs/server. All users are created with `isActive`
set to `true`.
Example:
%ms_createuser(newuser,secretpass,displayname=New User!)
@param [in] username The username to apply. No spaces or special characters.
@param [in] password The initial password to set.
@param [in] isadmin= (false) Set to true to give the user admin rights
@param [in] displayName= (0) Set a friendly name (spaces & special characters
are ok). If not provided, username will be used instead.
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
@param [out] outds= (work.ms_createuser) This output dataset will contain the
values from the JSON response (such as the id of the new user)
|ID:best.|DISPLAYNAME:$8.|USERNAME:$8.|ISACTIVE:best.|ISADMIN:best.|
|---|---|---|---|---|
|`6 `|`New User `|`newuser `|`1 `|`0 `|
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mf_getuniquelibref.sas
@li mp_abort.sas
<h4> Related Files </h4>
@li ms_createuser.test.sas
@li ms_getusers.sas
**/
%macro ms_createuser(username,password
,isadmin=false
,displayname=0
,outds=work.ms_createuser
,mdebug=0
);
%mp_abort(
iftrue=(&syscc ne 0)
,mac=ms_createuser.sas
,msg=%str(syscc=&syscc on macro entry)
)
%local fref0 fref1 fref2 libref optval rc msg;
%let fref0=%mf_getuniquefileref();
%let fref1=%mf_getuniquefileref();
%let fref2=%mf_getuniquefileref();
%let libref=%mf_getuniquelibref();
/* avoid sending bom marker to API */
%let optval=%sysfunc(getoption(bomfile));
options nobomfile;
data _null_;
file &fref0 termstr=crlf;
username=quote(cats(symget('username')));
password=quote(cats(symget('password')));
isadmin=symget('isadmin');
displayname=quote(cats(symget('displayname')));
if displayname='"0"' then displayname=username;
%if &mdebug=1 %then %do;
putlog _all_;
%end;
put '{'@;
put '"displayName":' displayname @;
put ',"username":' username @;
put ',"password":' password @;
put ',"isAdmin":' isadmin @;
put ',"isActive": true }';
run;
data _null_;
file &fref1 lrecl=1000;
infile "&_sasjs_tokenfile" lrecl=1000;
input;
if _n_=1 then do;
put "Content-Type: application/json";
put "accept: application/json";
end;
put _infile_;
run;
%if &mdebug=1 %then %do;
data _null_;
infile &fref0;
input;
put _infile_;
data _null_;
infile &fref1;
input;
put _infile_;
run;
%end;
proc http method='POST' in=&fref0 headerin=&fref1 out=&fref2
url="&_sasjs_apiserverurl/SASjsApi/user";
%if &mdebug=1 %then %do;
debug level=1;
%end;
run;
%mp_abort(
iftrue=(&syscc ne 0)
,mac=ms_createuser.sas
,msg=%str(Issue submitting query to SASjsApi/user)
)
libname &libref JSON fileref=&fref2;
data &outds;
set &libref..root;
drop ordinal_root;
run;
%mp_abort(
iftrue=(&syscc ne 0)
,mac=ms_createuser.sas
,msg=%str(Issue reading response JSON)
)
/* reset options */
options &optval;
%if &mdebug=1 %then %do;
filename &fref0 clear;
filename &fref1 clear;
filename &fref2 clear;
libname &libref clear;
%end;
%mend ms_createuser;

View File

@@ -0,0 +1,573 @@
/**
@file ms_createwebservice.sas
@brief Create a Web-Ready Stored Program
@details This macro creates a Stored Program along with the necessary precode
to enable the %webout() macro
Usage:
<code>
%* compile macros ;
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc;
%* parmcards lets us write to a text file from open code ;
filename ft15f001 temp;
parmcards4;
%webout(FETCH)
%* do some sas, any inputs are now already WORK tables;
data example1 example2;
set sashelp.class;
run;
%* send data back;
%webout(OPEN)
%webout(ARR,example1) * Array format, fast, suitable for large tables ;
%webout(OBJ,example2) * Object format, easier to work with ;
%webout(CLOSE)
;;;;
%ms_createwebservice(path=/Public/app/common,name=appInit,code=ft15f001)
</code>
For more examples of using these web services with the SASjs Adapter, see:
https://github.com/sasjs/adapter#readme
@param [in] path= (0) The full SASjs Drive path in which to create the service
@param [in] name= Stored Program name
@param [in] desc= The description of the service (not implemented yet)
@param [in] precode= Space separated list of filerefs, pointing to the code
that needs to be attached to the beginning of the service (optional)
@param [in] code= (ft15f001) Space seperated fileref(s) of the actual code to
be added
@param [in] mDebug= (0) set to 1 to show debug messages in the log
<h4> SAS Macros </h4>
@li ms_createfile.sas
@li mf_getuser.sas
@li mf_getuniquename.sas
@li mf_getuniquefileref.sas
@li mp_abort.sas
<h4> Related Files </h4>
@li ms_createwebservice.test.sas
@version 9.2
@author Allan Bowe
**/
%macro ms_createwebservice(path=0
,name=initService
,precode=
,code=ft15f001
,desc=Not currently used
,mDebug=0
)/*/STORE SOURCE*/;
%local dbg;
%if &mdebug=1 %then %do;
%put &sysmacroname entry vars:;
%put _local_;
%end;
%else %let dbg=*;
%mp_abort(iftrue=(&syscc ge 4 )
,mac=ms_createwebservice
,msg=%str(syscc=&syscc on macro entry)
)
%mp_abort(iftrue=("&path"="0")
,mac=ms_createwebservice
,msg=%str(Path not provided)
)
/* remove any trailing slash */
%if "%substr(&path,%length(&path),1)" = "/" %then
%let path=%substr(&path,1,%length(&path)-1);
/**
* Add webout macro
* These put statements are auto generated - to change the macro, change the
* source (ms_webout) and run `build.py`
*/
%local sasjsref;
%let sasjsref=%mf_getuniquefileref();
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=N ';
put ' ,maxobs=MAX ';
put ')/*/STORE SOURCE*/; ';
put '%local tempds colinfo fmtds i numcols stmt_obs; ';
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; ';
put ' data _null_;file &jref encoding=''utf-8'' lrecl=200; ';
put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
put ' run; ';
put '%end; ';
put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
put ' /* force variable names to always be uppercase in the JSON */ ';
put ' options validvarname=upcase; ';
put ' /* To avoid issues with _webout on EBI - such as encoding diffs and truncation ';
put ' (https://support.sas.com/kb/49/325.html) we use temporary files */ ';
put ' filename _sjs1 temp lrecl=200 ; ';
put ' data _null_; file _sjs1 encoding=''utf-8''; ';
put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; ';
put ' run; ';
put ' /* now write to _webout 1 char at a time */ ';
put ' data _null_; ';
put ' infile _sjs1 lrecl=1 recfm=n; ';
put ' file &jref mod lrecl=1 recfm=n; ';
put ' input sourcechar $char1. @@; ';
put ' format sourcechar hex2.; ';
put ' put sourcechar char1. @@; ';
put ' run; ';
put ' filename _sjs1 clear; ';
put ' ';
put ' /* grab col defs */ ';
put ' proc contents noprint data=&ds ';
put ' out=_data_(keep=name type length format formatl formatd varnum label); ';
put ' run; ';
put ' %let colinfo=%scan(&syslast,2,.); ';
put ' proc sort data=&colinfo; ';
put ' by varnum; ';
put ' run; ';
put ' /* move meta to mac vars */ ';
put ' data _null_; ';
put ' if _n_=1 then call symputx(''numcols'',nobs,''l''); ';
put ' set &colinfo end=last nobs=nobs; ';
put ' name=upcase(name); ';
put ' /* fix formats */ ';
put ' if type=2 or type=6 then do; ';
put ' typelong=''char''; ';
put ' length fmt $49.; ';
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''; ';
put ' if format='''' then fmt=''best.''; ';
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 ' run; ';
put ' ';
put ' %let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
put ' ';
put ' %if &engine=PROCJSON %then %do; ';
put ' %if &missing=STRING %then %do; ';
put ' %put &sysmacroname: Special Missings not supported in proc json.; ';
put ' %put &sysmacroname: Switching to DATASTEP engine; ';
put ' %goto datastep; ';
put ' %end; ';
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''; ';
put ' proc json out=_sjs2 pretty ';
put ' %if &action=ARR %then nokeys ; ';
put ' ;export &tempds / nosastags fmtnumeric; ';
put ' run; ';
put ' /* send back to webout */ ';
put ' data _null_; ';
put ' infile _sjs2 lrecl=1 recfm=n; ';
put ' file &jref mod lrecl=1 recfm=n; ';
put ' input sourcechar $char1. @@; ';
put ' format sourcechar hex2.; ';
put ' put sourcechar char1. @@; ';
put ' run; ';
put ' filename _sjs2 clear; ';
put ' %end; ';
put ' %else %if &engine=DATASTEP %then %do; ';
put ' %datastep: ';
put ' %if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1 ';
put ' %then %do; ';
put ' %put &sysmacroname: &ds NOT FOUND!!!; ';
put ' %return; ';
put ' %end; ';
put ' ';
put ' %if &fmt=Y %then %do; ';
put ' data _data_; ';
put ' /* rename on entry */ ';
put ' set &ds(rename=( ';
put ' %do i=1 %to &numcols; ';
put ' &&name&i=&&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 ' %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 ' run; ';
put ' %let fmtds=&syslast; ';
put ' %end; ';
put ' ';
put ' proc format; /* credit yabwon for special null removal */ ';
put ' value bart (default=40) ';
put ' %if &missing=NULL %then %do; ';
put ' ._ - .z = null ';
put ' %end; ';
put ' %else %do; ';
put ' ._ = [quote()] ';
put ' . = null ';
put ' .a - .z = [quote()] ';
put ' %end; ';
put ' other = [best.]; ';
put ' ';
put ' data &tempds; ';
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 ' %end; ';
put ' %end; ';
put ' %if &fmt=Y %then %do; ';
put ' set &fmtds; ';
put ' %end; ';
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 ' &&name&i=''"''!!trim( ';
put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ ';
put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ ';
put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ ';
put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ ';
put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ ';
put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ ';
put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
put ' prxchange(''s/\\/\\\\/'',-1,&&name&i) ';
put ' ))))))))))))!!''"''; ';
put ' end; ';
put ' else &&name&i=quote(cats(&&name&i)); ';
put ' %end; ';
put ' %end; ';
put ' run; ';
put ' ';
put ' filename _sjs3 temp lrecl=131068 ; ';
put ' data _null_; ';
put ' file _sjs3 encoding=''utf-8''; ';
put ' if _n_=1 then put "["; ';
put ' set &tempds; ';
put ' if _n_>1 then put "," @; put ';
put ' %if &action=ARR %then "[" ; %else "{" ; ';
put ' %do i=1 %to &numcols; ';
put ' %if &i>1 %then "," ; ';
put ' %if &action=OBJ %then """&&name&i"":" ; ';
put ' "&&name&i"n /* name literal for reserved variable names */ ';
put ' %end; ';
put ' %if &action=ARR %then "]" ; %else "}" ; ; ';
put ' ';
put ' /* close out the table */ ';
put ' data _null_; ';
put ' file _sjs3 mod encoding=''utf-8''; ';
put ' put '']''; ';
put ' run; ';
put ' data _null_; ';
put ' infile _sjs3 lrecl=1 recfm=n; ';
put ' file &jref mod lrecl=1 recfm=n; ';
put ' input sourcechar $char1. @@; ';
put ' format sourcechar hex2.; ';
put ' put sourcechar char1. @@; ';
put ' run; ';
put ' filename _sjs3 clear; ';
put ' %end; ';
put ' ';
put ' proc sql; ';
put ' drop table &colinfo, &tempds; ';
put ' ';
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 ' put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{"; ';
put ' do i=1 to &numcols; ';
put ' name=quote(trim(symget(cats(''name'',i)))); ';
put ' format=quote(trim(symget(cats(''fmt'',i)))); ';
put ' label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i))))); ';
put ' length=quote(trim(symget(cats(''length'',i)))); ';
put ' type=quote(trim(symget(cats(''typelong'',i)))); ';
put ' if i>1 then put "," @@; ';
put ' put name '':{"format":'' format '',"label":'' label ';
put ' '',"length":'' length '',"type":'' type ''}''; ';
put ' end; ';
put ' put ''}}''; ';
put ' run; ';
put ' /* send back to webout */ ';
put ' data _null_; ';
put ' infile _sjs4 lrecl=1 recfm=n; ';
put ' file &jref mod lrecl=1 recfm=n; ';
put ' input sourcechar $char1. @@; ';
put ' format sourcechar hex2.; ';
put ' put sourcechar char1. @@; ';
put ' run; ';
put ' filename _sjs4 clear; ';
put ' %end; ';
put '%end; ';
put ' ';
put '%else %if &action=CLOSE %then %do; ';
put ' data _null_; file &jref encoding=''utf-8'' mod ; ';
put ' put "}"; ';
put ' run; ';
put '%end; ';
put '%mend mp_jsonout; ';
put ' ';
put '%macro mf_getuser( ';
put ')/*/STORE SOURCE*/; ';
put ' %local user; ';
put ' ';
put ' %if %symexist(_sasjs_username) %then %let user=&_sasjs_username; ';
put ' %else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do; ';
put ' %let user=&SYS_COMPUTE_SESSION_OWNER; ';
put ' %end; ';
put ' %else %if %symexist(_metaperson) %then %do; ';
put ' %if %length(&_metaperson)=0 %then %let user=&sysuserid; ';
put ' /* sometimes SAS will add @domain extension - remove for consistency */ ';
put ' /* but be sure to quote in case of usernames with commas */ ';
put ' %else %let user=%unquote(%scan(%quote(&_metaperson),1,@)); ';
put ' %end; ';
put ' %else %let user=&sysuserid; ';
put ' ';
put ' %quote(&user) ';
put ' ';
put '%mend mf_getuser; ';
put ' ';
put '%macro ms_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL ';
put ' ,showmeta=N ';
put '); ';
put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug ';
put ' sasjs_tables; ';
put ' ';
put '%local i tempds; ';
put '%let action=%upcase(&action); ';
put ' ';
put '%if &action=FETCH %then %do; ';
put ' %if %str(&_debug) ge 131 %then %do; ';
put ' options mprint notes mprintnest; ';
put ' %end; ';
put ' %let _webin_file_count=%eval(&_webin_file_count+0); ';
put ' /* now read in the data */ ';
put ' %do i=1 %to &_webin_file_count; ';
put ' %if &_webin_file_count=1 %then %do; ';
put ' %let _webin_fileref1=&_webin_fileref; ';
put ' %let _webin_name1=&_webin_name; ';
put ' %end; ';
put ' data _null_; ';
put ' infile &&_webin_fileref&i termstr=crlf lrecl=32767; ';
put ' input; ';
put ' call symputx(''input_statement'',_infile_); ';
put ' putlog "&&_webin_name&i input statement: " _infile_; ';
put ' stop; ';
put ' data &&_webin_name&i; ';
put ' infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding=''utf-8'' ';
put ' lrecl=32767; ';
put ' input &input_statement; ';
put ' %if %str(&_debug) ge 131 %then %do; ';
put ' if _n_<20 then putlog _infile_; ';
put ' %end; ';
put ' run; ';
put ' %let sasjs_tables=&sasjs_tables &&_webin_name&i; ';
put ' %end; ';
put '%end; ';
put ' ';
put '%else %if &action=OPEN %then %do; ';
put ' /* fix encoding and ensure enough lrecl */ ';
put ' OPTIONS NOBOMFILE lrecl=32767; ';
put ' ';
put ' /* set the header */ ';
put ' %mfs_httpheader(Content-type,application/json) ';
put ' ';
put ' /* setup json. */ ';
put ' data _null_;file &fref encoding=''utf-8'' termstr=lf ; ';
put ' put ''{"SYSDATE" : "'' "&SYSDATE" ''"''; ';
put ' put '',"SYSTIME" : "'' "&SYSTIME" ''"''; ';
put ' run; ';
put ' ';
put '%end; ';
put ' ';
put '%else %if &action=ARR or &action=OBJ %then %do; ';
put ' %if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then %do; ';
put ' /* functions in formats unsupported */ ';
put ' %put &sysmacroname: forcing missing back to NULL as feature not supported; ';
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 ' ) ';
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 ' data;run;%let tempds=%scan(&syslast,2,.); ';
put ' ods output Members=&tempds; ';
put ' proc datasets library=WORK memtype=data; ';
put ' %local wtcnt;%let wtcnt=0; ';
put ' data _null_; ';
put ' set &tempds; ';
put ' if not (upcase(name) =:"DATA"); /* ignore temp datasets */ ';
put ' if not (upcase(name)=:"_DATA_"); ';
put ' i+1; ';
put ' call symputx(cats(''wt'',i),name,''l''); ';
put ' call symputx(''wtcnt'',i,''l''); ';
put ' data _null_; file &fref mod encoding=''utf-8'' termstr=lf; ';
put ' put ",""WORK"":{"; ';
put ' %do i=1 %to &wtcnt; ';
put ' %let wt=&&wt&i; ';
put ' data _null_; file &fref mod encoding=''utf-8'' termstr=lf; ';
put ' dsid=open("WORK.&wt",''is''); ';
put ' nlobs=attrn(dsid,''NLOBS''); ';
put ' nvars=attrn(dsid,''NVARS''); ';
put ' rc=close(dsid); ';
put ' if &i>1 then put '',''@; ';
put ' put " ""&wt"" : {"; ';
put ' put ''"nlobs":'' nlobs; ';
put ' put '',"nvars":'' nvars; ';
put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=Y,maxobs=10) ';
put ' data _null_; file &fref mod encoding=''utf-8'' termstr=lf; ';
put ' put "}"; ';
put ' %end; ';
put ' data _null_; file &fref mod encoding=''utf-8'' termstr=lf; ';
put ' put "}"; ';
put ' run; ';
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 ' put ",""_DEBUG"" : ""&_debug"" "; ';
put ' put '',"_PROGRAM" : '' _PROGRAM ; ';
put ' put ",""SYSCC"" : ""&syscc"" "; ';
put ' put ",""SYSENCODING"" : ""&sysencoding"" "; ';
put ' syserrortext=quote(cats(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 ' sysvlong=quote(trim(symget(''sysvlong''))); ';
put ' put '',"SYSVLONG" : '' sysvlong; ';
put ' syswarningtext=quote(cats(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)); ';
put ' put '',"MEMSIZE" : '' memsize; ';
put ' put "}" @; ';
put ' run; ';
put '%end; ';
put ' ';
put '%mend ms_webout; ';
put ' ';
put '%macro mfs_httpheader(header_name ';
put ' ,header_value ';
put ')/*/STORE SOURCE*/; ';
put '%global sasjs_stpsrv_header_loc; ';
put '%local fref fid i; ';
put ' ';
put '%if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc)) ne 0 %then %do; ';
put ' %put &=fref &=sasjs_stpsrv_header_loc; ';
put ' %put %str(ERR)OR: %sysfunc(sysmsg()); ';
put ' %return; ';
put '%end; ';
put ' ';
put '%let fid=%sysfunc(fopen(&fref,A)); ';
put ' ';
put '%if &fid=0 %then %do; ';
put ' %put %str(ERR)OR: %sysfunc(sysmsg()); ';
put ' %return; ';
put '%end; ';
put ' ';
put '%let rc=%sysfunc(fput(&fid,%str(&header_name): %str(&header_value))); ';
put '%let rc=%sysfunc(fwrite(&fid)); ';
put ' ';
put '%let rc=%sysfunc(fclose(&fid)); ';
put '%let rc=%sysfunc(filename(&fref)); ';
put ' ';
put '%mend mfs_httpheader; ';
/* WEBOUT END */
put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO);';
put ' %ms_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt,missing=&missing';
put ' ,showmeta=&showmeta';
put ' )';
put '%mend;';
run;
/* add precode and code */
%local x fref freflist;
%let freflist=&precode &code ;
%do x=1 %to %sysfunc(countw(&freflist));
%let fref=%scan(&freflist,&x);
%put &sysmacroname: adding &fref;
data _null_;
file &sasjsref lrecl=3000 termstr=crlf mod;
infile &fref lrecl=3000;
input;
put _infile_;
run;
%end;
/* create the web service */
%ms_createfile(&path/&name..sas, inref=&sasjsref, mdebug=&mdebug)
%put ;%put ;%put ;%put ;%put ;%put ;
%put &sysmacroname: STP &name successfully created in &path;
%put ;%put ;%put ;
%put Check it out here:;
%put ;%put ;%put ;
%put &_sasjs_apiserverurl.&_sasjs_apipath?_PROGRAM=&path/&name;
%put ;%put ;%put ;%put ;%put ;%put ;
%mend ms_createwebservice;

View File

@@ -17,6 +17,8 @@
@param [in] driveloc The full path to the file in SASjs Drive
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
**/
@@ -24,12 +26,23 @@
,mdebug=0
);
proc http method='DELETE'
%local headref;
%let headref=%mf_getuniquefileref();
data _null_;
file &headref lrecl=1000;
infile "&_sasjs_tokenfile" lrecl=1000;
input;
put _infile_;
run;
proc http method='DELETE' headerin=&headref
url="&_sasjs_apiserverurl/SASjsApi/drive/file?_filePath=&driveloc";
%if &mdebug=1 %then %do;
debug level=2;
%end;
run;
filename &headref clear;
%mend ms_deletefile;

View File

@@ -5,12 +5,15 @@
Example:
%ms_getfile(/some/stored/file.ext, outref=myfile)
%ms_getfile(/Public/app/dc/services/public/settings.sas, outref=myfile)
@param [in] driveloc The full path to the file in SASjs Drive
@param [out] outref= (msgetfil) The fileref to contain the file.
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mf_getuniquename.sas
**/
@@ -19,14 +22,29 @@
,mdebug=0
);
filename &outref temp;
/* use the recfm in a separate fileref to avoid issues with subsequent reads */
%local binaryfref floc headref;
%let binaryfref=%mf_getuniquefileref();
%let headref=%mf_getuniquefileref();
%let floc=%sysfunc(pathname(work))/%mf_getuniquename().txt;
filename &outref "&floc" lrecl=32767;
filename &binaryfref "&floc" recfm=n;
proc http method='GET' out=&outref
data _null_;
file &headref lrecl=1000;
infile "&_sasjs_tokenfile" lrecl=1000;
input;
put _infile_;
run;
proc http method='GET' out=&binaryfref headerin=&headref
url="&_sasjs_apiserverurl/SASjsApi/drive/file?_filePath=&driveloc";
%if &mdebug=1 %then %do;
debug level=2;
%end;
run;
filename &binaryfref clear;
filename &headref clear;
%mend ms_getfile;
%mend ms_getfile;

147
server/ms_getgroups.sas Normal file
View File

@@ -0,0 +1,147 @@
/**
@file
@brief Fetches the list of groups from SASjs Server
@details Fetches the list of groups from SASjs Server and writes them to an
output dataset. Provide a username to filter for the groups for a particular
user.
Example:
%ms_getgroups(outds=userlist)
With filter on username:
%ms_getgroups(outds=userlist, user=James)
With filter on userid:
%ms_getgroups(outds=userlist, uid=1)
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
@param [in] user= (0) Provide the username on which to filter
@param [in] uid= (0) Provide the userid on which to filter
@param [out] outds= (work.ms_getgroups) This output dataset will contain the
list of groups. Format:
|NAME:$32.|DESCRIPTION:$64.|GROUPID:best.|
|---|---|---|
|`SomeGroup `|`A group `|`1`|
|`Another Group`|`this is a different group`|`2`|
|`admin`|`Administrators `|`3`|
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mf_getuniquelibref.sas
@li mp_abort.sas
<h4> Related Files </h4>
@li ms_creategroup.sas
@li ms_getgroups.test.sas
**/
%macro ms_getgroups(
user=0,
uid=0,
outds=work.ms_getgroups,
mdebug=0
);
%mp_abort(
iftrue=(&syscc ne 0)
,mac=ms_getgroups.sas
,msg=%str(syscc=&syscc on macro entry)
)
%local fref0 fref1 libref optval rc msg url;
%if %sysget(MODE)=desktop %then %do;
/* groups api does not exist in desktop mode */
data &outds;
length NAME $32 DESCRIPTION $64. GROUPID 8;
name="&sysuserid";
description="&sysuserid (group - desktop mode)";
groupid=1;
output;
stop;
run;
%return;
%end;
%let fref0=%mf_getuniquefileref();
%let fref1=%mf_getuniquefileref();
%let libref=%mf_getuniquelibref();
/* avoid sending bom marker to API */
%let optval=%sysfunc(getoption(bomfile));
options nobomfile;
data _null_;
file &fref0 lrecl=1000;
infile "&_sasjs_tokenfile" lrecl=1000;
input;
if _n_=1 then put "accept: application/json";
put _infile_;
run;
%if &mdebug=1 %then %do;
data _null_;
infile &fref0;
input;
put _infile_;
run;
%end;
%if "&user" ne "0" %then %let url=/SASjsApi/user/by/username/&user;
%else %if "&uid" ne "0" %then %let url=/SASjsApi/user/&uid;
%else %let url=/SASjsApi/group;
proc http method='GET' headerin=&fref0 out=&fref1
url="&_sasjs_apiserverurl.&url";
%if &mdebug=1 %then %do;
debug level=1;
%end;
run;
%mp_abort(
iftrue=(&syscc ne 0)
,mac=ms_getgroups.sas
,msg=%str(Issue submitting GET query to SASjsApi)
)
libname &libref JSON fileref=&fref1;
%if "&user"="0" and "&uid"="0" %then %do;
data &outds;
length NAME $32 DESCRIPTION $64. GROUPID 8;
if _n_=1 then call missing(of _all_);
set &libref..root;
drop ordinal_root;
run;
%end;
%else %do;
data &outds;
length NAME $32 DESCRIPTION $64. GROUPID 8;
if _n_=1 then call missing(of _all_);
set &libref..groups;
drop ordinal_:;
run;
%end;
%mp_abort(
iftrue=(&syscc ne 0)
,mac=ms_getgroups.sas
,msg=%str(Issue reading response JSON)
)
/* reset options */
options &optval;
%if &mdebug=1 %then %do;
filename &fref0 clear;
filename &fref1 clear;
libname &libref clear;
%end;
%mend ms_getgroups;

148
server/ms_getusers.sas Normal file
View File

@@ -0,0 +1,148 @@
/**
@file
@brief Fetches the list of users from SASjs Server
@details Fetches the list of users from SASjs Server and writes them to an
output dataset. Can also be filtered, for a particular group.
Example:
%ms_getusers(outds=userlist)
Filtering for a group by group name:
%ms_getusers(outds=work.groupmembers, group=GROUPNAME)
Filtering for a group by group id:
%ms_getusers(outds=work.groupmembers, gid=1)
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
@param [in] group= (0) Set to a group name to filter members for that group
@param [in] gid= (0) Set to a group id to filter members for that group
@param [out] outds= (work.ms_getusers) This output dataset will contain the
list of user accounts. Format:
|DISPLAYNAME:$60.|USERNAME:$30.|ID:best.|
|---|---|---|
|`Super Admin `|`secretuser `|`1`|
|`Sabir Hassan`|`sabir`|`2`|
|`Mihajlo Medjedovic `|`mihajlo `|`3`|
|`Ivor Townsend `|`ivor `|`4`|
|`New User `|`newuser `|`5`|
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mf_getuniquelibref.sas
@li mp_abort.sas
<h4> Related Files </h4>
@li ms_createuser.sas
@li ms_getgroups.sas
@li ms_getusers.test.sas
**/
%macro ms_getusers(
outds=work.ms_getusers,
group=0,
gid=0,
mdebug=0
);
%mp_abort(
iftrue=(&syscc ne 0)
,mac=ms_getusers.sas
,msg=%str(syscc=&syscc on macro entry)
)
%local fref0 fref1 libref optval rc msg url;
%let fref0=%mf_getuniquefileref();
%let fref1=%mf_getuniquefileref();
%let libref=%mf_getuniquelibref();
%if %sysget(MODE)=desktop %then %do;
/* users api does not exist in desktop mode */
data &outds;
length DISPLAYNAME $60 USERNAME:$30 ID 8;
USERNAME="&sysuserid";
DISPLAYNAME="&sysuserid (desktop mode)";
ID=1;
output;
stop;
run;
%return;
%end;
/* avoid sending bom marker to API */
%let optval=%sysfunc(getoption(bomfile));
options nobomfile;
data _null_;
file &fref0 lrecl=1000;
infile "&_sasjs_tokenfile" lrecl=1000;
input;
if _n_=1 then put "accept: application/json";
put _infile_;
run;
%if &mdebug=1 %then %do;
data _null_;
infile &fref0;
input;
put _infile_;
run;
%end;
%if "&group" ne "0" %then %let url=/SASjsApi/group/by/groupname/&group;
%else %if "&gid" ne "0" %then %let url=/SASjsApi/group/&gid;
%else %let url=/SASjsApi/user;
proc http method='GET' headerin=&fref0 out=&fref1
url="&_sasjs_apiserverurl.&url";
%if &mdebug=1 %then %do;
debug level=1;
%end;
run;
%mp_abort(
iftrue=(&syscc ne 0)
,mac=ms_getusers.sas
,msg=%str(Issue submitting API query)
)
libname &libref JSON fileref=&fref1;
%if "&group"="0" and "&gid"="0" %then %do;
data &outds;
length DISPLAYNAME $60 USERNAME:$30 ID 8;
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;
if nobs=0 then call missing(of _all_);
set &libref..users nobs=nobs;
drop ordinal_root ordinal_users;
run;
%end;
%mp_abort(
iftrue=(&syscc ne 0)
,mac=ms_getusers.sas
,msg=%str(Issue reading response JSON)
)
/* reset options */
options &optval;
%if &mdebug=1 %then %do;
filename &fref0 clear;
filename &fref1 clear;
libname &libref clear;
%end;
%mend ms_getusers;

View File

@@ -15,48 +15,155 @@
parameter)
@param [in] debug= (131) The value to supply to the _debug URL parameter
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
@param [in] inputparams=(_null_) A dataset containing name/value pairs in the
following format:
|name:$32|value:$10000|
|---|---|
|stpmacname|some value|
|mustbevalidname|can be anything, oops, %abort!!|
@param [in] inputfiles= (_null_) A dataset containing fileref/name/filename in
the following format:
|fileref:$8|name:$32|filename:$256|
|---|---|--|
|someref|some_name|some_filename.xls|
|fref2|another_file|zyx_v2.csv|
@param [out] outref= (outweb) The output fileref to contain the response JSON
(will be created using temp engine)
@param [out] outlogds= (_null_) Set to the name of a dataset to contain the
log. Table format:
|line:$2000|
|---|
|log line 1|
|log line 2|
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mf_getuniquelibref.sas
@li mp_abort.sas
**/
%macro ms_runstp(pgm
,debug=131
,inputparams=_null_
,inputfiles=_null_
,outref=outweb
,outlogds=_null_
,mdebug=0
);
%local dbg fname1;
%local dbg mainref authref boundary;
%let mainref=%mf_getuniquefileref();
%let authref=%mf_getuniquefileref();
%let boundary=%mf_getuniquename();
%if &inputparams=0 %then %let inputparams=_null_;
%if &mdebug=1 %then %do;
%put &sysmacroname entry vars:;
%put _local_;
%end;
%else %let dbg=*;
%let fname1=%mf_getuniquefileref();
%mp_abort(iftrue=("&pgm"="")
,mac=&sysmacroname
,msg=%str(Program not provided)
)
/* avoid sending bom marker to API */
%local optval;
%let optval=%sysfunc(getoption(bomfile));
options nobomfile;
/* add params */
data _null_;
file &fname1;
infile "&_sasjs_tokenfile";
input;
put 'Authorization: Bearer' _infile_;
file &mainref termstr=crlf lrecl=32767 mod;
length line $1000 name $32 value $32767;
if _n_=1 then call missing(of _all_);
set &inputparams;
put "--&boundary";
line=cats('Content-Disposition: form-data; name="',name,'"');
put line;
put ;
put value;
run;
filename &outref temp;
/* parse input file list */
%local webcount;
%let webcount=0;
data _null_;
set &inputfiles end=last;
length fileref $8 name $32 filename $256;
call symputx(cats('webref',_n_),fileref,'l');
call symputx(cats('webname',_n_),name,'l');
call symputx(cats('webfilename',_n_),filename,'l');
if last then do;
call symputx('webcount',_n_);
call missing(of _all_);
end;
run;
/* write out the input files */
%local i;
%do i=1 %to &webcount;
data _null_;
file &mainref termstr=crlf lrecl=32767 mod;
infile &&webref&i lrecl=32767;
if _n_ = 1 then do;
length line $32767;
line=cats(
'Content-Disposition: form-data; name="'
,"&&webname&i"
,'"; filename="'
,"&&webfilename&i"
,'"'
);
put "--&boundary";
put line;
put "Content-Type: text/plain";
put ;
end;
input;
put _infile_; /* add the actual file to be sent */
run;
%end;
data _null_;
file &mainref termstr=crlf mod;
put "--&boundary--";
run;
data _null_;
file &authref lrecl=1000;
infile "&_sasjs_tokenfile" lrecl=1000;
input;
if _n_=1 then put "Content-Type: multipart/form-data; boundary=&boundary";
put _infile_;
run;
%if &mdebug=1 %then %do;
data _null_;
infile &authref;
input;
put _infile_;
data _null_;
infile &mainref;
input;
put _infile_;
run;
%end;
filename &outref temp lrecl=32767;
/* prepare request*/
proc http method='POST' headerin=&fname1 out=&outref
proc http method='POST' headerin=&authref in=&mainref out=&outref
url="&_sasjs_apiserverurl.&_sasjs_apipath?_program=&pgm%str(&)_debug=131";
%if &mdebug=1 %then %do;
debug level=2;
%end;
run;
%if (&SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201)
or &mdebug=1 %then %do;
or &mdebug=1
%then %do;
data _null_;infile &outref;input;putlog _infile_;run;
%end;
%mp_abort(
@@ -65,6 +172,20 @@ run;
,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
)
/* reset options */
options &optval;
%if &outlogds ne _null_ or &mdebug=1 %then %do;
%local dumplib;
%let dumplib=%mf_getuniquelibref();
libname &dumplib json fileref=&outref;
data &outlogds;
set &dumplib..log;
%if &mdebug=1 %then %do;
putlog line=;
%end;
run;
%end;
%if &mdebug=1 %then %do;
%put &sysmacroname exit vars:;
@@ -72,6 +193,7 @@ run;
%end;
%else %do;
/* clear refs */
filename &fname1 clear;
filename &authref;
filename &mainref;
%end;
%mend ms_runstp;
%mend ms_runstp;

154
server/ms_testservice.sas Normal file
View File

@@ -0,0 +1,154 @@
/**
@file
@brief Will execute a SASjs web service on SASjs Server
@details Prepares the input files and retrieves the resulting datasets from
the response JSON.
@param [in] program The Stored Program endpoint to test
@param [in] inputfiles=(0) A list of space seperated fileref:filename pairs as
follows:
inputfiles=inref:filename inref2:filename2
@param [in] inputdatasets= (0) All datasets in this space seperated list are
converted into SASJS-formatted CSVs (see mp_ds2csv.sas) files and added to
the list of `inputfiles` for ingestion. The dataset will be sent with the
same name (no need for a colon modifier).
@param [in] inputparams=(0) A dataset containing name/value pairs in the
following format:
|name:$32|value:$1000|
|---|---|
|stpmacname|some value|
|mustbevalidname|can be anything, oops, %abort!!|
@param [in] debug= (131) Provide the _debug value to pass to the STP
@param [in] mdebug= (0) Set to 1 to provide macro debugging (this macro)
@param [out] outlib= (0) Output libref to contain the final tables. Set to
0 if the service output is not in JSON format.
@param [out] outref= (0) Output fileref to create, to contain the full _webout
response.
@param [out] outlogds= (_null_) Set to the name of a dataset to contain the
log. Table format:
|line:$2000|
|---|
|log line 1|
|log line 2|
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mf_getuniquename.sas
@li mp_abort.sas
@li mp_binarycopy.sas
@li mp_chop.sas
@li mp_ds2csv.sas
@li ms_runstp.sas
<h4> Related Programs </h4>
@li mp_testservice.test.sas
@version 9.4
@author Allan Bowe
**/
%macro ms_testservice(program,
inputfiles=0,
inputdatasets=0,
inputparams=0,
debug=0,
mdebug=0,
outlib=0,
outref=0,
outlogds=_null_
)/*/STORE SOURCE*/;
%local dbg i var ds1 fref1 chopout1 chopout2;
%if &mdebug=1 %then %do;
%put &sysmacroname entry vars:;
%put _local_;
%end;
%else %let dbg=*;
/* convert inputdatasets to filerefs */
%if "&inputdatasets" ne "0" %then %do;
%if %quote(&inputfiles)=0 %then %let inputfiles=;
%do i=1 %to %sysfunc(countw(&inputdatasets,%str( )));
%let var=%scan(&inputdatasets,&i,%str( ));
%local dsref&i;
%let dsref&i=%mf_getuniquefileref();
%mp_ds2csv(&var,outref=&&dsref&i,headerformat=SASJS)
%let inputfiles=&inputfiles &&dsref&i:%scan(&var,-1,.);
%end;
%end;
/* parse the filerefs - convert to a dataset */
%let ds1=%mf_getuniquename();
data &ds1;
length fileref $8 name $32 filename $256 var $300;
if "&inputfiles" ne "0" then do;
webcount=countw("&inputfiles");
do i=1 to webcount;
var=scan("&inputfiles",i,' ');
fileref=scan(var,1,':');
name=scan(var,2,':');
filename=cats(name,'.csv');
output;
end;
end;
run;
/* execute the STP */
%let fref1=%mf_getuniquefileref();
%ms_runstp(&program
,debug=&debug
,inputparams=&inputparams
,inputfiles=&ds1
,outref=&fref1
,mdebug=&mdebug
,outlogds=&outlogds
)
/* SASjs services have the _webout embedded in wrapper JSON */
/* Files can also be very large - so use a dedicated macro to chop it out */
%local matchstr1 matchstr2 ;
%let matchstr1={"status":"success","_webout":{;
%let matchstr2=},"log":[{;
%let chopout1=%sysfunc(pathname(work))/%mf_getuniquename(prefix=chop1);
%let chopout2=%sysfunc(pathname(work))/%mf_getuniquename(prefix=chop2);
%mp_chop("%sysfunc(pathname(&fref1,F))"
,matchvar=matchstr1
,keep=LAST
,matchpoint=END
,offset=-1
,outfile="&chopout1"
,mdebug=&mdebug
)
%mp_chop("&chopout1"
,matchvar=matchstr2
,keep=FIRST
,matchpoint=START
,offset=1
,outfile="&chopout2"
,mdebug=&mdebug
)
%if &outlib ne 0 %then %do;
libname &outlib json "&chopout2";
%end;
%if &outref ne 0 %then %do;
filename &outref "&chopout2";
%end;
%if &mdebug=0 %then %do;
filename &webref clear;
filename &fref1 clear;
filename &fref2 clear;
%end;
%else %do;
%put &sysmacroname exit vars:;
%put _local_;
%end;
%mend ms_testservice;

View File

@@ -1,6 +1,6 @@
/**
@file
@brief Send data to/from @sasjs/server
@brief Send data to/from sasjs/server
@details This macro should be added to the start of each web service,
**immediately** followed by a call to:
@@ -27,7 +27,7 @@
@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"}}`
@@ -47,7 +47,7 @@
**/
%macro ms_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL
,showmeta=NO
,showmeta=N
);
%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug
sasjs_tables;
@@ -67,13 +67,14 @@
%let _webin_name1=&_webin_name;
%end;
data _null_;
infile &&_webin_fileref&i termstr=crlf;
infile &&_webin_fileref&i termstr=crlf lrecl=32767;
input;
call symputx('input_statement',_infile_);
putlog "&&_webin_name&i input statement: " _infile_;
stop;
data &&_webin_name&i;
infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding='utf-8';
infile &&_webin_fileref&i firstobs=2 dsd termstr=crlf encoding='utf-8'
lrecl=32767;
input &input_statement;
%if %str(&_debug) ge 131 %then %do;
if _n_<20 then putlog _infile_;
@@ -84,14 +85,14 @@
%end;
%else %if &action=OPEN %then %do;
/* fix encoding */
OPTIONS NOBOMFILE;
/* fix encoding and ensure enough lrecl */
OPTIONS NOBOMFILE lrecl=32767;
/* set the header */
%mfs_httpheader(Content-type,application/json)
/* setup json */
data _null_;file &fref encoding='utf-8' termstr=lf;
/* setup json. */
data _null_;file &fref encoding='utf-8' termstr=lf ;
put '{"SYSDATE" : "' "&SYSDATE" '"';
put ',"SYSTIME" : "' "&SYSTIME" '"';
run;
@@ -99,6 +100,11 @@
%end;
%else %if &action=ARR or &action=OBJ %then %do;
%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then %do;
/* functions in formats unsupported */
%put &sysmacroname: forcing missing back to NULL as feature not supported;
%let missing=NULL;
%end;
%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref
,engine=DATASTEP,missing=&missing,showmeta=&showmeta
)
@@ -106,7 +112,6 @@
%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;
data;run;%let tempds=%scan(&syslast,2,.);
ods output Members=&tempds;
proc datasets library=WORK memtype=data;
@@ -114,6 +119,7 @@
data _null_;
set &tempds;
if not (upcase(name) =:"DATA"); /* ignore temp datasets */
if not (upcase(name)=:"_DATA_");
i+1;
call symputx(cats('wt',i),name,'l');
call symputx('wtcnt',i,'l');
@@ -130,23 +136,25 @@
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)
data _null_; file &fref mod encoding='utf-8' termstr=lf;
put "}";
%end;
data _null_; file &fref mod encoding='utf-8' termstr=lf termstr=lf;
data _null_; file &fref mod encoding='utf-8' termstr=lf;
put "}";
run;
%end;
/* close off json */
data _null_;file &fref mod encoding='utf-8' termstr=lf;
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()"" ";
put ",""_DEBUG"" : ""&_debug"" ";
put ',"_PROGRAM" : ' _PROGRAM ;
put ",""SYSCC"" : ""&syscc"" ";
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
put ",""SYSENCODING"" : ""&sysencoding"" ";
syserrortext=quote(cats(symget('SYSERRORTEXT')));
put ',"SYSERRORTEXT" : ' syserrortext;
SYSHOSTINFOLONG=quote(trim(symget('SYSHOSTINFOLONG')));
put ',"SYSHOSTINFOLONG" : ' SYSHOSTINFOLONG;
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
@@ -161,7 +169,8 @@
put ",""SYSTCPIPHOSTNAME"" : ""&SYSTCPIPHOSTNAME"" ";
sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong;
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" ";
syswarningtext=quote(cats(symget('SYSWARNINGTEXT')));
put ',"SYSWARNINGTEXT" : ' syswarningtext;
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
length autoexec $512;
autoexec=quote(urlencode(trim(getoption('autoexec'))));

View File

@@ -0,0 +1,22 @@
/**
@file
@brief Testing mf_fmtdttm macro
<h4> SAS Macros </h4>
@li mf_fmtdttm.sas
@li mp_assert.sas
@li mp_assertscope.sas
**/
%global test1;
%mp_assertscope(SNAPSHOT)
%let test1=%mf_fmtdttm();
%mp_assertscope(COMPARE,ignorelist=test1)
%mp_assert(
iftrue=("&test1"="DATETIME19.3" or "&test1"="E8601DT26.6"),
desc=Basic test,
outds=work.test_results
)

View File

@@ -0,0 +1,46 @@
/**
@file
@brief Testing mf_getvarlist macro
<h4> SAS Macros </h4>
@li mf_getvarcount.sas
@li mp_assertscope.sas
**/
data work.all work.nums(keep=num1 num2) work.chars(keep=char1 char2);
length num1 num2 8 char1 char2 char3 $4;
call missing (of _all_);
output;
run;
%mp_assertscope(SNAPSHOT)
%put scope check:%mf_getvarcount(work.all);
%mp_assertscope(COMPARE)
%mp_assert(
iftrue=(%mf_getvarcount(work.all)=5),
desc=%str(Checking for mixed vars),
outds=work.test_results
)
%mp_assert(
iftrue=(%mf_getvarcount(work.all,typefilter=C)=3),
desc=%str(Checking for char in mixed vars),
outds=work.test_results
)
%mp_assert(
iftrue=(%mf_getvarcount(work.all,typefilter=N)=2),
desc=%str(Checking for num in mixed vars),
outds=work.test_results
)
%mp_assert(
iftrue=(%mf_getvarcount(work.nums,typefilter=c)=0),
desc=%str(Checking for char in num vars),
outds=work.test_results
)
%mp_assert(
iftrue=(%mf_getvarcount(work.chars,typefilter=N)=0),
desc=%str(Checking for num in char vars),
outds=work.test_results
)

View File

@@ -30,4 +30,12 @@
),
desc=Checking mf_isint(-1),
outds=work.test_results
)
%mp_assert(
iftrue=(
"%mf_isint()"="0"
),
desc=Checking mf_isint(),
outds=work.test_results
)

Some files were not shown because too many files have changed in this diff Show More