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

Compare commits

...

90 Commits

Author SHA1 Message Date
Allan Bowe
6e32d9b743 Merge pull request #273 from sasjs/allanbowe/mp-jsonout-fails-in-meta-272
fix: setting length of label property in mp_jsonout
2022-07-04 13:27:02 +01:00
Allan Bowe
6b167e7a4c fix: longer label to allow for escapes 2022-07-04 12:26:19 +00:00
Allan Bowe
011672b1ed fix: setting length of label property in mp_jsonout 2022-07-04 12:24:53 +00:00
Allan Bowe
a7eb926810 Merge pull request #271 from sasjs/ms_getusers
fix: ensuring results when strict mode enabled in ms_getusers
2022-07-02 21:14:06 +01:00
Allan Bowe
cad7f13a0e fix: ensuring results when strict mode enabled in ms_getusers 2022-07-02 20:13:02 +00:00
Allan Bowe
65fcea817a Merge pull request #270 from sasjs/forcerelease
fix: forcing release for the previous fix
2022-07-01 00:17:11 +02:00
Allan Bowe
22fade13e7 fix: forcing release for the previous fix 2022-06-30 22:16:45 +00:00
Allan Bowe
7146310072 fix: missing ampersand 2022-06-30 22:06:27 +00:00
Allan Bowe
b7de1c25ec Merge pull request #269 from sasjs/jsonfix
fix: mX_webout macros in DEBUG mode had truncated json
2022-06-30 23:44:44 +02:00
Allan Bowe
f4c7f47ffe fix: mX_webout macros in DEBUG mode had truncated json
This was due to options obs=10 which affected new cross-encoding streaming technique
2022-06-30 21:41:10 +00:00
munja
cdf339d077 fix: reduce logging when debug is off 2022-06-29 20:09:00 +01:00
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
144 changed files with 3895 additions and 1766 deletions

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

@@ -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

2577
all.sas

File diff suppressed because it is too large Load Diff

View File

@@ -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

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

@@ -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

@@ -33,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,174 +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(_METAFOLDER)
or "&SYSPROCESSNAME "="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;
options nosyntaxcheck;
%end;
%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 syswarningtext syserrortext $32767 ;
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='""';
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)
@@ -243,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

@@ -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;

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)

View File

@@ -1,50 +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 ms_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
@param [in] mDebug= (0) set to 1 to show debug messages in the log
@version 9.2
@author Allan Bowe
**/
@@ -57,40 +20,13 @@ Usage:
,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
)
%end;
%else %if &platform=SASJS %then %do;
%if "&path"="HOME" %then %let path=/Users/&_sasjs_username/My Folder;
%ms_createwebservice(path=&path
,name=&name
,code=&code
,precode=&precode
,mdebug=&mdebug
)
%end;
%else %do;
%if "&path"="HOME" %then %let path=/User Folders/&_METAPERSON/My Folder;
%mm_createwebservice(path=&path
,name=&name
,code=&code
,precode=&precode
,desc=&desc
,replace=&replace
)
%end;
%mend mp_createwebservice;

View File

@@ -51,7 +51,7 @@
@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
@@ -254,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;

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

@@ -58,7 +58,7 @@
%if %mf_getattrn(&libds,NLOBS)=0 %then %do;
data &outds;
length hashkey $32;
retain hashkey "%sysfunc(md5(%str(&salt)),$hex32.)";
hashkey=put(md5("&salt"),$hex32.);
output;
stop;
run;
@@ -75,7 +75,8 @@
%end;
;
length &prevkeyvar &keyvar $32;
retain &prevkeyvar "%sysfunc(md5(%str(&salt)),$hex32.)";
retain &prevkeyvar;
if _n_=1 then &prevkeyvar=put(md5("&salt"),$hex32.);
set &libds end=&lastvar;
/* hash should include previous row */
&keyvar=%mp_md5(

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,10 +74,12 @@
%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;
@@ -75,9 +88,23 @@
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,6 +235,7 @@
%else %do;
set &ds;
%end;
&stmt_obs;
format _numeric_ bart.;
%do i=1 %to &numcols;
%if &&typelong&i=char or &fmt=Y %then %do;
@@ -220,10 +260,9 @@
%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
@@ -231,39 +270,38 @@
%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;
length label $350;
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 "," @@;
@@ -272,6 +310,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

@@ -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

@@ -1,53 +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_chop.sas
@li mp_ds2csv.sas
@li ms_testservice.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
@@ -65,237 +23,17 @@
viyaresult=WEBOUT_JSON,
viyacontext=SAS Job Execution compute context
)/*/STORE SOURCE*/;
%local dbg pcnt fref1 fref2 webref webrefpath 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 platform=%mf_getplatform();
%let fref1=%mf_getuniquefileref();
%let fref2=%mf_getuniquefileref();
%let webref=%mf_getuniquefileref();
%let webrefpath=%sysfunc(pathname(work))/%mf_getuniquename();
/* mp_chop requires a physical path as input */
filename &webref "&webrefpath";
%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 %if &platform=SASJS %then %do;
%ms_testservice(&program
,inputfiles=&inputfiles
,inputdatasets=&inputdatasets
,inputparams=&inputparams
,debug=&debug
,mdebug=&mdebug
,outlib=&outlib
,outref=&outref
)
%end;
%else %do;
%put %str(ERR)OR: Unrecognised platform: &platform;
%end;
%if &mdebug=0 %then %do;
filename &fref1 clear;
%if &platform ne SASJS %then %do;
filename &fref2 clear;
filename &webref clear;
%end;
%end;
%else %do;
%put &sysmacroname exit vars:;
%put _local_;
%end;
%mend mp_testservice;
%mend mp_testservice;

View File

@@ -102,7 +102,7 @@ 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")]

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
@@ -94,13 +104,37 @@
*/
/*! \dir ddl
* \brief Data Definition Language files
* \details Provides templates for commonly used tables in sasjs/core.
Attributes:
/*! \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: _mddl_
* 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

@@ -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,10 +97,12 @@ 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; ';
@@ -108,9 +111,23 @@ data _null_;
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,6 +258,7 @@ 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; ';
@@ -253,10 +283,9 @@ data _null_;
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 ';
@@ -264,39 +293,38 @@ 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 ' length label $350; ';
put ' put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{"; ';
put ' do i=1 to &numcols; ';
put ' name=quote(trim(symget(cats(''name'',i)))); ';
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 "," @@; ';
@@ -305,6 +333,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 ' ';
@@ -315,18 +352,19 @@ data _null_;
put '%end; ';
put '%mend mp_jsonout; ';
put ' ';
put '%macro mf_getuser(type=META ';
put '%macro mf_getuser( ';
put ')/*/STORE SOURCE*/; ';
put ' %local user metavar; ';
put ' %if &type=OS %then %let metavar=_secureusername; ';
put ' %else %let metavar=_metaperson; ';
put ' %local user; ';
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 ' %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(&&&metavar),1,@)); ';
put ' %else %let user=%unquote(%scan(%quote(&_metaperson),1,@)); ';
put ' %end; ';
put ' %else %let user=&sysuserid; ';
put ' ';
@@ -334,7 +372,7 @@ data _null_;
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; ';
@@ -405,9 +443,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; ';
@@ -418,11 +457,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''); ';
@@ -431,16 +470,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()"" "; ';
@@ -451,6 +490,7 @@ data _null_;
put ' put '',"_METAPERSON": '' _METAPERSON; ';
put ' put '',"_PROGRAM" : '' _PROGRAM ; ';
put ' put ",""SYSCC"" : ""&syscc"" "; ';
put ' put ",""SYSENCODING"" : ""&sysencoding"" "; ';
put ' syserrortext=quote(cats(symget(''SYSERRORTEXT''))); ';
put ' put '',"SYSERRORTEXT" : '' syserrortext; ';
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
@@ -471,6 +511,16 @@ 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; ';
@@ -492,7 +542,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;
@@ -528,12 +578,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,6 +162,7 @@
put ',"_METAPERSON": ' _METAPERSON;
put ',"_PROGRAM" : ' _PROGRAM ;
put ",""SYSCC"" : ""&syscc"" ";
put ",""SYSENCODING"" : ""&sysencoding"" ";
syserrortext=quote(cats(symget('SYSERRORTEXT')));
put ',"SYSERRORTEXT" : ' syserrortext;
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
@@ -177,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;

View File

@@ -6,8 +6,10 @@
"fcmp",
"lua",
"server",
"tests/crossplatform",
"tests/ddl"
"xplatform",
"tests/base",
"tests/ddlonly",
"tests/x-platform"
],
"docConfig": {
"displayMacroCore": false,
@@ -65,7 +67,7 @@
},
{
"name": "server",
"serverUrl": "https://sas.analytium.co.uk:5007",
"serverUrl": "",
"serverType": "SASJS",
"httpsAgentOptions": {
"allowInsecureRequests": false

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;

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;

View File

@@ -98,10 +98,12 @@ 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; ';
@@ -110,9 +112,23 @@ data _null_;
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 ';
@@ -166,13 +182,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: ';
@@ -231,6 +259,7 @@ 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; ';
@@ -255,10 +284,9 @@ data _null_;
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 ';
@@ -266,39 +294,38 @@ 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 ' length label $350; ';
put ' put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{"; ';
put ' do i=1 to &numcols; ';
put ' name=quote(trim(symget(cats(''name'',i)))); ';
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 "," @@; ';
@@ -307,6 +334,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 ' ';
@@ -317,18 +353,19 @@ data _null_;
put '%end; ';
put '%mend mp_jsonout; ';
put ' ';
put '%macro mf_getuser(type=META ';
put '%macro mf_getuser( ';
put ')/*/STORE SOURCE*/; ';
put ' %local user metavar; ';
put ' %if &type=OS %then %let metavar=_secureusername; ';
put ' %else %let metavar=_metaperson; ';
put ' %local user; ';
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 ' %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(&&&metavar),1,@)); ';
put ' %else %let user=%unquote(%scan(%quote(&_metaperson),1,@)); ';
put ' %end; ';
put ' %else %let user=&sysuserid; ';
put ' ';
@@ -337,7 +374,7 @@ data _null_;
put '%mend mf_getuser; ';
put ' ';
put '%macro ms_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; ';
@@ -402,7 +439,6 @@ data _null_;
put '%else %if &action=CLOSE %then %do; ';
put ' %if %str(&_debug) ge 131 %then %do; ';
put ' /* if debug mode, send back first 10 records of each work table also */ ';
put ' options obs=10; ';
put ' data;run;%let tempds=%scan(&syslast,2,.); ';
put ' ods output Members=&tempds; ';
put ' proc datasets library=WORK memtype=data; ';
@@ -427,7 +463,7 @@ data _null_;
put ' put " ""&wt"" : {"; ';
put ' put ''"nlobs":'' nlobs; ';
put ' put '',"nvars":'' nvars; ';
put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=YES) ';
put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=Y,maxobs=10) ';
put ' data _null_; file &fref mod encoding=''utf-8'' termstr=lf; ';
put ' put "}"; ';
put ' %end; ';
@@ -443,6 +479,7 @@ data _null_;
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''))); ';

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;

View File

@@ -2,16 +2,26 @@
@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.
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:$18.|USERNAME:$10.|ID:best.|
|DISPLAYNAME:$60.|USERNAME:$30.|ID:best.|
|---|---|---|
|`Super Admin `|`secretuser `|`1`|
|`Sabir Hassan`|`sabir`|`2`|
@@ -20,7 +30,6 @@
|`New User `|`newuser `|`5`|
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mf_getuniquelibref.sas
@@ -28,14 +37,17 @@
<h4> Related Files </h4>
@li ms_createuser.sas
@li ms_getgroups.sas
@li ms_getusers.test.sas
**/
%macro ms_getusers(
outds=work.ms_getusers
,mdebug=0
);
outds=work.ms_getusers,
group=0,
gid=0,
mdebug=0
);
%mp_abort(
iftrue=(&syscc ne 0)
@@ -43,11 +55,24 @@
,msg=%str(syscc=&syscc on macro entry)
)
%local fref0 fref1 libref optval rc msg;
%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;
@@ -68,26 +93,42 @@ run;
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/SASjsApi/user";
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 GET query to SASjsApi/user)
,msg=%str(Issue submitting API query)
)
libname &libref JSON fileref=&fref1;
data &outds;
set &libref..root;
drop ordinal_root;
run;
%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)

View File

@@ -162,6 +162,7 @@ proc http method='POST' headerin=&authref in=&mainref out=&outref
%end;
run;
%if (&SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201)
or &mdebug=1
%then %do;
data _null_;infile &outref;input;putlog _infile_;run;
%end;
@@ -177,7 +178,7 @@ options &optval;
%if &outlogds ne _null_ or &mdebug=1 %then %do;
%local dumplib;
%let dumplib=%mf_getuniquelibref();
libname &dumplib json (&outref);
libname &dumplib json fileref=&outref;
data &outlogds;
set &dumplib..log;
%if &mdebug=1 %then %do;
@@ -195,4 +196,4 @@ options &optval;
filename &authref;
filename &mainref;
%end;
%mend ms_runstp;
%mend ms_runstp;

View File

@@ -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;
@@ -112,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;
@@ -137,7 +136,7 @@
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;
@@ -153,6 +152,7 @@
put ",""_DEBUG"" : ""&_debug"" ";
put ',"_PROGRAM" : ' _PROGRAM ;
put ",""SYSCC"" : ""&syscc"" ";
put ",""SYSENCODING"" : ""&sysencoding"" ";
syserrortext=quote(cats(symget('SYSERRORTEXT')));
put ',"SYSERRORTEXT" : ' syserrortext;
SYSHOSTINFOLONG=quote(trim(symget('SYSHOSTINFOLONG')));

View File

@@ -5,6 +5,7 @@
<h4> SAS Macros </h4>
@li mp_binarycopy.sas
@li mp_assert.sas
@li mp_hashdataset.sas
**/
@@ -96,4 +97,40 @@ run;
iftrue=("&string4"="&string4_check"),
desc=Append Check (ref to file),
outds=work.test_results
)
)
/* test 5 - ensure copy works for binary characters */
/* do this backwards to avoid null chars in JSON preview */
data work.test5;
do i=255 to 1 by -1;
str=byte(i);
output;
end;
run;
/* get an md5 hash of the ds */
%mp_hashdataset(work.test5,outds=myhash)
/* copy it */
%mp_binarycopy(inloc="%sysfunc(pathname(work))/test5.sas7bdat",
outloc="%sysfunc(pathname(work))/test5copy.sas7bdat"
)
/* get an md5 hash of the copied ds */
%mp_hashdataset(work.test5copy,outds=myhash2)
/* compare hashes */
%let test5a=0;
%let test5b=1;
data _null_;
set myhash;
call symputx('test5a',hashkey);
run;
data _null_;
set myhash2;
call symputx('test5b',hashkey);
run;
%mp_assert(
iftrue=("&test5a"="&test5b"),
desc=Ensuring binary copy works on binary characters,
outds=work.test_results
)

View File

@@ -21,7 +21,8 @@
%mf_mkdir(&root/a/d)
%mf_mkdir(&root/a/e)
%mf_mkdir(&root/a/e/f)
data "&root/a/e/f/ds1.sas7bdat";
libname test "&root/a/e/f";
data test.ds1;
x=1;
run;

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