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

Compare commits

...

56 Commits

Author SHA1 Message Date
Allan Bowe
486aba84ca Merge pull request #72 from sasjs/webinnings
New mp_webin() macro and performance improvement to mf_getuniquefileref()
2021-09-08 19:51:36 +03:00
Allan Bowe
b5944181e1 chore(dependencies): bumping cli 2021-09-08 19:35:27 +03:00
Allan Bowe
3f69cf506a feat: mp_webin() macro for handling the _webin_xxx macro variables in SAS web services 2021-09-08 19:34:28 +03:00
Allan Bowe
6013897c50 feat: updating mf_getuniquefileref() to use native approach to get a unique fileref (which is 100 times faster than the old approach) 2021-09-08 15:30:51 +03:00
Allan Bowe
27cf2a2532 Merge pull request #71 from sasjs/including
fix: conditional logic around mp_abort(mode=INCLUDE) to cover case wh…
2021-09-05 20:56:40 +03:00
Allan Bowe
d096cbddeb fix: conditional logic around mp_abort(mode=INCLUDE) to cover case when no mp_include was executed 2021-09-05 20:55:44 +03:00
Allan Bowe
117503f214 Merge pull request #70 from sasjs/including
feat: new mp_include() feature for handling %includes with macros
2021-09-03 11:17:21 +03:00
Allan Bowe
5cc5fae750 chore: adding mp_abort to mp_include sas macros list 2021-09-03 11:06:12 +03:00
Allan Bowe
d93032e1a9 chore: docs 2021-09-03 10:54:21 +03:00
Allan Bowe
507557b2cb feat: new mp_include() feature for handling %includes with macros 2021-09-03 00:40:10 +03:00
Allan Bowe
ee5c3c185a chore: merge 2021-08-28 14:22:16 +03:00
Allan Bowe
e5592a2eb2 chore: avoiding the string 'ERROR:' in logs 2021-08-28 14:21:36 +03:00
Allan Bowe
59200a6e73 Merge pull request #69 from sasjs/issue68
feat: supporting postgres timestamps for mp_ds2inserts and mp_lib2inserts
2021-08-24 21:03:09 +03:00
Allan Bowe
f468f60ae1 fix: substr issue in mp_ds2inserts. Closes #68 2021-08-24 20:51:29 +03:00
Allan Bowe
9f60d827b6 feat: supporting postgres timestamps for mp_ds2inserts and mp_lib2inserts 2021-08-24 20:39:02 +03:00
Allan Bowe
5c936ddb65 Merge pull request #67 from sasjs/issue66
feat: adding APPEND option to mp_binarycopy.sas, and a new test (mp_b…
2021-08-24 14:04:25 +03:00
Allan Bowe
d0bde62594 feat: adding APPEND option to mp_binarycopy.sas, and a new test (mp_binarycopy.test.sas). Closes #66 2021-08-24 13:43:31 +03:00
Allan Bowe
ada9192337 Merge pull request #65 from sasjs/issue64
fix: increasing limit for mv_getfoldermember.sas, also adding a test,…
2021-08-20 22:50:27 +03:00
Allan Bowe
6161f588a9 fix: increasing limit for mv_getfoldermember.sas, also adding a test, and updating mf_getapploc to search _program by default (and fixing it to work with macro tests, and updating the test for that also) 2021-08-20 22:38:56 +03:00
Allan Bowe
67079d8c17 fix: adding default value for exists in mf_existfunction 2021-08-19 00:20:49 +03:00
Allan Bowe
75bd39adb0 Merge pull request #63 from sasjs/issue62
feat: adding FCMP capability
2021-08-19 00:00:15 +03:00
Allan Bowe
078bdbeecf chore: bumping devDependency (sasjs cli) 2021-08-18 23:47:17 +03:00
Allan Bowe
8ddb86785c feat: new fcmp stpsrv_header function 2021-08-18 23:45:45 +03:00
Allan Bowe
005af0ecf8 feat: new mf_existfunction macro 2021-08-18 23:36:12 +03:00
Allan Bowe
bc410a9135 chore: fixing docs for tests 2021-08-18 19:55:05 +03:00
Allan Bowe
fc8ba2e36c chore: moving files to tidy up docs 2021-08-18 19:43:38 +03:00
Allan Bowe
756441384a feat: adding first fcmp macro, mcf_string2file.sas 2021-08-18 18:35:51 +03:00
Allan Bowe
10f9eecf9e Merge pull request #60 from sasjs/vpn-fix
chore: vpn fix
2021-08-13 14:41:25 +03:00
470ebb50a7 chore: vpn fix 2021-08-13 12:57:31 +02:00
Allan Bowe
26cd5d9d31 Merge pull request #59 from Stefan-Dimitrov-Stoyanov/patch-1
Update README.md
2021-08-12 11:59:25 +03:00
Stefan-Dimitrov-Stoyanov
0b694bb878 Update README.md
Fix the missing space at the end of the first line under the Installation header:  https://github.com/sasjs/core/blob/main/README.md#installation
2021-08-11 13:34:44 +01:00
Allan Bowe
b403c02bba chore: docs for mm_createfolder 2021-08-06 15:36:55 +03:00
Allan Bowe
0b555bb31c Merge pull request #58 from sasjs/apploc
feat: new mf_getapploc macro
2021-08-04 22:04:55 +03:00
Allan Bowe
40b513a9e3 feat: new mf_getapploc macro 2021-08-04 22:00:18 +03:00
Allan Bowe
4eacf4deae Merge pull request #57 from sasjs/mf_existfref
fix: showing filerefs that exist (even when underlying does not) in m…
2021-08-03 14:32:23 +03:00
Allan Bowe
5824423c13 fix: showing filerefs that exist (even when underlying does not) in mf_existfileref, along with 3 tests 2021-08-03 14:16:52 +03:00
Allan Bowe
ce5bfd41dc chore(docs): updating header for mm_gettables 2021-08-02 10:37:01 +03:00
Allan Bowe
0c67a07e42 Merge pull request #56 from sasjs/ddlworkz
feat: new mp_lib2inserts macro.  In addition, modified mp_getddl to i…
2021-07-30 10:11:27 +03:00
Allan Bowe
187504600a chore: fixing test 2021-07-30 00:21:02 +03:00
Allan Bowe
658d67feaa chore: fixing comments 2021-07-30 00:17:44 +03:00
Allan Bowe
5207a77591 feat: new mp_lib2inserts macro. In addition, modified mp_getddl to ignore views, closing #5. Created a test, which highlighted another issue in mp_getddl (labels were being double quoted which caused macro resolution attempts when %including). Changed to single quotes. Switched 'outlib' to 'outschema' in mp_ds2inserts to harmonise with mp_getddl. Added a maxobs option, to speed up testing. 2021-07-30 00:14:29 +03:00
Allan Bowe
4456adf1dc fix: switching default flavour from BASE to SAS to be consistent with mp_getddl 2021-07-29 20:35:57 +03:00
Allan Bowe
03962c2a50 fix: for PGSQL DDL generation, ignore tables with over 1600 columns (as they are not supported in Postgres) 2021-07-29 15:45:16 +03:00
Allan Bowe
6d2fc7e265 fix: removing bug introduced to mp_getddl and adding a test to prevent regression 2021-07-29 13:02:58 +03:00
Allan Bowe
39b2e7c5f9 fix: supporting salts over 32 chars in mp_hashdataset() 2021-07-28 23:22:43 +03:00
Allan Bowe
f99adf5c3e Merge pull request #55 from sasjs/insertforpg
feat: updating mp_ds2inserts to support postgres database
2021-07-28 19:11:38 +03:00
Allan Bowe
69f8e91a2d feat: adding salt as option for mp_hashdataset 2021-07-28 17:04:41 +03:00
Allan Bowe
5b5d01993f fix: updating mp_getddl to enable append to a fileref 2021-07-28 17:01:49 +03:00
Allan Bowe
00fa464a7c feat: updating mp_ds2inserts to support postgres database 2021-07-27 11:07:12 +03:00
Allan Bowe
a5baf46233 chore: removing unnecessary proc format and generating the all.sas file 2021-07-26 22:30:02 +03:00
Allan Bowe
d63d2a4ec1 Merge pull request #54 from sasjs/mp_ds2inserts
feat: mp_ds2inserts macro
2021-07-26 22:10:44 +03:00
Allan Bowe
900f694065 chore: removing extra period 2021-07-26 21:59:48 +03:00
Allan Bowe
838324c15e chore: updating header 2021-07-26 19:06:53 +03:00
Allan Bowe
e3205ec06c feat: mp_ds2inserts macro for creating programs for inserting data (and corresponding test) 2021-07-26 19:04:49 +03:00
Allan Bowe
154a33434e chore: more contributors 2021-07-24 21:06:30 +03:00
Allan Bowe
bfa1bbaeb1 chore: all contributors update in README 2021-07-24 21:03:49 +03:00
66 changed files with 3065 additions and 610 deletions

104
.all-contributorsrc Normal file
View File

@@ -0,0 +1,104 @@
{
"projectName": "core",
"projectOwner": "sasjs",
"repoType": "github",
"repoHost": "https://github.com",
"files": [
"README.md"
],
"imageSize": 100,
"commit": false,
"commitConvention": "angular",
"contributors": [
{
"login": "allanbowe",
"name": "Allan Bowe",
"avatar_url": "https://avatars.githubusercontent.com/u/4420615?v=4",
"profile": "https://github.com/allanbowe",
"contributions": [
"business",
"code",
"content",
"doc",
"infra",
"maintenance",
"mentoring",
"question",
"review",
"test"
]
},
{
"login": "rafgag",
"name": "rafgag",
"avatar_url": "https://avatars.githubusercontent.com/u/69139928?v=4",
"profile": "https://github.com/rafgag",
"contributions": [
"code"
]
},
{
"login": "tmoody",
"name": "Trevor Moody",
"avatar_url": "https://avatars.githubusercontent.com/u/79837106?v=4",
"profile": "https://github.com/tmoody",
"contributions": [
"code"
]
},
{
"login": "krishna-acondy",
"name": "Krishna Acondy",
"avatar_url": "https://avatars.githubusercontent.com/u/2980428?v=4",
"profile": "https://krishna-acondy.io/",
"contributions": [
"code",
"infra",
"blog",
"content",
"ideas",
"video"
]
},
{
"login": "saadjutt01",
"name": "Muhammad Saad ",
"avatar_url": "https://avatars.githubusercontent.com/u/8914650?v=4",
"profile": "https://github.com/saadjutt01",
"contributions": [
"code",
"ideas"
]
},
{
"login": "YuryShkoda",
"name": "Yury Shkoda",
"avatar_url": "https://avatars.githubusercontent.com/u/25773492?v=4",
"profile": "https://www.erudicat.com/",
"contributions": [
"code",
"infra",
"video"
]
},
{
"login": "medjedovicm",
"name": "Mihajlo Medjedovic",
"avatar_url": "https://avatars.githubusercontent.com/u/18329105?v=4",
"profile": "https://github.com/medjedovicm",
"contributions": [
"infra"
]
},
{
"login": "kkchandok",
"name": "kkchandok",
"avatar_url": "https://avatars.githubusercontent.com/u/46090627?v=4",
"profile": "https://github.com/kkchandok",
"contributions": [
"ideas"
]
}
],
"contributorsPerLine": 7
}

View File

@@ -39,7 +39,7 @@ jobs:
sudo apt install apt-transport-https
sudo wget https://swupdate.openvpn.net/repos/openvpn-repo-pkg-key.pub
sudo apt-key add openvpn-repo-pkg-key.pub
sudo wget -O /etc/apt/sources.list.d/openvpn3.list https://swupdate.openvpn.net/community/openvpn3/repos/openvpn3-bionic.list
sudo wget -O /etc/apt/sources.list.d/openvpn3.list https://swupdate.openvpn.net/community/openvpn3/repos/openvpn3-focal.list
sudo apt update
sudo apt install openvpn3

View File

@@ -41,6 +41,13 @@ Documentation: https://core.sasjs.io
- No X command
- Prefixes: _mf_, _mp_
**fcmp** library (SAS9/Viya)
- 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)
- OS independent
@@ -84,7 +91,7 @@ run;
# 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:
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");
@@ -179,3 +186,34 @@ If you find this library useful, please leave a [star](https://github.com/sasjs/
## Contributors ✨
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-8-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="https://github.com/allanbowe"><img src="https://avatars.githubusercontent.com/u/4420615?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Allan Bowe</b></sub></a><br /><a href="#business-allanbowe" title="Business development">💼</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Code">💻</a> <a href="#content-allanbowe" title="Content">🖋</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Documentation">📖</a> <a href="#infra-allanbowe" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#maintenance-allanbowe" title="Maintenance">🚧</a> <a href="#mentoring-allanbowe" title="Mentoring">🧑‍🏫</a> <a href="#question-allanbowe" title="Answering Questions">💬</a> <a href="https://github.com/sasjs/core/pulls?q=is%3Apr+reviewed-by%3Aallanbowe" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/rafgag"><img src="https://avatars.githubusercontent.com/u/69139928?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rafgag</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=rafgag" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/tmoody"><img src="https://avatars.githubusercontent.com/u/79837106?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Trevor Moody</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=tmoody" title="Code">💻</a></td>
<td align="center"><a href="https://krishna-acondy.io/"><img src="https://avatars.githubusercontent.com/u/2980428?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Krishna Acondy</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=krishna-acondy" title="Code">💻</a> <a href="#infra-krishna-acondy" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#blog-krishna-acondy" title="Blogposts">📝</a> <a href="#content-krishna-acondy" title="Content">🖋</a> <a href="#ideas-krishna-acondy" title="Ideas, Planning, & Feedback">🤔</a> <a href="#video-krishna-acondy" title="Videos">📹</a></td>
<td align="center"><a href="https://github.com/saadjutt01"><img src="https://avatars.githubusercontent.com/u/8914650?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Muhammad Saad </b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=saadjutt01" title="Code">💻</a> <a href="#ideas-saadjutt01" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://www.erudicat.com/"><img src="https://avatars.githubusercontent.com/u/25773492?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yury Shkoda</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=YuryShkoda" title="Code">💻</a> <a href="#infra-YuryShkoda" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#video-YuryShkoda" title="Videos">📹</a></td>
<td align="center"><a href="https://github.com/medjedovicm"><img src="https://avatars.githubusercontent.com/u/18329105?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mihajlo Medjedovic</b></sub></a><br /><a href="#infra-medjedovicm" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/kkchandok"><img src="https://avatars.githubusercontent.com/u/46090627?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kkchandok</b></sub></a><br /><a href="#ideas-kkchandok" title="Ideas, Planning, & Feedback">🤔</a></td>
</tr>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

1160
all.sas

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,13 @@
%macro mf_existfileref(fref
)/*/STORE SOURCE*/;
%if %sysfunc(fileref(&fref))=0 %then %do;
%local rc;
%let rc=%sysfunc(fileref(&fref));
%if &rc=0 %then %do;
1
%end;
%else %if &rc<0 %then %do;
%put &sysmacroname: Fileref &fref exists but the underlying file does not;
1
%end;
%else %do;

37
base/mf_existfunction.sas Normal file
View File

@@ -0,0 +1,37 @@
/**
@file
@brief Checks if a function exists
@details Returns 1 if the function exists, else 0. Note that this function
can be slow as it needs to open the sashelp.vfuncs table.
Usage:
%put %mf_existfunction(CAT);
%put %mf_existfunction(DOG);
Full credit to [Bart](https://sasensei.com/user/305) for the vfunc pointer
and the tidy approach for pure macro data set filtering.
Check out his [SAS Packages](https://github.com/yabwon/SAS_PACKAGES)
framework! Where you can find the same [function](
https://github.com/yabwon/SAS_PACKAGES/blob/main/packages/baseplus.md#functionexists-macro
).
@param [in] name (positional) - function name
@author Allan Bowe
**/
/** @cond */
%macro mf_existfunction(name
)/*/STORE SOURCE*/;
%local dsid rc exist;
%let dsid=%sysfunc(open(sashelp.vfunc(where=(fncname="%upcase(&name)"))));
%let exist=1;
%let exist=%sysfunc(fetch(&dsid, NOSET));
%let rc=%sysfunc(close(&dsid));
%sysevalf(0 = &exist)
%mend mf_existfunction;
/** @endcond */

73
base/mf_getapploc.sas Normal file
View File

@@ -0,0 +1,73 @@
/**
@file
@brief Returns the appLoc from the _program variable
@details When working with SASjs apps, web services / tests / jobs are always
deployed to a root (app) location in the SAS logical folder tree.
When building apps for use in other environments, you do not necessarily know
where the backend services will be deployed. Therefore a function like this
is handy in order to dynamically figure out the appLoc, and enable other
services to be connected by a relative reference.
SASjs apps always have the same immediate substructure (one or more of the
following):
@li /data
@li /jobs
@li /services
@li /tests/jobs
@li /tests/services
@li /tests/macros
This function works by testing for the existence of any of the above in the
automatic _program variable, and returning the part to the left of it.
Usage:
%put %mf_getapploc(&_program)
%put %mf_getapploc(/some/location/services/admin/myservice);
%put %mf_getapploc(/some/location/jobs/extract/somejob/);
%put %mf_getapploc(/some/location/tests/jobs/somejob/);
@author Allan Bowe
**/
%macro mf_getapploc(pgm);
%if "&pgm"="" %then %do;
%if %symexist(_program) %then %let pgm=&_program;
%else %do;
%put &sysmacroname: No value provided and no _program variable available;
%return;
%end;
%end;
%local root;
/**
* First check we are not in the tests/macros folder (which has no subfolders)
*/
%if %index(&pgm,/tests/macros/) %then %do;
%let root=%substr(&pgm,1,%index(&pgm,/tests/macros)-1);
&root
%return;
%end;
/**
* Next, move up two levels to avoid matches on subfolder or service name
*/
%let root=%substr(&pgm,1,%length(&pgm)-%length(%scan(&pgm,-1,/))-1);
%let root=%substr(&root,1,%length(&root)-%length(%scan(&root,-1,/))-1);
%if %index(&root,/tests/) %then %do;
%let root=%substr(&root,1,%index(&root,/tests/)-1);
%end;
%else %if %index(&root,/services) %then %do;
%let root=%substr(&root,1,%index(&root,/services)-1);
%end;
%else %if %index(&root,/jobs) %then %do;
%let root=%substr(&root,1,%index(&root,/jobs)-1);
%end;
%else %put &sysmacroname: Could not find an app location from &pgm;
&root
%mend mf_getapploc ;

View File

@@ -1,37 +1,58 @@
/**
@file
@brief Assigns and returns an unused fileref
@details
@details Using the native approach for assigning filerefs fails as some
procedures (such as proc http) do not recognise the temporary names (starting
with a hash), returning a message such as:
> ERROR 22-322: Expecting a name.
This macro works by attempting a random fileref (with a prefix), seeing if it
is already assigned, and if not - returning the fileref.
If your process can accept filerefs with the hash (#) prefix, then set
`prefix=0` to revert to the native approach - which is significantly faster
when there are a lot of filerefs in a session.
Use as follows:
%let fileref1=%mf_getuniquefileref();
%let fileref2=%mf_getuniquefileref();
%let fileref2=%mf_getuniquefileref(prefix=0);
%put &fileref1 &fileref2;
which returns:
which returns filerefs similar to:
> mcref0 mcref1
> _7432233 #LN00070
@param prefix= first part of fileref. Remember that filerefs can only be 8
characters, so a 7 letter prefix would mean that `maxtries` should be 10.
@param maxtries= the last part of the libref. Provide an integer value.
@param [in] prefix= (_) first part of fileref. Remember that filerefs can only
be 8 characters, so a 7 letter prefix would mean `maxtries` should be 10.
if using zero (0) as the prefix, a native assignment is used.
@param [in] maxtries= (1000) the last part of the libref. Must be an integer.
@version 9.2
@author Allan Bowe
**/
%macro mf_getuniquefileref(prefix=mcref,maxtries=1000);
%local x fname;
%let x=0;
%do x=0 %to &maxtries;
%if %sysfunc(fileref(&prefix&x)) > 0 %then %do;
%let fname=&prefix&x;
%macro mf_getuniquefileref(prefix=_,maxtries=1000);
%local rc fname;
%if &prefix=0 %then %do;
%let rc=%sysfunc(filename(fname,,temp));
%if &rc %then %put %sysfunc(sysmsg());
&prefix&x
%*put &sysmacroname: Fileref &prefix&x was assigned and returned;
%return;
&fname
%end;
%else %do;
%local x len;
%let len=%eval(8-%length(&prefix));
%let x=0;
%do x=0 %to &maxtries;
%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);
%if %sysfunc(fileref(&fname)) > 0 %then %do;
%let rc=%sysfunc(filename(fname,,temp));
%if &rc %then %put %sysfunc(sysmsg());
&fname
%return;
%end;
%end;
%put unable to find available fileref after &maxtries attempts;
%end;
%put unable to find available fileref in range &prefix.0-&maxtries;
%mend mf_getuniquefileref;

View File

@@ -15,24 +15,47 @@
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. For 9.4m3 we take a unique approach - we open a macro
but don't close it! This provides a graceful abort, EXCEPT when called
called within a %include within a macro (and that macro contains additional
logic). See mp_abort.test.nofix.sas for the example case.
If you know of another way to gracefully abort a 9.4m3 STP session, we'd
love to hear about it!
and set SYSCC=0. 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).
@param mac= to contain the name of the calling macro
@param msg= message to be returned
@param iftrue= supply a condition under 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
scenario, the %include should be switched for the mp_include.sas macro.
This provides an indicator that we are running a macro within a \%include
(`_SYSINCLUDEFILEDEVICE`) and allows us to provide a dataset with the abort
values (msg, mac).
We can then run an abort cancel FILE to stop the include running, and pass
the dataset back to the calling program to run a regular \%mp_abort().
The dataset will contain the following fields:
@li iftrue (1=1)
@li msg (the message)
@li mac (the mac param)
@version 9.4M3
@param mode= (REGULAR) If mode=INCLUDE then the &errds dataset is checked for
an abort status.
Valid values:
@li REGULAR (default)
@li INCLUDE
<h4> Related Macros </h4>
@li mp_include.sas
@version 9.4
@author Allan Bowe
@cond
**/
%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)
, errds=work.mp_abort_errds
, mode=REGULAR
)/*/STORE SOURCE*/;
%global sysprocessmode sysprocessname;
@@ -43,9 +66,44 @@
%if %length(&mac)>0 %then %put NOTE- called by &mac;
%put NOTE - &msg;
%if %symexist(_SYSINCLUDEFILEDEVICE) %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;
/* Stored Process Server web app context */
%if %symexist(_metaperson) or "&SYSPROCESSNAME "="Compute Server " %then %do;
%if %symexist(_metaperson)
or "&SYSPROCESSNAME "="Compute Server "
or &mode=INCLUDE
%then %do;
options obs=max replace nosyntaxcheck mprint;
%if &mode=INCLUDE %then %do;
%if %sysfunc(exist(&errds))=1 %then %do;
data _null_;
set &errds;
call symputx('iftrue',iftrue,'l');
call symputx('mac',mac,'l');
call symputx('msg',msg,'l');
putlog (_all_)(=);
run;
%if (&iftrue)=0 %then %return;
%end;
%else %do;
%put &sysmacroname: No include errors found;
%return;
%end;
%end;
/* extract log errs / warns, if exist */
%local logloc logline;
%global logmsg; /* capture global messages */
@@ -132,7 +190,9 @@
put ',"_PROGRAM" : ' _PROGRAM ;
put ",""SYSCC"" : ""&syscc"" ";
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
put ",""SYSJOBID"" : ""&sysjobid"" ";
put ",""SYSSITE"" : ""&syssite"" ";
sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong;
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" ";
@@ -149,11 +209,22 @@
rc=stpsrvset('program error', 0);
call symputx("syscc",0,"g");
run;
%if "%substr(&sysvlong.xxxxxxxxx,1,9)" ne "9.04.01M3" %then %do;
%put NOTE: Ending SAS session due to:;
%put NOTE- &msg;
endsas;
%end;
/**
* endsas kills 9.4m3 deployments by orphaning multibridges.
* Abort variants are ungraceful (non zero return code)
* This approach lets SAS run silently until the end :-)
* Caution - fails when called within a %include within a macro
* Use mp_include() to handle this.
*/
filename skip temp;
data _null_;
file skip;
put '%macro skip();';
comment '%mend skip; -> fix lint ';
put '%macro skippy();';
comment '%mend skippy; -> fix lint ';
run;
%inc skip;
%end;
%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
/* endsas kills the session making it harder to fetch results */
@@ -169,24 +240,6 @@
abort cancel nolist;
run;
%end;
%else %if "%substr(&sysvlong.xxxxxxxxx,1,9)" = "9.04.01M3" %then %do;
/**
* endsas kills 9.4m3 deployments by orphaning multibridges.
* Abort variants are ungraceful (non zero return code)
* This approach lets SAS run silently until the end :-)
* Caution - fails when called within a %include within a macro
* See tests/mp_abort.test.1 for an example case.
*/
filename skip temp;
data _null_;
file skip;
put '%macro skip();';
comment '%mend skip; -> fix lint ';
put '%macro skippy();';
comment '%mend skippy; -> fix lint ';
run;
%inc skip;
%end;
%else %do;
%abort cancel;
%end;

View File

@@ -9,10 +9,29 @@
%mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout)
@param inloc full, quoted "path/and/filename.ext" of the object to be copied
@param outloc full, quoted "path/and/filename.ext" of object to be created
@param inref can override default input fileref to avoid naming clash
@param outref an override default output fileref to avoid naming clash
To append to a file, use the mode option, eg:
filename tmp1 temp;
filename tmp2 temp;
data _null_;
file tmp1;
put 'stacking';
run;
%mp_binarycopy(inref=tmp1, outref=tmp2, mode=APPEND)
%mp_binarycopy(inref=tmp1, outref=tmp2, mode=APPEND)
@param [in] inloc quoted "path/and/filename.ext" of the file to be copied
@param [out] outloc quoted "path/and/filename.ext" of the file to be created
@param [in] inref (____in) If provided, this fileref will take precedence over
the `inloc` parameter
@param [out] outref (____in) If provided, this fileref will take precedence
over the `outloc` parameter. It must already exist!
@param [in] mode (CREATE) Valid values:
@li CREATE - Create the file (even if it already exists)
@li APPEND - Append to the file (don't overwrite)
@returns nothing
@version 9.2
@@ -24,20 +43,29 @@
,outloc= /* full path and filename of object to be created */
,inref=____in /* override default to use own filerefs */
,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;
/* these IN and OUT filerefs can point to anything */
%if &inref = ____in %then %do;
filename &inref &inloc lrecl=1048576 ;
%end;
%if &outref=____out %then %do;
filename &outref &outloc lrecl=1048576 ;
filename &outref &outloc lrecl=1048576 &mod;
%end;
/* copy the file byte-for-byte */
data _null_;
length filein 8 fileid 8;
filein = fopen("&inref",'I',1,'B');
fileid = fopen("&outref",'O',1,'B');
fileid = fopen("&outref",&outmode,1,'B');
rec = '20'x;
do while(fread(filein)=0);
rc = fget(filein,rec,1);

179
base/mp_ds2inserts.sas Normal file
View File

@@ -0,0 +1,179 @@
/**
@file
@brief Export a dataset to SQL insert statements
@details Converts dataset values to SQL insert statements for use across
multiple database types.
Usage:
%mp_ds2inserts(sashelp.class,outref=myref,outds=class)
data class;
set sashelp.class;
stop;
proc sql;
%inc myref;
@param [in] ds The dataset to be exported
@param [in] maxobs= (max) The max number of inserts to create
@param [out] outref= (0) The output fileref. If it does not exist, it is
created. If it does exist, new records are APPENDED.
@param [out] schema= (0) The library (or schema) in which the target table is
located. If not provided, is ignored.
@param [out] outds= (0) The output table to load. If not provided, will
default to the table in the &ds parameter.
@param [in] flavour= (SAS) The SQL flavour to be applied to the output. Valid
options:
@li SAS (default) - suitable for regular proc sql
@li PGSQL - Used for Postgres databases
@param [in] applydttm= (YES) If YES, any columns using datetime formats will
be converted to native DB datetime literals
<h4> SAS Macros </h4>
@li mf_existfileref.sas
@li mf_getvarcount.sas
@li mf_getvarformat.sas
@li mf_getvarlist.sas
@li mf_getvartype.sas
@version 9.2
@author Allan Bowe (credit mjsq)
**/
%macro mp_ds2inserts(ds, outref=0,schema=0,outds=0,flavour=SAS,maxobs=max
,applydttm=YES
)/*/STORE SOURCE*/;
%if not %sysfunc(exist(&ds)) %then %do;
%put %str(WAR)NING: &ds does not exist;
%return;
%end;
%if not %sysfunc(exist(&ds)) %then %do;
%put %str(WAR)NING: &ds does not exist;
%return;
%end;
%if %index(&ds,.)=0 %then %let ds=WORK.&ds;
%let flavour=%upcase(&flavour);
%if &flavour ne SAS and &flavour ne PGSQL %then %do;
%put %str(WAR)NING: &flavour is not supported;
%return;
%end;
%if &outref=0 %then %do;
%put %str(WAR)NING: Please provide a fileref;
%return;
%end;
%if %mf_existfileref(&outref)=0 %then %do;
filename &outref temp lrecl=66000;
%end;
%if &schema=0 %then %let schema=;
%else %let schema=&schema..;
%if &outds=0 %then %let outds=%scan(&ds,2,.);
%local nobs;
proc sql noprint;
select count(*) into: nobs TRIMMED from &ds;
%if &nobs=0 %then %do;
data _null_;
file &outref mod;
put "/* No rows found in &ds */";
run;
%end;
%local vars;
%let vars=%mf_getvarcount(&ds);
%if &vars=0 %then %do;
data _null_;
file &outref mod;
put "/* No columns found in &schema.&ds */";
run;
%return;
%end;
%else %if &vars>1600 and &flavour=PGSQL %then %do;
data _null_;
file &fref mod;
put "/* &schema.&ds contains &vars vars */";
put "/* Postgres cannot handle tables with over 1600 vars */";
put "/* No inserts will be generated for this table */";
run;
%return;
%end;
%local varlist varlistcomma;
%let varlist=%mf_getvarlist(&ds);
%let varlistcomma=%mf_getvarlist(&ds,dlm=%str(,),quote=double);
/* next, export data */
data _null_;
file &outref mod ;
if _n_=1 then put "/* &schema.&outds (&nobs rows, &vars columns) */";
set &ds;
%if &maxobs ne max %then %do;
if _n_>&maxobs then stop;
%end;
length _____str $32767;
format _numeric_ best.;
format _character_ ;
%local i comma var vtype vfmt;
%do i=1 %to %sysfunc(countw(&varlist));
%let var=%scan(&varlist,&i);
%let vtype=%mf_getvartype(&ds,&var);
%let vfmt=%upcase(%mf_getvarformat(&ds,&var,force=1));
%if &i=1 %then %do;
%if &flavour=SAS %then %do;
put "insert into &schema.&outds set ";
put " &var="@;
%end;
%else %if &flavour=PGSQL %then %do;
_____str=cats(
"INSERT INTO &schema.&outds ("
,symget('varlistcomma')
,") VALUES ("
);
put _____str;
put " "@;
%end;
%end;
%else %do;
%if &flavour=SAS %then %do;
put " ,&var="@;
%end;
%else %if &flavour=PGSQL %then %do;
put " ,"@;
%end;
%end;
%if &vtype=N %then %do;
%if &flavour=SAS %then %do;
put &var;
%end;
%else %if &flavour=PGSQL %then %do;
if missing(&var) then put 'NULL';
%if &applydttm=YES and "%substr(&vfmt.xxxxxxxx,1,8)"="DATETIME"
%then %do;
else put "TIMESTAMP '" &var E8601DT25.6 "'";
%end;
%else %do;
else put &var;
%end;
%end;
%end;
%else %do;
_____str="'"!!trim(tranwrd(&var,"'","''"))!!"'";
put _____str;
%end;
%end;
%if &flavour=SAS %then %do;
put ';';
%end;
%else %if &flavour=PGSQL %then %do;
put ');';
%end;
if _n_=&nobs then put /;
run;
%mend mp_ds2inserts;

View File

@@ -78,7 +78,7 @@ run;
data &outds;
if &sqlrc or &syscc or &syserr then do;
REASON_CD='VALIDATION_ERROR: '!!
REASON_CD='VALIDATION_ERR'!!'OR: '!!
coalescec(symget('SYSERRORTEXT'),symget('SYSWARNINGTEXT'));
output;
end;

View File

@@ -5,6 +5,8 @@
to create tables in SAS or a database. The macro can be used at table or
library level. The default behaviour is to create DDL in SAS format.
Note - views are not currently supported.
Usage:
data test(index=(pk=(x y)/unique /nomiss));
@@ -16,12 +18,14 @@
%mp_getddl(work,test,flavour=tsql,showlog=YES)
<h4> SAS Macros </h4>
@li mf_existfileref.sas
@li mf_getvarcount.sas
@li mp_getconstraints.sas
@param lib libref of the library to create DDL for. Should be assigned.
@param ds dataset to create ddl for (optional)
@param fref= the fileref to which to write the DDL. If not preassigned, will
be assigned to TEMP.
@param fref= the fileref to which to _append_ the DDL. If it does not exist,
it will be created.
@param flavour= The type of DDL to create (default=SAS). Supported=TSQL
@param showlog= Set to YES to show the DDL in the log
@param schema= Choose a preferred schema name (default is to use actual schema
@@ -37,9 +41,10 @@
)/*/STORE SOURCE*/;
/* check fileref is assigned */
%if %sysfunc(fileref(&fref)) > 0 %then %do;
filename &fref temp;
%if %mf_existfileref(&fref)=0 %then %do;
filename &fref temp ;
%end;
%if %length(&libref)=0 %then %let libref=WORK;
%let flavour=%upcase(&flavour);
@@ -47,6 +52,7 @@ proc sql noprint;
create table _data_ as
select * from dictionary.tables
where upcase(libname)="%upcase(&libref)"
and memtype='DATA' /* views not currently supported */
%if %length(&ds)>0 %then %do;
and upcase(memname)="%upcase(&ds)"
%end;
@@ -118,7 +124,7 @@ create table _data_ as
%mend addConst;
data _null_;
file &fref;
file &fref mod;
put "/* DDL generated by &sysuserid on %sysfunc(datetime(),datetime19.) */";
run;
@@ -141,13 +147,15 @@ run;
put "create table &libref..&curds(";
end;
else do;
/* just a placeholder - we filter out views at the top */
put "create view &libref..&curds(";
end;
put " "@@;
end;
else put " ,"@@;
if length(format)>1 then fmt=" format="!!cats(format);
if length(label)>1 then lab=" label="!!quote(trim(label));
if length(label)>1 then
lab=" label="!!cats("'",tranwrd(label,"'","''"),"'");
if notnull='yes' then notnul=' not null';
if type='char' then typ=cats('char(',length,')');
else if length ne 8 then typ='num length='!!left(length);
@@ -219,6 +227,7 @@ run;
put "create table [&schema].[&curds](";
end;
else do;
/* just a placeholder - we filter out views at the top */
put "create view [&schema].[&curds](";
end;
put " "@@;
@@ -302,71 +311,81 @@ run;
put "CREATE SCHEMA &schema;";
%do x=1 %to %sysfunc(countw(&dsnlist));
%let curds=%scan(&dsnlist,&x);
data _null_;
file &fref mod;
put "/* Postgres Flavour DDL for &schema..&curds */";
data _null_;
file &fref mod;
set &colinfo (where=(upcase(memname)="&curds")) end=last;
length fmt $32;
if _n_=1 then do;
if memtype='DATA' then do;
put "CREATE TABLE &schema..&curds (";
%local curdsvarcount;
%let curdsvarcount=%mf_getvarcount(&libref..&curds);
%if &curdsvarcount>1600 %then %do;
data _null_;
file &fref mod;
put "/* &libref..&curds contains &curdsvarcount vars */";
put "/* Postgres cannot create tables with over 1600 vars */";
put "/* No DDL will be generated for this table";
run;
%end;
%else %do;
data _null_;
file &fref mod;
put "/* Postgres Flavour DDL for &schema..&curds */";
data _null_;
file &fref mod;
set &colinfo (where=(upcase(memname)="&curds")) end=last;
length fmt $32;
if _n_=1 then do;
if memtype='DATA' then do;
put "CREATE TABLE &schema..&curds (";
end;
else do;
/* just a placeholder - we filter out views at the top */
put "CREATE VIEW &schema..&curds (";
end;
put " "@@;
end;
else do;
put "CREATE VIEW &schema..&curds (";
end;
put " "@@;
end;
else put " ,"@@;
format=upcase(format);
if 1=0 then; /* dummy if */
%if &applydttm=YES %then %do;
else if format=:'DATETIME' then fmt=' TIMESTAMP ';
%end;
else if type='num' then fmt=' DOUBLE PRECISION';
else fmt='VARCHAR('!!cats(length)!!')';
if notnull='yes' then notnul=' NOT NULL';
/* quote column names in case they represent reserved words */
name2=quote(trim(name));
put name2 fmt notnul;
run;
else put " ,"@@;
format=upcase(format);
if 1=0 then; /* dummy if */
%if &applydttm=YES %then %do;
else if format=:'DATETIME' then fmt=' TIMESTAMP ';
%end;
else if type='num' then fmt=' DOUBLE PRECISION';
else fmt='VARCHAR('!!cats(length)!!')';
if notnull='yes' then notnul=' NOT NULL';
/* quote column names in case they represent reserved words */
name2=quote(trim(name));
put name2 fmt notnul;
run;
/* Extra step for data constraints */
%addConst()
/* Extra step for data constraints */
%addConst()
data _null_;
file &fref mod;
put ');';
run;
data _null_;
file &fref mod;
put ');';
run;
/* Create Unique Indexes, but only if they were not already defined within
the Constraints section. */
data _null_;
*length ds $128;
set &idxinfo(
where=(
memname="&curds"
and unique='yes'
and indxname not in (
%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))
/* Create Unique Indexes, but only if they were not already defined within
the Constraints section. */
data _null_;
*length ds $128;
set &idxinfo(
where=(
memname="&curds"
and unique='yes'
and indxname not in (
%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))
)
)
)
);
file &fref mod;
by idxusage indxname;
/* ds=cats(libname,'.',memname); */
if first.indxname then do;
);
file &fref mod;
by idxusage indxname;
if first.indxname then do;
put 'CREATE UNIQUE INDEX "' indxname +(-1) '" ' "ON &schema..&curds(";
put ' "' name +(-1) '"' ;
end;
else put ' ,"' name +(-1) '"';
*else put ' ,' name ;
if last.indxname then do;
put ');';
end;
run;
end;
else put ' ,"' name +(-1) '"';
if last.indxname then do;
put ');';
end;
run;
%end;
%end;
%end;
%if %upcase(&showlog)=YES %then %do;

View File

@@ -20,6 +20,7 @@
@li mf_getvartype.sas
@param [in] libds dataset to hash
@param [in] salt= Provide a salt (could be, for instance, the dataset name)
@param [out] outds= (work.mf_hashdataset) The output dataset to create. This
will contain one column (hashkey) with one observation (a hex32.
representation of the input hash)
@@ -33,7 +34,8 @@
%macro mp_hashdataset(
libds,
outds=
outds=,
salt=
)/*/STORE SOURCE*/;
%if %mf_getattrn(&libds,NLOBS)=0 %then %do;
%put %str(WARN)ING: Dataset &libds is empty;, or is not a dataset;
@@ -53,7 +55,7 @@
%let varlist=%mf_getvarlist(&libds);
data &outds(rename=(&keyvar=hashkey) keep=&keyvar);
length &prevkeyvar &keyvar $32;
retain &prevkeyvar;
retain &prevkeyvar "%sysfunc(md5(%str(&salt)),$hex32.)";
set &libds end=&lastvar;
/* hash should include previous row */
&keyvar=put(md5(&prevkeyvar

104
base/mp_include.sas Normal file
View File

@@ -0,0 +1,104 @@
/**
@file
@brief Performs a wrapped \%include
@details This macro wrapper is necessary if you need your included code to
know that it is being \%included.
If you are using %include in a regular program, you could make use of the
following macro variables:
@li SYSINCLUDEFILEDEVICE
@li SYSINCLUDEFILEDIR
@li SYSINCLUDEFILEFILEREF
@li SYSINCLUDEFILENAME
However these variables are NOT available inside a macro, as documented here:
https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/mcrolref/n1j5tcc0n2xczyn1kg1o0606gsv9.htm
This macro can be used in place of the %include statement, and will insert
the following (equivalent) global variables:
@li _SYSINCLUDEFILEDEVICE
@li _SYSINCLUDEFILEDIR
@li _SYSINCLUDEFILEFILEREF
@li _SYSINCLUDEFILENAME
These can be used whenever testing _within a macro_. Outside of the macro,
the regular automatic variables will still be available (thanks to a
concatenated file list in the include statement).
Example usage:
filename example temp;
data _null_;
file example;
put '%macro test();';
put '%put &=_SYSINCLUDEFILEFILEREF;';
put '%put &=SYSINCLUDEFILEFILEREF;';
put '%mend; %test()';
put '%put &=SYSINCLUDEFILEFILEREF;';
run;
%mp_include(example)
@param [in] fileref The fileref of the file to be included. Must be provided.
@param [in] prefix= (_) The prefix to apply to the global variables.
@param [in] opts= (SOURCE2) The options to apply to the %inc statement
@param [in] 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 scenario, the %mp_abort() macro will test for the existence of
the `_SYSINCLUDEFILEDEVICE` variable and return the outputs (msg,mac) inside
this dataset.
It will then run an abort cancel FILE to stop the include running, and pass
the dataset back.
NOTE - it is NOT possible to read this dataset as part of _this_ macro -
when running abort cancel FILE, ALL macros are closed, so instead it is
necessary to invoke "%mp_abort(mode=INCLUDE)" OUTSIDE of any macro wrappers.
@version 9.4
@author Allan Bowe
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mp_abort.sas
**/
%macro mp_include(fileref
,prefix=_
,opts=SOURCE2
,errds=work.mp_abort_errds
)/*/STORE SOURCE*/;
/* prepare precode */
%local tempref;
%let tempref=%mf_getuniquefileref();
data _null_;
file &tempref;
set sashelp.vextfl(where=(fileref="%upcase(&fileref)"));
put '%let _SYSINCLUDEFILEDEVICE=' xengine ';';
name=scan(xpath,-1,'/\');
put '%let _SYSINCLUDEFILENAME=' name ';';
path=subpad(xpath,1,length(xpath)-length(name)-1);
put '%let _SYSINCLUDEFILEDIR=' path ';';
put '%let _SYSINCLUDEFILEFILEREF=' "&fileref;";
run;
/* prepare the errds */
data &errds;
length msg mac $1000;
iftrue='1=0';
run;
/* include the include */
%inc &tempref &fileref/&opts;
%mp_abort(iftrue= (&syscc ne 0)
,mac=%str(&_SYSINCLUDEFILEDIR/&_SYSINCLUDEFILENAME)
,msg=%str(syscc=&syscc after executing &_SYSINCLUDEFILENAME)
)
filename &tempref clear;
%mend mp_include;

View File

@@ -4,7 +4,7 @@
@details PROC JSON is faster but will produce errs like the ones below if
special chars are encountered.
> ERROR: Some code points did not transcode.
> (ERR)OR: Some code points did not transcode.
> An object or array close is not valid at this point in the JSON text.

77
base/mp_lib2inserts.sas Normal file
View File

@@ -0,0 +1,77 @@
/**
@file
@brief Convert all data in a library to SQL insert statements
@details Gets list of members then calls the <code>%mp_ds2inserts()</code>
macro.
Usage:
%mp_getddl(sashelp, schema=work, fref=tempref)
%mp_lib2inserts(sashelp, schema=work, outref=tempref)
%inc tempref;
The output will be one file in the outref fileref.
<h4> SAS Macros </h4>
@li mp_ds2inserts.sas
@param [in] lib Library in which to convert all datasets to inserts
@param [in] flavour= (SAS) The SQL flavour to be applied to the output. Valid
options:
@li SAS (default) - suitable for regular proc sql
@li PGSQL - Used for Postgres databases
@param [in] maxobs= (max) The max number of observations (per table) to create
@param [out] outref= Output fileref in which to create the insert statements.
If it exists, it will be appended to, otherwise it will be created.
@param [out] schema= (0) The schema of the target database, or the libref.
@param [in] applydttm= (YES) If YES, any columns using datetime formats will
be converted to native DB datetime literals
@version 9.2
@author Allan Bowe
**/
%macro mp_lib2inserts(lib
,flavour=SAS
,outref=0
,schema=0
,maxobs=max
,applydttm=YES
)/*/STORE SOURCE*/;
/* Find the tables */
%local x ds memlist;
proc sql noprint;
select distinct lowcase(memname)
into: memlist
separated by ' '
from dictionary.tables
where upcase(libname)="%upcase(&lib)"
and memtype='DATA'; /* exclude views */
%let flavour=%upcase(&flavour);
%if &flavour ne SAS and &flavour ne PGSQL %then %do;
%put %str(WAR)NING: &flavour is not supported;
%return;
%end;
/* create the inserts */
%do x=1 %to %sysfunc(countw(&memlist));
%let ds=%scan(&memlist,&x);
%mp_ds2inserts(&lib..&ds
,outref=&outref
,schema=&schema
,outds=&ds
,flavour=&flavour
,maxobs=&maxobs
,applydttm=&applydttm
)
%end;
%mend mp_lib2inserts;

58
base/mp_webin.sas Normal file
View File

@@ -0,0 +1,58 @@
/**
@file
@brief Fix the `_WEBIN` variables provided to SAS web services
@details When uploading files to SAS Stored Processes or Viya Jobs a number
of global macro variables are automatically created - however there are some
differences in behaviour both between SAS 9 and Viya, and also between a
single file upload and a multi-file upload.
This macro "straightens" up the global macro variables to make it easier /
simpler to write code that works in both environments and with a variable
number of file inputs.
After running this macro, the following global variables will *always* exist:
@li `_WEBIN_FILE_COUNT`
@li `_WEBIN_FILENAME1`
@li `_WEBIN_FILEREF1`
@li `_WEBIN_NAME1`
Usage:
%mp_webin()
This was created as a macro procedure (over a macro function) as it will also
use the filename statement in Viya environments (where `_webin_fileuri` is
provided).
<h4> SAS Macros </h4>
@li mf_getplatform.sas
**/
%macro mp_webin();
/* prepare global variables */
%global _webin_file_count
_webin_filename _webin_filename1
_webin_fileref _webin_fileref1
_webin_fileuri _webin_fileuri1
_webin_name _webin_name1
;
/* create initial versions */
%let _webin_file_count=%eval(&_webin_file_count+0);
%let _webin_filename1=%sysfunc(coalescec(&_webin_filename1,&_webin_filename));
%let _webin_fileref1=%sysfunc(coalescec(&_webin_fileref1,&_webin_fileref));
%let _webin_fileuri1=%sysfunc(coalescec(&_webin_fileuri1,&_webin_fileuri));
%let _webin_name1=%sysfunc(coalescec(&_webin_name1,&_webin_name));
/* If Viya, create temporary fileref(s) */
%local i;
%if %mf_getplatform()=SASVIYA %then %do i=1 %to &_webin_file_count;
%let _webin_fileref&i=%mf_getuniquefileref(prefix=0);
filename &&_webin_fileref&i filesrvc "&&_webin_fileuri&i";
%end;
%mend mp_webin;

View File

@@ -84,7 +84,7 @@ options noquotelenmax;
"""
f = open('all.sas', "w") # r / r+ / rb / rb+ / w / wb
f.write(header)
folders=['base','meta','metax','viya','lua']
folders=['base','meta','metax','viya','lua','fcmp']
for folder in folders:
filenames = [fn for fn in Path('./' + folder).iterdir() if fn.match("*.sas")]
filenames.sort()

View File

@@ -0,0 +1,94 @@
/**
@file
@brief Provides a replacement for the stpsrv_header function
@details The stpsrv_header is normally a built-in function, used to set the
headers for SAS 9 Stored Processes as documented here:
https://go.documentation.sas.com/doc/en/itechcdc/9.4/stpug/srvhead.htm
The purpose of this custom function is to provide a replacement when running
similar code as a web service against
[sasjs/server](https://github.com/sasjs/server). It operates by creating a
text file with the headers. The location of this text file is determined by
a macro variable (`sasjs_stpsrv_header_loc`) which needs to be injected into
each service by the calling process, eg:
%let sasjs_stpsrv_header_loc = C:/temp/some_uuid/stpsrv_header.txt;
Note - the function works by appending headers to the file. If multiple same-
named headers are provided, they will all be appended - the calling process
needs to pick up the last one. This will mean removing the attribute if the
final record has an empty value.
The function takes the following (positional) parameters:
| PARAMETER | DESCRIPTION |
|------------|-------------|
| name $ | name of the header attribute to create|
| value $ | value of the header attribute|
It returns 0 if successful, or -1 if an error occured.
Usage:
%let sasjs_stpsrv_header_loc=%sysfunc(pathname(work))/stpsrv_header.txt;
%mcf_stpsrv_header(wrap=YES, insert_cmplib=YES)
data _null_;
rc=stpsrv_header('Content-type','application/text');
rc=stpsrv_header('Content-disposition',"attachment; filename=file.txt");
run;
data _null_;
infile "&sasjs_stpsrv_header_loc";
input;
putlog _infile_;
run;
@param [out] wrap= (NO) Choose YES to add the proc fcmp wrapper.
@param [out] insert_cmplib= (NO) Choose YES to insert the package into the
CMPLIB reference.
@param [out] lib= (work) The output library in which to create the catalog.
@param [out] cat= (sasjs) The output catalog in which to create the package.
@param [out] pkg= (utils) The output package in which to create the function.
Uses a 3 part format: libref.catalog.package
**/
%macro mcf_stpsrv_header(wrap=NO
,insert_cmplib=NO
,lib=WORK
,cat=SASJS
,pkg=UTILS
)/*/STORE SOURCE*/;
%if &wrap=YES %then %do;
proc fcmp outcat=&lib..&cat..&pkg;
%end;
function stpsrv_header(name $, value $);
length loc $128 val $512;
loc=symget('sasjs_stpsrv_header_loc');
val=trim(name)!!': '!!value;
length fref $8;
rc=filename(fref,loc);
if (rc ne 0) then return( -1 );
fid = fopen(fref,'a');
if (fid = 0) then return( -1 );
rc=fput(fid, val);
rc=fwrite(fid);
rc=fclose(fid);
rc=filename(fref);
return(0);
endsub;
%if &wrap=YES %then %do;
quit;
%end;
%if &insert_cmplib=YES %then %do;
options insert=(CMPLIB=(&lib..&cat));
%end;
%mend mcf_stpsrv_header;

79
fcmp/mcf_string2file.sas Normal file
View File

@@ -0,0 +1,79 @@
/**
@file
@brief Adds a string to a file
@details Creates an fcmp function for appending a string to an external file.
If the file does not exist, it is created.
The function itself takes the following (positional) parameters:
| PARAMETER | DESCRIPTION |
|------------|-------------|
| filepath $ | full path to the file|
| string $ | string to add to the file |
| mode $ | mode of the output - either APPEND (default) or CREATE |
It returns 0 if successful, or -1 if an error occured.
Usage:
%mcf_string2file(wrap=YES, insert_cmplib=YES)
data _null_;
rc=mcf_string2file(
"%sysfunc(pathname(work))/newfile.txt"
, "This is a test"
, "CREATE");
run;
data _null_;
infile "%sysfunc(pathname(work))/newfile.txt";
input;
putlog _infile_;
run;
@param [out] wrap= (NO) Choose YES to add the proc fcmp wrapper.
@param [out] insert_cmplib= (NO) Choose YES to insert the package into the
CMPLIB reference.
@param [out] lib= (work) The output library in which to create the catalog.
@param [out] cat= (sasjs) The output catalog in which to create the package.
@param [out] pkg= (utils) The output package in which to create the function.
Uses a 3 part format: libref.catalog.package
**/
%macro mcf_string2file(wrap=NO
,insert_cmplib=NO
,lib=WORK
,cat=SASJS
,pkg=UTILS
)/*/STORE SOURCE*/;
%if &wrap=YES %then %do;
proc fcmp outcat=&lib..&cat..&pkg;
%end;
function mcf_string2file(filepath $, string $, mode $);
if mode='APPEND' then fmode='a';
else fmode='o';
length fref $8;
rc=filename(fref,filepath);
if (rc ne 0) then return( -1 );
fid = fopen(fref,fmode);
if (fid = 0) then return( -1 );
rc=fput(fid, string);
rc=fwrite(fid);
rc=fclose(fid);
rc=filename(fref);
return(0);
endsub;
%if &wrap=YES %then %do;
quit;
%end;
%if &insert_cmplib=YES %then %do;
options insert=(CMPLIB=(&lib..&cat));
%end;
%mend mcf_string2file;

View File

@@ -20,6 +20,19 @@
*/
/*! \dir fcmp
* \brief Macros for generating FCMP functions
* \details These macros have the following attributes:
* Macro name matches compiled function / subroutine name
* Prefixes: _mcf_, _mcs_
The macro part is just a wrapper for the underlying function / subroutine,
and has switches for including the proc fcmp / quit statements and whether
to insert the package into the CMPLIB option.
*/
/*! \dir meta
* \brief Metadata Aware Macros
* \details These macros have the following attributes:

View File

@@ -14,10 +14,10 @@
usage:
%mm_createfolder(path=/some/meta/folder)
%mm_createfolder(path=/some/meta/folder)
@param path= Name of the folder to create.
@param mdebug= set DBG to 1 to disable DEBUG messages
@param [in] path= Name of the folder to create.
@param [in] mdebug= set DBG to 1 to disable DEBUG messages
@version 9.4
@author Allan Bowe

View File

@@ -1,18 +1,20 @@
/**
@file
@brief Creates a dataset with all metadata tables for a particular library
@details Will only show the tables to which a user has the requisite
metadata access.
@details Will only show the tables for which the executing user has the
requisite metadata access.
usage:
%mm_gettables(uri=A5X8AHW1.B40001S5)
%mm_gettables(uri=A5X8AHW1.B40001S5)
@param outds the dataset to create that contains the list of tables
@param uri the uri of the library for which to return tables
@param getauth= YES|NO - fetch the authdomain used in database connections.
Set to NO to improve runtimes in larger environments, as there can be a
performance hit on the `metadata_getattr(domainuri, "Name", AuthDomain)` call.
@param [in] uri= the uri of the library for which to return tables
@param [out] outds= (work.mm_gettables) the dataset to contain the list of
tables
@param [in] getauth= (YES) Fetch the authdomain used in database connections.
Set to NO to improve runtimes in larger environments, as there can be a
performance hit on the `metadata_getattr(domainuri, "Name", AuthDomain)`
call.
@returns outds dataset containing all groups in a column named "metagroup"
(defaults to work.mm_getlibs). The following columns are provided:
@@ -40,8 +42,8 @@ data &outds;
libdesc $200 libref engine $8 IsDBMSLibname $1
tablename $50 /* metadata table names can be longer than $32 */
;
keep libname libdesc libref engine ServerContext path_schema AuthDomain tableuri
tablename IsPreassigned IsDBMSLibname id;
keep libname libdesc libref engine ServerContext path_schema AuthDomain
tableuri tablename IsPreassigned IsDBMSLibname id;
call missing (of _all_);
uri=symget('uri');

695
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -27,12 +27,12 @@
"main": "index.js",
"scripts": {
"build": "sasjs cbd -t viya",
"docs": "sasjs doc && ./sasjs/utils/build.sh",
"docs": "sasjs doc -t docsonly && ./sasjs/utils/build.sh",
"test": "sasjs test -t viya",
"lint": "sasjs lint",
"prepare": "git rev-parse --git-dir && git config core.hooksPath ./.git-hooks || true"
},
"devDependencies": {
"@sasjs/cli": "2.33.3"
"@sasjs/cli": "^2.37.8"
}
}

View File

@@ -2,11 +2,12 @@
"$schema": "https://cli.sasjs.io/sasjsconfig-schema.json",
"macroFolders": [
"base",
"fcmp",
"meta",
"metax",
"viya",
"lua",
"tests/base"
"tests/crossplatform"
],
"docConfig": {
"displayMacroCore": false,
@@ -33,7 +34,7 @@
"allowInsecureRequests": false,
"appLoc": "/Public/temp/macrocore",
"macroFolders": [
"tests/viya"
"tests/viyaonly"
],
"programFolders": [],
"deployConfig": {
@@ -48,7 +49,15 @@
"serverType": "SAS9",
"appLoc": "/Shared Data/temp/macrocore",
"macroFolders": [
"tests/meta"
"tests/sas9only"
]
},
{
"name": "docsonly",
"serverType": "SAS9",
"macroFolders": [
"tests/sas9only",
"tests/viyaonly"
]
}
]

View File

@@ -0,0 +1,39 @@
/**
@file
@brief Testing mcf_stpsrv_header macro
<h4> SAS Macros </h4>
@li mcf_stpsrv_header.sas
@li mp_assert.sas
**/
%let sasjs_stpsrv_header_loc=%sysfunc(pathname(work))/stpsrv_header.txt;
%mcf_stpsrv_header(wrap=YES, insert_cmplib=YES)
data _null_;
rc=stpsrv_header('Content-type','application/text');
rc=stpsrv_header('Content-disposition',"attachment; filename=file.txt");
run;
%let test1=FAIL;
%let test2=FAIL;
data _null_;
infile "&sasjs_stpsrv_header_loc";
input;
if _n_=1 and _infile_='Content-type: application/text'
then call symputx('test1','PASS');
else if _n_=2 & _infile_='Content-disposition: attachment; filename=file.txt'
then call symputx('test2','PASS');
run;
%mp_assert(
iftrue=(%str(&test1)=%str(PASS)),
desc=Check first header line
)
%mp_assert(
iftrue=(%str(&test2)=%str(PASS)),
desc=Check second header line
)

View File

@@ -0,0 +1,52 @@
/**
@file
@brief Testing mcf_string2file macro
<h4> SAS Macros </h4>
@li mcf_string2file.sas
@li mp_assert.sas
**/
%mcf_string2file(wrap=YES, insert_cmplib=YES)
data _null_;
rc=mcf_string2file(
"%sysfunc(pathname(work))/newfile.txt"
, "line1"
, "APPEND");
rc=mcf_string2file(
"%sysfunc(pathname(work))/newfile.txt"
, "line2"
, "APPEND");
run;
data _null_;
infile "%sysfunc(pathname(work))/newfile.txt";
input;
if _n_=2 then call symputx('val',_infile_);
run;
%mp_assert(
iftrue=(%str(&val)=%str(line2)),
desc=Check if APPEND works
)
data _null_;
rc=mcf_string2file(
"%sysfunc(pathname(work))/newfile.txt"
, "creating"
, "CREATE");
run;
data _null_;
infile "%sysfunc(pathname(work))/newfile.txt";
input;
if _n_=1 then call symputx('val2',_infile_);
run;
%mp_assert(
iftrue=(%str(&val2)=%str(creating)),
desc=Check if CREATE works
)

View File

@@ -0,0 +1,35 @@
/**
@file
@brief Testing mf_existfileref macro
<h4> SAS Macros </h4>
@li mf_existfileref.sas
@li mp_assert.sas
**/
filename ref1 temp;
filename ref2 temp;
data _null_;
file ref1;
put 'exists';
run;
%mp_assert(
iftrue=(%mf_existfileref(ref1)=1),
desc=Checking fileref WITH target file exists,
outds=work.test_results
)
%mp_assert(
iftrue=(%mf_existfileref(ref2)=1),
desc=Checking fileref WITHOUT target file exists,
outds=work.test_results
)
%mp_assert(
iftrue=(%mf_existfileref(ref3)=0),
desc=Checking non-existant fref does not exist,
outds=work.test_results
)

View File

@@ -0,0 +1,22 @@
/**
@file
@brief Testing mf_existfunction macro
<h4> SAS Macros </h4>
@li mf_existfunction.sas
@li mp_assert.sas
**/
%mp_assert(
iftrue=(%mf_existfunction(CAT)=1),
desc=Checking if CAT function exists,
outds=work.test_results
)
%mp_assert(
iftrue=(%mf_existfunction(DOG)=0),
desc=Checking DOG function does not exist,
outds=work.test_results
)

View File

@@ -0,0 +1,49 @@
/**
@file
@brief Testing mf_getapploc macro
<h4> SAS Macros </h4>
@li mf_getapploc.sas
@li mp_assert.sas
**/
%mp_assert(
iftrue=(
"%mf_getapploc(/some/loc/tests/services/x/service)"="/some/loc"
),
desc=Checking test appLoc matches,
outds=work.test_results
)
%mp_assert(
iftrue=(
"%mf_getapploc(/some/loc/tests/services/tests/service)"="/some/loc"
),
desc=Checking nested services appLoc matches,
outds=work.test_results
)
%mp_assert(
iftrue=(
"%mf_getapploc(/some/area/services/admin/service)"="/some/area"
),
desc=Checking services appLoc matches,
outds=work.test_results
)
%mp_assert(
iftrue=(
"%mf_getapploc(/some/area/jobs/jobs/job)"="/some/area"
),
desc=Checking jobs appLoc matches,
outds=work.test_results
)
%mp_assert(
iftrue=(
"%mf_getapploc(/some/area/tests/macros/somemacro.sas)"="/some/area"
),
desc=Checking tests/macros appLoc matches (which has no subfolder),
outds=work.test_results
)

View File

@@ -0,0 +1,36 @@
/**
@file
@brief Testing mf_getuniquefileref macro
@details To test performance you can also use the following macro:
%macro x(prefix);
%let now=%sysfunc(datetime());
%do x=1 %to 1000;
%let rc=%mf_getuniquefileref(prefix=&prefix);
%end;
%put %sysevalf(%sysfunc(datetime())-&now);
%mend;
%x(_)
%x(0)
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mp_assert.sas
**/
%mp_assert(
iftrue=(
"%substr(%mf_getuniquefileref(prefix=0),1,1)"="#"
),
desc=Checking for a natively assigned fileref,
outds=work.test_results
)
%mp_assert(
iftrue=(
"%substr(%mf_getuniquefileref(),1,1)"="_"
),
desc=Checking for a default fileref,
outds=work.test_results
)

View File

@@ -0,0 +1,99 @@
/**
@file
@brief Testing mp_binarycopy.sas macro
<h4> SAS Macros </h4>
@li mp_binarycopy.sas
@li mp_assert.sas
**/
/* TEST 1 - regular file copy */
%let string1=test1;
filename tmp temp;
filename myref temp;
data _null_;
file tmp;
put "&string1";
run;
%mp_binarycopy(inref=tmp, outref=myref)
data _null_;
infile myref;
input;
put _infile_;
call symputx('string1_check',_infile_);
stop;
run;
%mp_assert(
iftrue=("&string1"="&string1_check"),
desc=Basic String Compare,
outds=work.test_results
)
/* TEST 2 - File append */
%let string2=test2;
%let path2=%sysfunc(pathname(work))/somefile.txt;
data _null_;
file "&path2";
put "&string2";
run;
%mp_binarycopy(inloc="&path2", outref=myref, mode=APPEND)
data _null_;
infile myref;
input;
put _infile_;
if _n_=2 then call symputx('string2_check',_infile_);
run;
%mp_assert(
iftrue=("&string2"="&string2_check"),
desc=Append Check (file to ref),
outds=work.test_results
)
/* TEST 3 - File create (ref to existing file) */
%let string3=test3;
%let path3=%sysfunc(pathname(work))/somefile3.txt;
filename tmp3 temp;
data _null_;
file tmp3;
put "&string3";
run;
data _null_;
file "&path3";
put "this should not be returned";
run;
%mp_binarycopy(inref=tmp3, outloc="&path3")
data _null_;
infile "&path3";
input;
put _infile_;
if _n_=1 then call symputx('string3_check',_infile_);
run;
%mp_assert(
iftrue=("&string3"="&string3_check"),
desc=Append Check (ref to existing file),
outds=work.test_results
)
/* TEST 4 - File append (ref to file) */
%let string4=test4;
%let string4_check=;
filename tmp4 temp;
data _null_;
file tmp4;
put "&string4";
run;
%mp_binarycopy(inref=tmp4, outloc="&path3",mode=APPEND)
data _null_;
infile "&path3";
input;
put _infile_;
if _n_=2 then call symputx('string4_check',_infile_);
run;
%mp_assert(
iftrue=("&string4"="&string4_check"),
desc=Append Check (ref to file),
outds=work.test_results
)

View File

@@ -0,0 +1,31 @@
/**
@file
@brief Testing mp_ds2inserts.sas macro
<h4> SAS Macros </h4>
@li mp_ds2inserts.sas
@li mp_assert.sas
**/
/**
* test 1 - rebuild an existing dataset
* Cars is a great dataset - it contains leading spaces, and formatted numerics
*/
%mp_ds2inserts(sashelp.cars,outref=testref,schema=work,outds=test)
data work.test;
set sashelp.cars;
stop;
proc sql;
%inc testref;
proc compare base=sashelp.cars compare=work.test;
quit;
%mp_assert(
iftrue=(&sysinfo=1),
desc=sashelp.cars is identical except for ds label,
outds=work.test_results
)

View File

@@ -0,0 +1,23 @@
/**
@file
@brief Testing mp_getddl.sas macro
<h4> SAS Macros </h4>
@li mp_getddl.sas
@li mp_assert.sas
**/
data test(index=(pk=(x y)/unique /nomiss));
x=1;
y='blah';
label x='blah';
run;
proc sql; describe table &syslast;
%mp_getddl(work,test,flavour=tsql,showlog=YES)
%mp_assert(
iftrue=(&syscc=0),
desc=mp_getddl runs without errors,
outds=work.test_results
)

View File

@@ -0,0 +1,42 @@
/**
@file
@brief Testing mp_ds2inserts.sas macro
<h4> SAS Macros </h4>
@li mf_mkdir.sas
@li mp_getddl.sas
@li mp_lib2inserts.sas
@li mp_assert.sas
**/
/* grab 20 datasets from SASHELP */
%let path=%sysfunc(pathname(work));
%mf_mkdir(&path)
libname sashlp "&path";
proc sql noprint;
create table members as
select distinct lowcase(memname) as memname
from dictionary.tables
where upcase(libname)="SASHELP"
and memtype='DATA'; /* exclude views */
data _null_;
set work.members;
call execute(cats('data sashlp.',memname,';set sashelp.',memname,';run;'));
if _n_>20 then stop;
run;
/* export DDL and inserts */
%mp_getddl(sashlp, schema=work, fref=tempref)
%mp_lib2inserts(sashlp, schema=work, outref=tempref,maxobs=50)
/* check if it actually runs */
options source2;
%inc tempref;
/* without errors.. */
%mp_assert(
iftrue=(&syscc=0),
desc=Able to export 20 tables from sashelp using mp_lib2inserts,
outds=work.test_results
)

View File

@@ -0,0 +1,39 @@
/**
@file
@brief Testing mp_webin macro
<h4> SAS Macros </h4>
@li mp_webin.sas
@li mp_assert.sas
**/
/* force SAS9 tests as we don't have a valid URI available */
%macro mf_getplatform();
SAS9
%mend mf_getplatform;
/* TEST 1 */
%let _webin_file_count=1;
%let _webin_filename=test;
%mp_webin()
%mp_assert(
iftrue=(
%symexist(_WEBIN_FILEREF1)
),
desc=Checking if the macvar exists,
outds=work.test_results
)
/* TEST 2 */
%global _WEBIN_FILENAME1;
%mp_assert(
iftrue=(
%str(&_WEBIN_FILENAME1)=%str(test)
),
desc=Checking if the macvar exists,
outds=work.test_results
)

View File

@@ -0,0 +1,24 @@
/**
@file
@brief Testing mv_getfoldermembers macro
@details Testing is performed by ensuring that the tests/macros folder
contains more than 20 results (which also means the default was increased)
<h4> SAS Macros </h4>
@li mf_getapploc.sas
@li mp_assertdsobs.sas
@li mv_getfoldermembers.sas
**/
options mprint;
%mv_getfoldermembers(
root=%mf_getapploc()/tests/macros,
outds=work.results
)
%mp_assertdsobs(work.results,
desc=%str(Ensuring over 20 results found in %mf_getapploc()/tests/macros),
test=ATLEAST 21,
outds=work.test_results
)

View File

@@ -1,22 +1,25 @@
/**
@file mv_getfoldermembers.sas
@brief Gets a list of folders (and ids) for a given root
@details Works for both root level and below, oauth or password. Default is
oauth, and the token is expected in a global ACCESS_TOKEN variable.
@file
@brief Gets a list of folder members (and ids) for a given root
@details Returns all members for a particular Viya folder. Works at both root
level and below, and results are created in an output dataset.
%mv_getfoldermembers(root=/Public)
%mv_getfoldermembers(root=/Public, outds=work.mymembers)
@param root= The path for which to return the list of folders
@param outds= The output dataset to create (default is work.mv_getfolders). Format:
@param [in] root= (/) The path for which to return the list of folders
@param [out] outds= (work.mv_getfolders) The output dataset to create. Format:
|ordinal_root|ordinal_items|creationTimeStamp| modifiedTimeStamp|createdBy|modifiedBy|id| uri|added| type|name|description|
|---|---|---|---|---|---|---|---|---|---|---|---|
|1|1|2021-05-25T11:15:04.204Z|2021-05-25T11:15:04.204Z|allbow|allbow|4f1e3945-9655-462b-90f2-c31534b3ca47|/folders/folders/ed701ff3-77e8-468d-a4f5-8c43dec0fd9e|2021-05-25T11:15:04.212Z|child|my_folder_name|My folder Description|
@param access_token_var= The global macro variable to contain the access token
@param grant_type= valid values are "password" or "authorization_code" (unquoted).
The default is authorization_code.
@param [in] access_token_var= (ACCESS_TOKEN) The global macro variable to
contain the access token
@param [in] grant_type= (sas_services) Valid values are:
@li password
@li authorization_code
@li detect
@li sas_services
@version VIYA V.03.04
@author Allan Bowe, source: https://github.com/sasjs/core
@@ -28,6 +31,12 @@
@li mf_getuniquelibref.sas
@li mf_isblank.sas
<h4> Related Macros </h4>
@li mv_createfolder.sas
@li mv_deletefoldermember.sas
@li mv_deleteviyafolder.sas
@li mv_getfoldermembers.test.sas
**/
%macro mv_getfoldermembers(root=/
@@ -67,7 +76,7 @@ options noquotelenmax;
%if "&root"="/" %then %do;
/* if root just list root folders */
proc http method='GET' out=&fname1 &oauth_bearer
url="&base_uri/folders/rootFolders";
url="&base_uri/folders/rootFolders?limit=1000";
%if &grant_type=authorization_code %then %do;
headers "Authorization"="Bearer &&&access_token_var";
%end;
@@ -88,13 +97,17 @@ options noquotelenmax;
/*data _null_;infile &fname1;input;putlog _infile_;run;*/
libname &libref1 JSON fileref=&fname1;
/* now get the followon link to list members */
%local href;
%let href=0;
%local href cnt;
%let cnt=0;
data _null_;
set &libref1..links;
if rel='members' then call symputx('href',quote("&base_uri"!!trim(href)),'l');
if rel='members' then do;
url=cats("'","&base_uri",href,"?limit=10000'");
call symputx('href',url,'l');
call symputx('cnt',1,'l');
end;
run;
%if &href=0 %then %do;
%if &cnt=0 %then %do;
%put NOTE:;%put NOTE- No members found in &root!!;%put NOTE-;
%return;
%end;