mirror of
https://github.com/sasjs/core.git
synced 2025-12-12 15:04:36 +00:00
Compare commits
84 Commits
sasjs-cli-
...
v2.48.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74790ec80e | ||
|
|
afd8a754b4 | ||
|
|
bc1f7b3baa | ||
|
|
51690e68dc | ||
|
|
0fa076cb73 | ||
|
|
6506993704 | ||
|
|
a69db2ebfb | ||
|
|
d72ca7cb24 | ||
|
|
52dfa7b8f7 | ||
|
|
dae03c5730 | ||
|
|
14efe5d3fd | ||
|
|
653244d737 | ||
|
|
086831b3f5 | ||
|
|
6eca585fc1 | ||
|
|
f6ba36fc28 | ||
|
|
7406288d79 | ||
|
|
2e7fcbe5b8 | ||
|
|
3e7b9f8c14 | ||
|
|
e9189ccc06 | ||
|
|
8c00d715c2 | ||
|
|
d47a369cdf | ||
|
|
52bf6019fd | ||
|
|
25e61fd8ef | ||
|
|
3038be83a0 | ||
|
|
6e2447c70a | ||
|
|
486aba84ca | ||
|
|
b5944181e1 | ||
|
|
3f69cf506a | ||
|
|
6013897c50 | ||
|
|
27cf2a2532 | ||
|
|
d096cbddeb | ||
|
|
117503f214 | ||
|
|
5cc5fae750 | ||
|
|
d93032e1a9 | ||
|
|
507557b2cb | ||
|
|
ee5c3c185a | ||
|
|
e5592a2eb2 | ||
|
|
59200a6e73 | ||
|
|
f468f60ae1 | ||
|
|
9f60d827b6 | ||
|
|
5c936ddb65 | ||
|
|
d0bde62594 | ||
|
|
ada9192337 | ||
|
|
6161f588a9 | ||
|
|
67079d8c17 | ||
|
|
75bd39adb0 | ||
|
|
078bdbeecf | ||
|
|
8ddb86785c | ||
|
|
005af0ecf8 | ||
|
|
bc410a9135 | ||
|
|
fc8ba2e36c | ||
|
|
756441384a | ||
|
|
10f9eecf9e | ||
| 470ebb50a7 | |||
|
|
26cd5d9d31 | ||
|
|
0b694bb878 | ||
|
|
b403c02bba | ||
|
|
0b555bb31c | ||
|
|
40b513a9e3 | ||
|
|
4eacf4deae | ||
|
|
5824423c13 | ||
|
|
ce5bfd41dc | ||
|
|
0c67a07e42 | ||
|
|
187504600a | ||
|
|
658d67feaa | ||
|
|
5207a77591 | ||
|
|
4456adf1dc | ||
|
|
03962c2a50 | ||
|
|
6d2fc7e265 | ||
|
|
39b2e7c5f9 | ||
|
|
f99adf5c3e | ||
|
|
69f8e91a2d | ||
|
|
5b5d01993f | ||
|
|
00fa464a7c | ||
|
|
a5baf46233 | ||
|
|
d63d2a4ec1 | ||
|
|
900f694065 | ||
|
|
838324c15e | ||
|
|
e3205ec06c | ||
|
|
154a33434e | ||
|
|
bfa1bbaeb1 | ||
|
|
1f0128aec4 | ||
|
|
69f03f4e14 | ||
|
|
a932f321d8 |
104
.all-contributorsrc
Normal file
104
.all-contributorsrc
Normal 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
|
||||||
|
}
|
||||||
2
.github/workflows/run-tests.yml
vendored
2
.github/workflows/run-tests.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
|||||||
sudo apt install apt-transport-https
|
sudo apt install apt-transport-https
|
||||||
sudo wget https://swupdate.openvpn.net/repos/openvpn-repo-pkg-key.pub
|
sudo wget https://swupdate.openvpn.net/repos/openvpn-repo-pkg-key.pub
|
||||||
sudo apt-key add 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 update
|
||||||
sudo apt install openvpn3
|
sudo apt install openvpn3
|
||||||
|
|
||||||
|
|||||||
40
README.md
40
README.md
@@ -41,6 +41,13 @@ Documentation: https://core.sasjs.io
|
|||||||
- No X command
|
- No X command
|
||||||
- Prefixes: _mf_, _mp_
|
- 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)
|
**meta** library (SAS9 only)
|
||||||
|
|
||||||
- OS independent
|
- OS independent
|
||||||
@@ -84,7 +91,7 @@ run;
|
|||||||
|
|
||||||
# Installation
|
# 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
|
```sas
|
||||||
options insert=(sasautos="/your/path/macrocore/base");
|
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 -->
|
||||||
|
[](#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!
|
||||||
|
|||||||
@@ -17,7 +17,13 @@
|
|||||||
%macro mf_existfileref(fref
|
%macro mf_existfileref(fref
|
||||||
)/*/STORE SOURCE*/;
|
)/*/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
|
1
|
||||||
%end;
|
%end;
|
||||||
%else %do;
|
%else %do;
|
||||||
|
|||||||
37
base/mf_existfunction.sas
Normal file
37
base/mf_existfunction.sas
Normal 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
73
base/mf_getapploc.sas
Normal 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 ;
|
||||||
@@ -1,37 +1,58 @@
|
|||||||
/**
|
/**
|
||||||
@file
|
@file
|
||||||
@brief Assigns and returns an unused fileref
|
@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:
|
Use as follows:
|
||||||
|
|
||||||
%let fileref1=%mf_getuniquefileref();
|
%let fileref1=%mf_getuniquefileref();
|
||||||
%let fileref2=%mf_getuniquefileref();
|
%let fileref2=%mf_getuniquefileref(prefix=0);
|
||||||
%put &fileref1 &fileref2;
|
%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
|
@param [in] prefix= (_) first part of fileref. Remember that filerefs can only
|
||||||
characters, so a 7 letter prefix would mean that `maxtries` should be 10.
|
be 8 characters, so a 7 letter prefix would mean `maxtries` should be 10.
|
||||||
@param maxtries= the last part of the libref. Provide an integer value.
|
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
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
**/
|
**/
|
||||||
|
|
||||||
%macro mf_getuniquefileref(prefix=mcref,maxtries=1000);
|
%macro mf_getuniquefileref(prefix=_,maxtries=1000);
|
||||||
%local x fname;
|
%local rc fname;
|
||||||
%let x=0;
|
%if &prefix=0 %then %do;
|
||||||
%do x=0 %to &maxtries;
|
|
||||||
%if %sysfunc(fileref(&prefix&x)) > 0 %then %do;
|
|
||||||
%let fname=&prefix&x;
|
|
||||||
%let rc=%sysfunc(filename(fname,,temp));
|
%let rc=%sysfunc(filename(fname,,temp));
|
||||||
%if &rc %then %put %sysfunc(sysmsg());
|
%if &rc %then %put %sysfunc(sysmsg());
|
||||||
&prefix&x
|
&fname
|
||||||
%*put &sysmacroname: Fileref &prefix&x was assigned and returned;
|
|
||||||
%return;
|
|
||||||
%end;
|
%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;
|
%end;
|
||||||
%put unable to find available fileref in range &prefix.0-&maxtries;
|
|
||||||
%mend mf_getuniquefileref;
|
%mend mf_getuniquefileref;
|
||||||
@@ -15,24 +15,47 @@
|
|||||||
recognise this and fetch the log of the parent session instead)
|
recognise this and fetch the log of the parent session instead)
|
||||||
@li STP environments must finish cleanly to avoid the log being sent to
|
@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)
|
_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
|
and set SYSCC=0. We take a unique "soft abort" approach - we open a macro
|
||||||
but don't close it! This provides a graceful abort, EXCEPT when called
|
but don't close it! This works everywhere EXCEPT inside a \%include inside
|
||||||
called within a %include within a macro (and that macro contains additional
|
a macro. For that, we recommend you use mp_include.sas to perform the
|
||||||
logic). See mp_abort.test.nofix.sas for the example case.
|
include, and then call \%mp_abort(mode=INCLUDE) from the source program (ie,
|
||||||
If you know of another way to gracefully abort a 9.4m3 STP session, we'd
|
OUTSIDE of the top-parent macro).
|
||||||
love to hear about it!
|
|
||||||
|
|
||||||
|
|
||||||
@param mac= to contain the name of the calling macro
|
@param mac= to contain the name of the calling macro
|
||||||
@param msg= message to be returned
|
@param msg= message to be returned
|
||||||
@param iftrue= supply a condition under which the macro should be executed.
|
@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
|
@author Allan Bowe
|
||||||
@cond
|
@cond
|
||||||
**/
|
**/
|
||||||
|
|
||||||
%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)
|
%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)
|
||||||
|
, errds=work.mp_abort_errds
|
||||||
|
, mode=REGULAR
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
%global sysprocessmode sysprocessname;
|
%global sysprocessmode sysprocessname;
|
||||||
@@ -43,9 +66,44 @@
|
|||||||
%if %length(&mac)>0 %then %put NOTE- called by &mac;
|
%if %length(&mac)>0 %then %put NOTE- called by &mac;
|
||||||
%put NOTE - &msg;
|
%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 */
|
/* 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;
|
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 */
|
/* extract log errs / warns, if exist */
|
||||||
%local logloc logline;
|
%local logloc logline;
|
||||||
%global logmsg; /* capture global messages */
|
%global logmsg; /* capture global messages */
|
||||||
@@ -100,7 +158,7 @@
|
|||||||
/* send response in SASjs JSON format */
|
/* send response in SASjs JSON format */
|
||||||
data _null_;
|
data _null_;
|
||||||
file _webout mod lrecl=32000 encoding='utf-8';
|
file _webout mod lrecl=32000 encoding='utf-8';
|
||||||
length msg $32767 debug $8;
|
length msg $32767 ;
|
||||||
sasdatetime=datetime();
|
sasdatetime=datetime();
|
||||||
msg=cats(symget('msg'),'\n\nLog Extract:\n',symget('logmsg'));
|
msg=cats(symget('msg'),'\n\nLog Extract:\n',symget('logmsg'));
|
||||||
/* escape the quotes */
|
/* escape the quotes */
|
||||||
@@ -131,11 +189,16 @@
|
|||||||
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
|
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
|
||||||
put ',"_PROGRAM" : ' _PROGRAM ;
|
put ',"_PROGRAM" : ' _PROGRAM ;
|
||||||
put ",""SYSCC"" : ""&syscc"" ";
|
put ",""SYSCC"" : ""&syscc"" ";
|
||||||
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
|
syserrortext=quote(trim(symget('syserrortext')));
|
||||||
|
put ",""SYSERRORTEXT"" : " syserrortext;
|
||||||
|
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
||||||
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
||||||
|
put ",""SYSSCPL"" : ""&sysscpl"" ";
|
||||||
|
put ",""SYSSITE"" : ""&syssite"" ";
|
||||||
sysvlong=quote(trim(symget('sysvlong')));
|
sysvlong=quote(trim(symget('sysvlong')));
|
||||||
put ',"SYSVLONG" : ' sysvlong;
|
put ',"SYSVLONG" : ' sysvlong;
|
||||||
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" ";
|
syswarningtext=quote(trim(symget('syswarningtext')));
|
||||||
|
put ",""SYSWARNINGTEXT"" : " syswarningtext;
|
||||||
put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" ';
|
put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" ';
|
||||||
put "}" @;
|
put "}" @;
|
||||||
if debug ge '"131"' then put '>>weboutEND<<';
|
if debug ge '"131"' then put '>>weboutEND<<';
|
||||||
@@ -145,15 +208,26 @@
|
|||||||
|
|
||||||
%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;
|
%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;
|
||||||
data _null_;
|
data _null_;
|
||||||
putlog 'stpsrvset program error and syscc';
|
putlog 'stpsrvset program err and syscc';
|
||||||
rc=stpsrvset('program error', 0);
|
rc=stpsrvset('program error', 0);
|
||||||
call symputx("syscc",0,"g");
|
call symputx("syscc",0,"g");
|
||||||
run;
|
run;
|
||||||
%if "%substr(&sysvlong.xxxxxxxxx,1,9)" ne "9.04.01M3" %then %do;
|
/**
|
||||||
%put NOTE: Ending SAS session due to:;
|
* endsas kills 9.4m3 deployments by orphaning multibridges.
|
||||||
%put NOTE- &msg;
|
* Abort variants are ungraceful (non zero return code)
|
||||||
endsas;
|
* This approach lets SAS run silently until the end :-)
|
||||||
%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;
|
%end;
|
||||||
%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
|
%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
|
||||||
/* endsas kills the session making it harder to fetch results */
|
/* endsas kills the session making it harder to fetch results */
|
||||||
@@ -169,24 +243,6 @@
|
|||||||
abort cancel nolist;
|
abort cancel nolist;
|
||||||
run;
|
run;
|
||||||
%end;
|
%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;
|
%else %do;
|
||||||
%abort cancel;
|
%abort cancel;
|
||||||
%end;
|
%end;
|
||||||
|
|||||||
57
base/mp_appendfile.sas
Normal file
57
base/mp_appendfile.sas
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Append (concatenate) two or more files.
|
||||||
|
@details Will append one more more `appendrefs` (filerefs) to a `baseref`.
|
||||||
|
Uses a binary mechanism, so will work with any file type. For that reason -
|
||||||
|
use with care! And supply your own trailing carriage returns in each file..
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
filename tmp1 temp;
|
||||||
|
filename tmp2 temp;
|
||||||
|
filename tmp3 temp;
|
||||||
|
data _null_; file tmp1; put 'base file';
|
||||||
|
data _null_; file tmp2; put 'append1';
|
||||||
|
data _null_; file tmp3; put 'append2';
|
||||||
|
run;
|
||||||
|
%mp_appendfile(baseref=tmp1, appendrefs=tmp2 tmp3)
|
||||||
|
|
||||||
|
|
||||||
|
@param [in] baseref= Fileref of the base file (should exist)
|
||||||
|
@param [in] appendrefs= One or more filerefs to be appended to the base
|
||||||
|
fileref. Space separated.
|
||||||
|
|
||||||
|
@version 9.2
|
||||||
|
@author Allan Bowe, source: https://github.com/sasjs/core
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mp_abort.sas
|
||||||
|
@li mp_binarycopy.sas
|
||||||
|
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mp_appendfile(
|
||||||
|
baseref=0,
|
||||||
|
appendrefs=0
|
||||||
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
|
%mp_abort(iftrue= (&baseref=0)
|
||||||
|
,mac=&sysmacroname
|
||||||
|
,msg=%str(Baseref NOT specified!)
|
||||||
|
)
|
||||||
|
%mp_abort(iftrue= (&appendrefs=0)
|
||||||
|
,mac=&sysmacroname
|
||||||
|
,msg=%str(Appendrefs NOT specified!)
|
||||||
|
)
|
||||||
|
|
||||||
|
%local i;
|
||||||
|
%do i=1 %to %sysfunc(countw(&appendrefs));
|
||||||
|
%mp_abort(iftrue= (&syscc>0)
|
||||||
|
,mac=&sysmacroname
|
||||||
|
,msg=%str(syscc=&syscc)
|
||||||
|
)
|
||||||
|
%mp_binarycopy(inref=%scan(&appendrefs,&i), outref=&baseref, mode=APPEND)
|
||||||
|
%end;
|
||||||
|
|
||||||
|
%mend mp_appendfile;
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mf_existds.sas
|
@li mf_existds.sas
|
||||||
|
@li mf_getuniquename.sas
|
||||||
@li mf_nobs.sas
|
@li mf_nobs.sas
|
||||||
@li mp_abort.sas
|
@li mp_abort.sas
|
||||||
|
|
||||||
@@ -115,6 +116,26 @@
|
|||||||
select count(*) into: orig from &lib..&ds;
|
select count(*) into: orig from &lib..&ds;
|
||||||
quit;
|
quit;
|
||||||
|
|
||||||
|
%local notfound tmp1 tmp2;
|
||||||
|
%let tmp1=%mf_getuniquename();
|
||||||
|
%let tmp2=%mf_getuniquename();
|
||||||
|
|
||||||
|
/* this is a bit convoluted - but using sql outobs=10 throws warnings */
|
||||||
|
proc sql noprint;
|
||||||
|
create view &tmp1 as
|
||||||
|
select distinct &col
|
||||||
|
from &lib..&ds
|
||||||
|
where &col not in (
|
||||||
|
select &ccol from &clib..&cds
|
||||||
|
);
|
||||||
|
data &tmp2;
|
||||||
|
set &tmp1;
|
||||||
|
if _n_>10 then stop;
|
||||||
|
run;
|
||||||
|
proc sql;
|
||||||
|
select distinct &col into: notfound separated by ' ' from &tmp2;
|
||||||
|
|
||||||
|
|
||||||
%mp_abort(iftrue= (&syscc ne 0)
|
%mp_abort(iftrue= (&syscc ne 0)
|
||||||
,mac=&sysmacroname
|
,mac=&sysmacroname
|
||||||
,msg=%str(syscc=&syscc after macro query)
|
,msg=%str(syscc=&syscc after macro query)
|
||||||
@@ -125,7 +146,7 @@
|
|||||||
test_description=symget('desc');
|
test_description=symget('desc');
|
||||||
test_result='FAIL';
|
test_result='FAIL';
|
||||||
test_comments="&sysmacroname: &lib..&ds..&col has &result values "
|
test_comments="&sysmacroname: &lib..&ds..&col has &result values "
|
||||||
!!"not in &clib..&cds..&ccol ";
|
!!"not in &clib..&cds..&ccol.. First 10 vals:"!!symget('notfound');
|
||||||
%if &test=ANYVAL %then %do;
|
%if &test=ANYVAL %then %do;
|
||||||
if &result < &orig then test_result='PASS';
|
if &result < &orig then test_result='PASS';
|
||||||
%end;
|
%end;
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ run;
|
|||||||
|
|
||||||
%if &outfound=0 %then %do;
|
%if &outfound=0 %then %do;
|
||||||
filename &outref temp lrecl=2097088;
|
filename &outref temp lrecl=2097088;
|
||||||
%%end;
|
%end;
|
||||||
|
|
||||||
%if &action=ENCODE %then %do;
|
%if &action=ENCODE %then %do;
|
||||||
data _null_;
|
data _null_;
|
||||||
@@ -84,12 +84,12 @@ run;
|
|||||||
retain line "";
|
retain line "";
|
||||||
infile &inref recfm=F lrecl= 1 end=eof;
|
infile &inref recfm=F lrecl= 1 end=eof;
|
||||||
input @1 stream $char1.;
|
input @1 stream $char1.;
|
||||||
file &outref lrecl=76;
|
file &outref recfm=N;
|
||||||
substr(line,(_N_-(CEIL(_N_/57)-1)*57),1) = byte(rank(stream));
|
substr(line,(_N_-(CEIL(_N_/57)-1)*57),1) = byte(rank(stream));
|
||||||
if mod(_N_,57)=0 or EOF then do;
|
if mod(_N_,57)=0 or EOF then do;
|
||||||
if eof then b64=put(trim(line),$base64X76.);
|
if eof then b64=put(trim(line),$base64X76.);
|
||||||
else b64=put(line, $base64X76.);
|
else b64=put(line, $base64X76.);
|
||||||
put b64;
|
put b64 + (-1) @;
|
||||||
line="";
|
line="";
|
||||||
end;
|
end;
|
||||||
run;
|
run;
|
||||||
@@ -101,19 +101,13 @@ run;
|
|||||||
fileout = fopen("&outref",'O',3,'B');
|
fileout = fopen("&outref",'O',3,'B');
|
||||||
char= '20'x;
|
char= '20'x;
|
||||||
do while(fread(filein)=0);
|
do while(fread(filein)=0);
|
||||||
raw="1234";
|
length raw $4;
|
||||||
do i=1 to 4;
|
do i=1 to 4;
|
||||||
rc=fget(filein,char,1);
|
rc=fget(filein,char,1);
|
||||||
substr(raw,i,1)=char;
|
substr(raw,i,1)=char;
|
||||||
end;
|
end;
|
||||||
val="123";
|
rc = fput(fileout,input(raw,$base64X4.));
|
||||||
val=input(raw,$base64X4.);
|
rc = fwrite(fileout);
|
||||||
do i=1 to 3;
|
|
||||||
length byte $1;
|
|
||||||
byte=byte(rank(substr(val,i,1)));
|
|
||||||
rc = fput(fileout, byte);
|
|
||||||
end;
|
|
||||||
rc =fwrite(fileout);
|
|
||||||
end;
|
end;
|
||||||
rc = fclose(filein);
|
rc = fclose(filein);
|
||||||
rc = fclose(fileout);
|
rc = fclose(fileout);
|
||||||
|
|||||||
@@ -9,10 +9,29 @@
|
|||||||
|
|
||||||
%mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout)
|
%mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout)
|
||||||
|
|
||||||
@param inloc full, quoted "path/and/filename.ext" of the object to be copied
|
To append to a file, use the mode option, eg:
|
||||||
@param outloc full, quoted "path/and/filename.ext" of object to be created
|
|
||||||
@param inref can override default input fileref to avoid naming clash
|
filename tmp1 temp;
|
||||||
@param outref an override default output fileref to avoid naming clash
|
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
|
@returns nothing
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@@ -24,20 +43,29 @@
|
|||||||
,outloc= /* full path and filename of object to be created */
|
,outloc= /* full path and filename of object to be created */
|
||||||
,inref=____in /* override default to use own filerefs */
|
,inref=____in /* override default to use own filerefs */
|
||||||
,outref=____out /* override default to use own filerefs */
|
,outref=____out /* override default to use own filerefs */
|
||||||
|
,mode=CREATE
|
||||||
)/*/STORE SOURCE*/;
|
)/*/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 */
|
/* these IN and OUT filerefs can point to anything */
|
||||||
%if &inref = ____in %then %do;
|
%if &inref = ____in %then %do;
|
||||||
filename &inref &inloc lrecl=1048576 ;
|
filename &inref &inloc lrecl=1048576 ;
|
||||||
%end;
|
%end;
|
||||||
%if &outref=____out %then %do;
|
%if &outref=____out %then %do;
|
||||||
filename &outref &outloc lrecl=1048576 ;
|
filename &outref &outloc lrecl=1048576 &mod;
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
/* copy the file byte-for-byte */
|
/* copy the file byte-for-byte */
|
||||||
data _null_;
|
data _null_;
|
||||||
length filein 8 fileid 8;
|
length filein 8 fileid 8;
|
||||||
filein = fopen("&inref",'I',1,'B');
|
filein = fopen("&inref",'I',1,'B');
|
||||||
fileid = fopen("&outref",'O',1,'B');
|
fileid = fopen("&outref",&outmode,1,'B');
|
||||||
rec = '20'x;
|
rec = '20'x;
|
||||||
do while(fread(filein)=0);
|
do while(fread(filein)=0);
|
||||||
rc = fget(filein,rec,1);
|
rc = fget(filein,rec,1);
|
||||||
|
|||||||
@@ -2,12 +2,21 @@
|
|||||||
@file
|
@file
|
||||||
@brief Create a CARDS file from a SAS dataset.
|
@brief Create a CARDS file from a SAS dataset.
|
||||||
@details Uses dataset attributes to convert all data into datalines.
|
@details Uses dataset attributes to convert all data into datalines.
|
||||||
Running the generated file will rebuild the original dataset.
|
Running the generated file will rebuild the original dataset. Includes
|
||||||
|
support for large decimals, binary data, PROCESSED_DTTM columns, and
|
||||||
|
alternative encoding. If the input dataset is empty, the cards file will
|
||||||
|
still be created.
|
||||||
|
|
||||||
|
Additional support to generate a random sample and max rows.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
%mp_ds2cards(base_ds=sashelp.class
|
%mp_ds2cards(base_ds=sashelp.class
|
||||||
|
, tgt_ds=work.class
|
||||||
, cards_file= "C:\temp\class.sas"
|
, cards_file= "C:\temp\class.sas"
|
||||||
, maxobs=5)
|
, showlog=NO
|
||||||
|
, maxobs=5
|
||||||
|
)
|
||||||
|
|
||||||
TODO:
|
TODO:
|
||||||
- labelling the dataset
|
- labelling the dataset
|
||||||
@@ -18,15 +27,24 @@
|
|||||||
that is converted to a cards file.
|
that is converted to a cards file.
|
||||||
@param [in] tgt_ds= Table that the generated cards file would create.
|
@param [in] tgt_ds= Table that the generated cards file would create.
|
||||||
Optional - if omitted, will be same as BASE_DS.
|
Optional - if omitted, will be same as BASE_DS.
|
||||||
@param [out] cards_file= Location in which to write the (.sas) cards file
|
@param [out] cards_file= ("%sysfunc(pathname(work))/cardgen.sas") Location in
|
||||||
@param [in] maxobs= to limit output to the first <code>maxobs</code>
|
which to write the (.sas) cards file
|
||||||
observations
|
@param [in] maxobs= (max) To limit output to the first <code>maxobs</code>
|
||||||
@param [in] showlog= whether to show generated cards file in the SAS log
|
observations, enter an integer here.
|
||||||
(YES/NO)
|
@param [in] random_sample= (NO) Set to YES to generate a random sample of
|
||||||
@param [in] outencoding= provide encoding value for file statement (eg utf-8)
|
data. Can be quite slow.
|
||||||
@param [in] append= If NO then will rebuild the cards file if it already
|
@param [in] showlog= (YES) Whether to show generated cards file in the SAS
|
||||||
|
log. Valid values:
|
||||||
|
@li YES
|
||||||
|
@li NO
|
||||||
|
@param [in] outencoding= Provide encoding value for file statement (eg utf-8)
|
||||||
|
@param [in] append= (NO) If NO then will rebuild the cards file if it already
|
||||||
exists, otherwise will append to it. Used by the mp_lib2cards.sas macro.
|
exists, otherwise will append to it. Used by the mp_lib2cards.sas macro.
|
||||||
|
|
||||||
|
<h4> Related Macros </h4>
|
||||||
|
@li mp_lib2cards.sas
|
||||||
|
@li mp_ds2inserts.sas
|
||||||
|
@li mp_mdtablewrite.sas
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
@@ -51,15 +69,15 @@
|
|||||||
%if (&tgt_ds = ) %then %let tgt_ds=&base_ds;
|
%if (&tgt_ds = ) %then %let tgt_ds=&base_ds;
|
||||||
%if %index(&tgt_ds,.)=0 %then %let tgt_ds=WORK.%scan(&base_ds,2,.);
|
%if %index(&tgt_ds,.)=0 %then %let tgt_ds=WORK.%scan(&base_ds,2,.);
|
||||||
%if ("&outencoding" ne "") %then %let outencoding=encoding="&outencoding";
|
%if ("&outencoding" ne "") %then %let outencoding=encoding="&outencoding";
|
||||||
%if ("&append" = "") %then %let append=;
|
%if ("&append" = "" or "&append" = "NO") %then %let append=;
|
||||||
%else %let append=mod;
|
%else %let append=mod;
|
||||||
|
|
||||||
/* get varcount */
|
/* get varcount */
|
||||||
%let nvars=0;
|
%let nvars=0;
|
||||||
proc sql noprint;
|
proc sql noprint;
|
||||||
select count(*) into: nvars from dictionary.columns
|
select count(*) into: nvars from dictionary.columns
|
||||||
where libname="%scan(%upcase(&base_ds),1)"
|
where upcase(libname)="%scan(%upcase(&base_ds),1)"
|
||||||
and memname="%scan(%upcase(&base_ds),2)";
|
and upcase(memname)="%scan(%upcase(&base_ds),2)";
|
||||||
%if &nvars=0 %then %do;
|
%if &nvars=0 %then %do;
|
||||||
%put %str(WARN)ING: Dataset &base_ds has no variables, will not be converted.;
|
%put %str(WARN)ING: Dataset &base_ds has no variables, will not be converted.;
|
||||||
%return;
|
%return;
|
||||||
@@ -115,8 +133,8 @@ proc sql
|
|||||||
reset outobs=max;
|
reset outobs=max;
|
||||||
create table datalines1 as
|
create table datalines1 as
|
||||||
select name,type,length,varnum,format,label from dictionary.columns
|
select name,type,length,varnum,format,label from dictionary.columns
|
||||||
where libname="%upcase(%scan(&base_ds,1))"
|
where upcase(libname)="%upcase(%scan(&base_ds,1))"
|
||||||
and memname="%upcase(%scan(&base_ds,2))";
|
and upcase(memname)="%upcase(%scan(&base_ds,2))";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Due to long decimals cannot use best. format
|
Due to long decimals cannot use best. format
|
||||||
@@ -137,6 +155,12 @@ data datalines_2;
|
|||||||
,put(',name,',best32.-l)
|
,put(',name,',best32.-l)
|
||||||
,substrn(put(',name,',bestd32.-l),1
|
,substrn(put(',name,',bestd32.-l),1
|
||||||
,findc(put(',name,',bestd32.-l),"0","TBK")))');
|
,findc(put(',name,',bestd32.-l),"0","TBK")))');
|
||||||
|
/**
|
||||||
|
* binary data must be converted, to store in text format. It is identified
|
||||||
|
* by the presence of the $HEX keyword in the format.
|
||||||
|
*/
|
||||||
|
else if upcase(format)=:'$HEX' then
|
||||||
|
dataline=cats('put(trim(',name,'),',format,')');
|
||||||
else dataline=name;
|
else dataline=name;
|
||||||
run;
|
run;
|
||||||
|
|
||||||
@@ -162,7 +186,8 @@ data _null_;
|
|||||||
|
|
||||||
|
|
||||||
/* Build input statement */
|
/* Build input statement */
|
||||||
if type='char' then type3=':$char.';
|
if upcase(format)=:'$HEX' then type3=':'!!format;
|
||||||
|
else if type='char' then type3=':$char.';
|
||||||
str2=put(name,$33.)||type3;
|
str2=put(name,$33.)||type3;
|
||||||
|
|
||||||
|
|
||||||
@@ -184,11 +209,12 @@ data _null_;
|
|||||||
file &cards_file. &outencoding lrecl=32767 termstr=nl &append;
|
file &cards_file. &outencoding lrecl=32767 termstr=nl &append;
|
||||||
length __attrib $32767;
|
length __attrib $32767;
|
||||||
if _n_=1 then do;
|
if _n_=1 then do;
|
||||||
put '/*******************************************************************';
|
put '/**';
|
||||||
put " Datalines for %upcase(%scan(&base_ds,2)) dataset ";
|
put ' @file';
|
||||||
put " Generated by %nrstr(%%)mp_ds2cards()";
|
put " @brief Datalines for %upcase(%scan(&base_ds,2)) dataset";
|
||||||
put " Available on github.com/sasjs/core";
|
put " @details Generated by %nrstr(%%)mp_ds2cards()";
|
||||||
put '********************************************************************/';
|
put " Available on github.com/sasjs/core";
|
||||||
|
put '**/';
|
||||||
put "data &tgt_ds &indexes;";
|
put "data &tgt_ds &indexes;";
|
||||||
put "attrib ";
|
put "attrib ";
|
||||||
%do i = 1 %to &nvars;
|
%do i = 1 %to &nvars;
|
||||||
@@ -216,7 +242,7 @@ data _null_;
|
|||||||
put "input ";
|
put "input ";
|
||||||
%do i = 1 %to &nvars.;
|
%do i = 1 %to &nvars.;
|
||||||
%if(%length(&&input_stmt_&i..)) %then
|
%if(%length(&&input_stmt_&i..)) %then
|
||||||
put " &&input_stmt_&i..";
|
put " &&input_stmt_&i..";
|
||||||
;
|
;
|
||||||
%end;
|
%end;
|
||||||
put ";";
|
put ";";
|
||||||
|
|||||||
179
base/mp_ds2inserts.sas
Normal file
179
base/mp_ds2inserts.sas
Normal 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;
|
||||||
@@ -78,7 +78,7 @@ run;
|
|||||||
|
|
||||||
data &outds;
|
data &outds;
|
||||||
if &sqlrc or &syscc or &syserr then do;
|
if &sqlrc or &syscc or &syserr then do;
|
||||||
REASON_CD='VALIDATION_ERROR: '!!
|
REASON_CD='VALIDATION_ERR'!!'OR: '!!
|
||||||
coalescec(symget('SYSERRORTEXT'),symget('SYSWARNINGTEXT'));
|
coalescec(symget('SYSERRORTEXT'),symget('SYSWARNINGTEXT'));
|
||||||
output;
|
output;
|
||||||
end;
|
end;
|
||||||
|
|||||||
65
base/mp_getcols.sas
Normal file
65
base/mp_getcols.sas
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Creates a dataset with column metadata.
|
||||||
|
@details This macro takes the `proc contents` output and "tidies it up" in the
|
||||||
|
following ways:
|
||||||
|
|
||||||
|
@li Blank labels are filled in with column names
|
||||||
|
@li Formats are reconstructed with default values
|
||||||
|
@li Types such as DATE / TIME / DATETIME are inferred from the formats
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
%mp_getcols(sashelp.airline,outds=work.myds)
|
||||||
|
|
||||||
|
@param ds The dataset from which to obtain column metadata
|
||||||
|
@param outds= (work.cols) The output dataset to create. Sample data:
|
||||||
|
|NAME $|LENGTH 8|VARNUM 8|LABEL $|FORMAT $49|TYPE $1 |DDTYPE $|
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
|AIR|8|2|international airline travel (thousands)|8.|N|NUMERIC|
|
||||||
|
|DATE|8|1|DATE|MONYY.|N|DATE|
|
||||||
|
|REGION|3|3|REGION|$3.|C|CHARACTER|
|
||||||
|
|
||||||
|
<h4> Related Macros </h4>
|
||||||
|
@li mf_getvarlist.sas
|
||||||
|
@li mm_getcols.sas
|
||||||
|
|
||||||
|
@version 9.2
|
||||||
|
@author Allan Bowe
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mp_getcols(ds, outds=work.cols);
|
||||||
|
|
||||||
|
proc contents noprint data=&ds
|
||||||
|
out=_data_ (keep=name type length label varnum format:);
|
||||||
|
run;
|
||||||
|
data &outds(keep=name type length varnum format label ddtype);
|
||||||
|
set &syslast(rename=(format=format2 type=type2));
|
||||||
|
name=upcase(name);
|
||||||
|
if type2=2 then do;
|
||||||
|
length format $49.;
|
||||||
|
if format2='' then format=cats('$',length,'.');
|
||||||
|
else if formatl=0 then format=cats(format2,'.');
|
||||||
|
else format=cats(format2,formatl,'.');
|
||||||
|
type='C';
|
||||||
|
ddtype='CHARACTER';
|
||||||
|
end;
|
||||||
|
else do;
|
||||||
|
if format2='' then format=cats(length,'.');
|
||||||
|
else if formatl=0 then format=cats(format2,'.');
|
||||||
|
else if formatd=0 then format=cats(format2,formatl,'.');
|
||||||
|
else format=cats(format2,formatl,'.',formatd);
|
||||||
|
type='N';
|
||||||
|
if format=:'DATETIME' then ddtype='DATETIME';
|
||||||
|
else if format=:'DATE' or format=:'DDMMYY' or format=:'MMDDYY'
|
||||||
|
or format=:'YYMMDD' or format=:'E8601DA' or format=:'B8601DA'
|
||||||
|
or format=:'MONYY'
|
||||||
|
then ddtype='DATE';
|
||||||
|
else if format=:'TIME' then ddtype='TIME';
|
||||||
|
else ddtype='NUMERIC';
|
||||||
|
end;
|
||||||
|
if label='' then label=name;
|
||||||
|
run;
|
||||||
|
|
||||||
|
%mend mp_getcols;
|
||||||
@@ -39,21 +39,21 @@
|
|||||||
/* must use SQL as proc datasets does not support length changes */
|
/* must use SQL as proc datasets does not support length changes */
|
||||||
proc sql noprint;
|
proc sql noprint;
|
||||||
create table &outds as
|
create table &outds as
|
||||||
select a.TABLE_CATALOG as libref
|
select upcase(a.TABLE_CATALOG) as libref
|
||||||
,a.TABLE_NAME
|
,upcase(a.TABLE_NAME) as TABLE_NAME
|
||||||
,a.constraint_type
|
,a.constraint_type
|
||||||
,a.constraint_name
|
,a.constraint_name
|
||||||
,b.column_name
|
,b.column_name
|
||||||
from dictionary.TABLE_CONSTRAINTS a
|
from dictionary.TABLE_CONSTRAINTS a
|
||||||
left join dictionary.constraint_column_usage b
|
left join dictionary.constraint_column_usage b
|
||||||
on a.TABLE_CATALOG=b.TABLE_CATALOG
|
on upcase(a.TABLE_CATALOG)=upcase(b.TABLE_CATALOG)
|
||||||
and a.TABLE_NAME=b.TABLE_NAME
|
and upcase(a.TABLE_NAME)=upcase(b.TABLE_NAME)
|
||||||
and a.constraint_name=b.constraint_name
|
and a.constraint_name=b.constraint_name
|
||||||
where a.TABLE_CATALOG="&lib"
|
where upcase(a.TABLE_CATALOG)="&lib"
|
||||||
and b.TABLE_CATALOG="&lib"
|
and upcase(b.TABLE_CATALOG)="&lib"
|
||||||
%if "&ds" ne "" %then %do;
|
%if "&ds" ne "" %then %do;
|
||||||
and a.TABLE_NAME="&ds"
|
and upcase(a.TABLE_NAME)="&ds"
|
||||||
and b.TABLE_NAME="&ds"
|
and upcase(b.TABLE_NAME)="&ds"
|
||||||
%end;
|
%end;
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
to create tables in SAS or a database. The macro can be used at table or
|
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.
|
library level. The default behaviour is to create DDL in SAS format.
|
||||||
|
|
||||||
|
Note - views are not currently supported.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
data test(index=(pk=(x y)/unique /nomiss));
|
data test(index=(pk=(x y)/unique /nomiss));
|
||||||
@@ -16,12 +18,14 @@
|
|||||||
%mp_getddl(work,test,flavour=tsql,showlog=YES)
|
%mp_getddl(work,test,flavour=tsql,showlog=YES)
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
|
@li mf_existfileref.sas
|
||||||
|
@li mf_getvarcount.sas
|
||||||
@li mp_getconstraints.sas
|
@li mp_getconstraints.sas
|
||||||
|
|
||||||
@param lib libref of the library to create DDL for. Should be assigned.
|
@param lib libref of the library to create DDL for. Should be assigned.
|
||||||
@param ds dataset to create ddl for (optional)
|
@param ds dataset to create ddl for (optional)
|
||||||
@param fref= the fileref to which to write the DDL. If not preassigned, will
|
@param fref= the fileref to which to _append_ the DDL. If it does not exist,
|
||||||
be assigned to TEMP.
|
it will be created.
|
||||||
@param flavour= The type of DDL to create (default=SAS). Supported=TSQL
|
@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 showlog= Set to YES to show the DDL in the log
|
||||||
@param schema= Choose a preferred schema name (default is to use actual schema
|
@param schema= Choose a preferred schema name (default is to use actual schema
|
||||||
@@ -37,9 +41,10 @@
|
|||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
/* check fileref is assigned */
|
/* check fileref is assigned */
|
||||||
%if %sysfunc(fileref(&fref)) > 0 %then %do;
|
%if %mf_existfileref(&fref)=0 %then %do;
|
||||||
filename &fref temp;
|
filename &fref temp ;
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
%if %length(&libref)=0 %then %let libref=WORK;
|
%if %length(&libref)=0 %then %let libref=WORK;
|
||||||
%let flavour=%upcase(&flavour);
|
%let flavour=%upcase(&flavour);
|
||||||
|
|
||||||
@@ -47,6 +52,7 @@ proc sql noprint;
|
|||||||
create table _data_ as
|
create table _data_ as
|
||||||
select * from dictionary.tables
|
select * from dictionary.tables
|
||||||
where upcase(libname)="%upcase(&libref)"
|
where upcase(libname)="%upcase(&libref)"
|
||||||
|
and memtype='DATA' /* views not currently supported */
|
||||||
%if %length(&ds)>0 %then %do;
|
%if %length(&ds)>0 %then %do;
|
||||||
and upcase(memname)="%upcase(&ds)"
|
and upcase(memname)="%upcase(&ds)"
|
||||||
%end;
|
%end;
|
||||||
@@ -118,7 +124,7 @@ create table _data_ as
|
|||||||
%mend addConst;
|
%mend addConst;
|
||||||
|
|
||||||
data _null_;
|
data _null_;
|
||||||
file &fref;
|
file &fref mod;
|
||||||
put "/* DDL generated by &sysuserid on %sysfunc(datetime(),datetime19.) */";
|
put "/* DDL generated by &sysuserid on %sysfunc(datetime(),datetime19.) */";
|
||||||
run;
|
run;
|
||||||
|
|
||||||
@@ -141,13 +147,15 @@ run;
|
|||||||
put "create table &libref..&curds(";
|
put "create table &libref..&curds(";
|
||||||
end;
|
end;
|
||||||
else do;
|
else do;
|
||||||
|
/* just a placeholder - we filter out views at the top */
|
||||||
put "create view &libref..&curds(";
|
put "create view &libref..&curds(";
|
||||||
end;
|
end;
|
||||||
put " "@@;
|
put " "@@;
|
||||||
end;
|
end;
|
||||||
else put " ,"@@;
|
else put " ,"@@;
|
||||||
if length(format)>1 then fmt=" format="!!cats(format);
|
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 notnull='yes' then notnul=' not null';
|
||||||
if type='char' then typ=cats('char(',length,')');
|
if type='char' then typ=cats('char(',length,')');
|
||||||
else if length ne 8 then typ='num length='!!left(length);
|
else if length ne 8 then typ='num length='!!left(length);
|
||||||
@@ -203,7 +211,7 @@ run;
|
|||||||
proc sql noprint;
|
proc sql noprint;
|
||||||
select sysvalue into: schemaactual
|
select sysvalue into: schemaactual
|
||||||
from dictionary.libnames
|
from dictionary.libnames
|
||||||
where libname="&libref" and engine='SQLSVR';
|
where upcase(libname)="&libref" and engine='SQLSVR';
|
||||||
%let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref));
|
%let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref));
|
||||||
|
|
||||||
%do x=1 %to %sysfunc(countw(&dsnlist));
|
%do x=1 %to %sysfunc(countw(&dsnlist));
|
||||||
@@ -219,6 +227,7 @@ run;
|
|||||||
put "create table [&schema].[&curds](";
|
put "create table [&schema].[&curds](";
|
||||||
end;
|
end;
|
||||||
else do;
|
else do;
|
||||||
|
/* just a placeholder - we filter out views at the top */
|
||||||
put "create view [&schema].[&curds](";
|
put "create view [&schema].[&curds](";
|
||||||
end;
|
end;
|
||||||
put " "@@;
|
put " "@@;
|
||||||
@@ -295,78 +304,88 @@ run;
|
|||||||
proc sql noprint;
|
proc sql noprint;
|
||||||
select sysvalue into: schemaactual
|
select sysvalue into: schemaactual
|
||||||
from dictionary.libnames
|
from dictionary.libnames
|
||||||
where libname="&libref" and engine='POSTGRES';
|
where upcase(libname)="&libref" and engine='POSTGRES';
|
||||||
%let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref));
|
%let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref));
|
||||||
data _null_;
|
data _null_;
|
||||||
file &fref mod;
|
file &fref mod;
|
||||||
put "CREATE SCHEMA &schema;";
|
put "CREATE SCHEMA &schema;";
|
||||||
%do x=1 %to %sysfunc(countw(&dsnlist));
|
%do x=1 %to %sysfunc(countw(&dsnlist));
|
||||||
%let curds=%scan(&dsnlist,&x);
|
%let curds=%scan(&dsnlist,&x);
|
||||||
data _null_;
|
%local curdsvarcount;
|
||||||
file &fref mod;
|
%let curdsvarcount=%mf_getvarcount(&libref..&curds);
|
||||||
put "/* Postgres Flavour DDL for &schema..&curds */";
|
%if &curdsvarcount>1600 %then %do;
|
||||||
data _null_;
|
data _null_;
|
||||||
file &fref mod;
|
file &fref mod;
|
||||||
set &colinfo (where=(upcase(memname)="&curds")) end=last;
|
put "/* &libref..&curds contains &curdsvarcount vars */";
|
||||||
length fmt $32;
|
put "/* Postgres cannot create tables with over 1600 vars */";
|
||||||
if _n_=1 then do;
|
put "/* No DDL will be generated for this table";
|
||||||
if memtype='DATA' then do;
|
run;
|
||||||
put "CREATE TABLE &schema..&curds (";
|
%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;
|
end;
|
||||||
else do;
|
else put " ,"@@;
|
||||||
put "CREATE VIEW &schema..&curds (";
|
format=upcase(format);
|
||||||
end;
|
if 1=0 then; /* dummy if */
|
||||||
put " "@@;
|
%if &applydttm=YES %then %do;
|
||||||
end;
|
else if format=:'DATETIME' then fmt=' TIMESTAMP ';
|
||||||
else put " ,"@@;
|
%end;
|
||||||
format=upcase(format);
|
else if type='num' then fmt=' DOUBLE PRECISION';
|
||||||
if 1=0 then; /* dummy if */
|
else fmt='VARCHAR('!!cats(length)!!')';
|
||||||
%if &applydttm=YES %then %do;
|
if notnull='yes' then notnul=' NOT NULL';
|
||||||
else if format=:'DATETIME' then fmt=' TIMESTAMP ';
|
/* quote column names in case they represent reserved words */
|
||||||
%end;
|
name2=quote(trim(name));
|
||||||
else if type='num' then fmt=' DOUBLE PRECISION';
|
put name2 fmt notnul;
|
||||||
else fmt='VARCHAR('!!cats(length)!!')';
|
run;
|
||||||
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 */
|
/* Extra step for data constraints */
|
||||||
%addConst()
|
%addConst()
|
||||||
|
|
||||||
data _null_;
|
data _null_;
|
||||||
file &fref mod;
|
file &fref mod;
|
||||||
put ');';
|
put ');';
|
||||||
run;
|
run;
|
||||||
|
|
||||||
/* Create Unique Indexes, but only if they were not already defined within
|
/* Create Unique Indexes, but only if they were not already defined within
|
||||||
the Constraints section. */
|
the Constraints section. */
|
||||||
data _null_;
|
data _null_;
|
||||||
*length ds $128;
|
*length ds $128;
|
||||||
set &idxinfo(
|
set &idxinfo(
|
||||||
where=(
|
where=(
|
||||||
memname="&curds"
|
memname="&curds"
|
||||||
and unique='yes'
|
and unique='yes'
|
||||||
and indxname not in (
|
and indxname not in (
|
||||||
%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))
|
%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
);
|
file &fref mod;
|
||||||
file &fref mod;
|
by idxusage indxname;
|
||||||
by idxusage indxname;
|
if first.indxname then do;
|
||||||
/* ds=cats(libname,'.',memname); */
|
|
||||||
if first.indxname then do;
|
|
||||||
put 'CREATE UNIQUE INDEX "' indxname +(-1) '" ' "ON &schema..&curds(";
|
put 'CREATE UNIQUE INDEX "' indxname +(-1) '" ' "ON &schema..&curds(";
|
||||||
put ' "' name +(-1) '"' ;
|
put ' "' name +(-1) '"' ;
|
||||||
end;
|
end;
|
||||||
else put ' ,"' name +(-1) '"';
|
else put ' ,"' name +(-1) '"';
|
||||||
*else put ' ,' name ;
|
if last.indxname then do;
|
||||||
if last.indxname then do;
|
put ');';
|
||||||
put ');';
|
end;
|
||||||
end;
|
run;
|
||||||
run;
|
%end;
|
||||||
|
|
||||||
%end;
|
%end;
|
||||||
%end;
|
%end;
|
||||||
%if %upcase(&showlog)=YES %then %do;
|
%if %upcase(&showlog)=YES %then %do;
|
||||||
|
|||||||
53
base/mp_gsubfile.sas
Normal file
53
base/mp_gsubfile.sas
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Performs a text substitution on a file
|
||||||
|
@details Makes use of the GSUB function in LUA to perform a text substitution
|
||||||
|
in a file - either in-place, or writing to a new location. The benefit of
|
||||||
|
using LUA is that the entire file can be loaded into a single variable,
|
||||||
|
thereby side stepping the 32767 character limit in a data step.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
%let file=%sysfunc(pathname(work))/file.txt;
|
||||||
|
%let str=replace/me;
|
||||||
|
%let rep=with/this;
|
||||||
|
data _null_;
|
||||||
|
file "&file";
|
||||||
|
put "&str";
|
||||||
|
run;
|
||||||
|
%mp_gsubfile(file=&file, patternvar=str, replacevar=rep)
|
||||||
|
data _null_;
|
||||||
|
infile "&file";
|
||||||
|
input;
|
||||||
|
list;
|
||||||
|
run;
|
||||||
|
|
||||||
|
@param file= (0) The file to perform the substitution on
|
||||||
|
@param patternvar= A macro variable containing the Lua
|
||||||
|
[pattern](https://www.lua.org/pil/20.2.html) to search for. Due to the use
|
||||||
|
of special (magic) characters in Lua patterns, it is safer to pass the NAME
|
||||||
|
of the macro variable containing the string, rather than the value itself.
|
||||||
|
@param replacevar= The name of the macro variable containing the replacement
|
||||||
|
_string_.
|
||||||
|
@param outfile= (0) The file to write the output to. If zero, then the file
|
||||||
|
is overwritten in-place.
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li ml_gsubfile.sas
|
||||||
|
|
||||||
|
<h4> Related Macros </h4>
|
||||||
|
@li mp_gsubfile.test.sas
|
||||||
|
|
||||||
|
@version 9.4
|
||||||
|
@author Allan Bowe
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mp_gsubfile(file=0,
|
||||||
|
patternvar=,
|
||||||
|
replacevar=,
|
||||||
|
outfile=0
|
||||||
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
|
%ml_gsubfile()
|
||||||
|
|
||||||
|
%mend mp_gsubfile;
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
@li mf_getvartype.sas
|
@li mf_getvartype.sas
|
||||||
|
|
||||||
@param [in] libds dataset to hash
|
@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
|
@param [out] outds= (work.mf_hashdataset) The output dataset to create. This
|
||||||
will contain one column (hashkey) with one observation (a hex32.
|
will contain one column (hashkey) with one observation (a hex32.
|
||||||
representation of the input hash)
|
representation of the input hash)
|
||||||
@@ -33,7 +34,8 @@
|
|||||||
|
|
||||||
%macro mp_hashdataset(
|
%macro mp_hashdataset(
|
||||||
libds,
|
libds,
|
||||||
outds=
|
outds=,
|
||||||
|
salt=
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
%if %mf_getattrn(&libds,NLOBS)=0 %then %do;
|
%if %mf_getattrn(&libds,NLOBS)=0 %then %do;
|
||||||
%put %str(WARN)ING: Dataset &libds is empty;, or is not a dataset;
|
%put %str(WARN)ING: Dataset &libds is empty;, or is not a dataset;
|
||||||
@@ -53,7 +55,7 @@
|
|||||||
%let varlist=%mf_getvarlist(&libds);
|
%let varlist=%mf_getvarlist(&libds);
|
||||||
data &outds(rename=(&keyvar=hashkey) keep=&keyvar);
|
data &outds(rename=(&keyvar=hashkey) keep=&keyvar);
|
||||||
length &prevkeyvar &keyvar $32;
|
length &prevkeyvar &keyvar $32;
|
||||||
retain &prevkeyvar;
|
retain &prevkeyvar "%sysfunc(md5(%str(&salt)),$hex32.)";
|
||||||
set &libds end=&lastvar;
|
set &libds end=&lastvar;
|
||||||
/* hash should include previous row */
|
/* hash should include previous row */
|
||||||
&keyvar=put(md5(&prevkeyvar
|
&keyvar=put(md5(&prevkeyvar
|
||||||
|
|||||||
104
base/mp_include.sas
Normal file
104
base/mp_include.sas
Normal 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;
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
@details PROC JSON is faster but will produce errs like the ones below if
|
@details PROC JSON is faster but will produce errs like the ones below if
|
||||||
special chars are encountered.
|
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.
|
> An object or array close is not valid at this point in the JSON text.
|
||||||
|
|
||||||
|
|||||||
77
base/mp_lib2inserts.sas
Normal file
77
base/mp_lib2inserts.sas
Normal 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;
|
||||||
@@ -14,14 +14,14 @@
|
|||||||
We take the standard definition one step further by embedding the informat
|
We take the standard definition one step further by embedding the informat
|
||||||
in the table header row, like so:
|
in the table header row, like so:
|
||||||
|
|
||||||
|var1:$|var2:best.|var3:date9.|
|
|var1:$32|var2:best.|var3:date9.|
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|some text|42|01JAN1960|
|
|some text|42|01JAN1960|
|
||||||
|blah|1|31DEC1999|
|
|blah|1|31DEC1999|
|
||||||
|
|
||||||
Which resolves to:
|
Which resolves to:
|
||||||
|
|
||||||
|var1:$|var2:best.|var3:date9.|
|
|var1:$32|var2:best.|var3:date9.|
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|some text|42|01JAN1960|
|
|some text|42|01JAN1960|
|
||||||
|blah|1|31DEC1999|
|
|blah|1|31DEC1999|
|
||||||
|
|||||||
59
base/mp_webin.sas
Normal file
59
base/mp_webin.sas
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
@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
|
||||||
|
@li mf_getuniquefileref.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();
|
||||||
|
filename &&_webin_fileref&i filesrvc "&&_webin_fileuri&i";
|
||||||
|
%end;
|
||||||
|
|
||||||
|
|
||||||
|
%mend mp_webin;
|
||||||
4
build.py
4
build.py
@@ -22,7 +22,7 @@ for file in files:
|
|||||||
for line in infile:
|
for line in infile:
|
||||||
ml.write(" put '" + line.rstrip().replace("'","''") + " ';\n")
|
ml.write(" put '" + line.rstrip().replace("'","''") + " ';\n")
|
||||||
ml.write("run;\n\n")
|
ml.write("run;\n\n")
|
||||||
ml.write("%inc \"%sysfunc(pathname(work))/" + name + ".lua\";\n\n")
|
ml.write("%inc \"%sysfunc(pathname(work))/" + name + ".lua\" /source2;\n\n")
|
||||||
ml.write("%mend " + name + ";\n")
|
ml.write("%mend " + name + ";\n")
|
||||||
|
|
||||||
ml.close()
|
ml.close()
|
||||||
@@ -84,7 +84,7 @@ options noquotelenmax;
|
|||||||
"""
|
"""
|
||||||
f = open('all.sas', "w") # r / r+ / rb / rb+ / w / wb
|
f = open('all.sas', "w") # r / r+ / rb / rb+ / w / wb
|
||||||
f.write(header)
|
f.write(header)
|
||||||
folders=['base','meta','metax','viya','lua']
|
folders=['base','meta','metax','viya','lua','fcmp']
|
||||||
for folder in folders:
|
for folder in folders:
|
||||||
filenames = [fn for fn in Path('./' + folder).iterdir() if fn.match("*.sas")]
|
filenames = [fn for fn in Path('./' + folder).iterdir() if fn.match("*.sas")]
|
||||||
filenames.sort()
|
filenames.sort()
|
||||||
|
|||||||
94
fcmp/mcf_stpsrv_header.sas
Normal file
94
fcmp/mcf_stpsrv_header.sas
Normal 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
79
fcmp/mcf_string2file.sas
Normal 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;
|
||||||
25
lua/gsubfile.lua
Normal file
25
lua/gsubfile.lua
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
local fpath, outpath, file, fcontent
|
||||||
|
|
||||||
|
-- configure in / out paths
|
||||||
|
fpath = sas.symget("file")
|
||||||
|
outpath = sas.symget("outfile")
|
||||||
|
if ( outpath == 0 )
|
||||||
|
then
|
||||||
|
outpath=fpath
|
||||||
|
end
|
||||||
|
|
||||||
|
-- open file and perform the substitution
|
||||||
|
file = io.open(fpath,"r")
|
||||||
|
fcontent = file:read("*all")
|
||||||
|
file:close()
|
||||||
|
fcontent = string.gsub(
|
||||||
|
fcontent,
|
||||||
|
sas.symget(sas.symget("patternvar")),
|
||||||
|
sas.symget(sas.symget("replacevar"))
|
||||||
|
)
|
||||||
|
|
||||||
|
-- write the file back out
|
||||||
|
file = io.open(outpath, "w+")
|
||||||
|
io.output(file)
|
||||||
|
io.write(fcontent)
|
||||||
|
io.close(file)
|
||||||
44
lua/ml_gsubfile.sas
Normal file
44
lua/ml_gsubfile.sas
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
@file ml_gsubfile.sas
|
||||||
|
@brief Compiles the gsubfile.lua lua file
|
||||||
|
@details Writes gsubfile.lua to the work directory
|
||||||
|
and then includes it.
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
%ml_gsubfile()
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro ml_gsubfile();
|
||||||
|
data _null_;
|
||||||
|
file "%sysfunc(pathname(work))/ml_gsubfile.lua";
|
||||||
|
put 'local fpath, outpath, file, fcontent ';
|
||||||
|
put ' ';
|
||||||
|
put '-- configure in / out paths ';
|
||||||
|
put 'fpath = sas.symget("file") ';
|
||||||
|
put 'outpath = sas.symget("outfile") ';
|
||||||
|
put 'if ( outpath == 0 ) ';
|
||||||
|
put 'then ';
|
||||||
|
put ' outpath=fpath ';
|
||||||
|
put 'end ';
|
||||||
|
put ' ';
|
||||||
|
put '-- open file and perform the substitution ';
|
||||||
|
put 'file = io.open(fpath,"r") ';
|
||||||
|
put 'fcontent = file:read("*all") ';
|
||||||
|
put 'file:close() ';
|
||||||
|
put 'fcontent = string.gsub( ';
|
||||||
|
put ' fcontent, ';
|
||||||
|
put ' sas.symget(sas.symget("patternvar")), ';
|
||||||
|
put ' sas.symget(sas.symget("replacevar")) ';
|
||||||
|
put ') ';
|
||||||
|
put ' ';
|
||||||
|
put '-- write the file back out ';
|
||||||
|
put 'file = io.open(outpath, "w+") ';
|
||||||
|
put 'io.output(file) ';
|
||||||
|
put 'io.write(fcontent) ';
|
||||||
|
put 'io.close(file) ';
|
||||||
|
run;
|
||||||
|
|
||||||
|
%inc "%sysfunc(pathname(work))/ml_gsubfile.lua" /source2;
|
||||||
|
|
||||||
|
%mend ml_gsubfile;
|
||||||
@@ -389,6 +389,6 @@ data _null_;
|
|||||||
put '-- JSON.LUA ENDS HERE ';
|
put '-- JSON.LUA ENDS HERE ';
|
||||||
run;
|
run;
|
||||||
|
|
||||||
%inc "%sysfunc(pathname(work))/ml_json.lua";
|
%inc "%sysfunc(pathname(work))/ml_json.lua" /source2;
|
||||||
|
|
||||||
%mend ml_json;
|
%mend ml_json;
|
||||||
|
|||||||
13
main.dox
13
main.dox
@@ -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
|
/*! \dir meta
|
||||||
* \brief Metadata Aware Macros
|
* \brief Metadata Aware Macros
|
||||||
* \details These macros have the following attributes:
|
* \details These macros have the following attributes:
|
||||||
|
|||||||
@@ -14,10 +14,10 @@
|
|||||||
|
|
||||||
usage:
|
usage:
|
||||||
|
|
||||||
%mm_createfolder(path=/some/meta/folder)
|
%mm_createfolder(path=/some/meta/folder)
|
||||||
|
|
||||||
@param path= Name of the folder to create.
|
@param [in] path= Name of the folder to create.
|
||||||
@param mdebug= set DBG to 1 to disable DEBUG messages
|
@param [in] mdebug= set DBG to 1 to disable DEBUG messages
|
||||||
|
|
||||||
@version 9.4
|
@version 9.4
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
|
|||||||
@@ -385,6 +385,7 @@ data _null_;
|
|||||||
put ' put ",""SYSERRORTEXT"" : ""&syserrortext"" "; ';
|
put ' put ",""SYSERRORTEXT"" : ""&syserrortext"" "; ';
|
||||||
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
|
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
|
||||||
put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
|
put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
|
||||||
|
put ' put ",""SYSSCPL"" : ""&sysscpl"" "; ';
|
||||||
put ' put ",""SYSSITE"" : ""&syssite"" "; ';
|
put ' put ",""SYSSITE"" : ""&syssite"" "; ';
|
||||||
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
|
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
|
||||||
put ' put '',"SYSVLONG" : '' sysvlong; ';
|
put ' put '',"SYSVLONG" : '' sysvlong; ';
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
/**
|
/**
|
||||||
@file
|
@file
|
||||||
@brief Creates a dataset with all metadata tables for a particular library
|
@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
|
@details Will only show the tables for which the executing user has the
|
||||||
metadata access.
|
requisite metadata access.
|
||||||
|
|
||||||
usage:
|
usage:
|
||||||
|
|
||||||
%mm_gettables(uri=A5X8AHW1.B40001S5)
|
%mm_gettables(uri=A5X8AHW1.B40001S5)
|
||||||
|
|
||||||
@param outds the dataset to create that contains the list of tables
|
@param [in] uri= the uri of the library for which to return tables
|
||||||
@param uri the uri of the library for which to return tables
|
@param [out] outds= (work.mm_gettables) the dataset to contain the list of
|
||||||
@param getauth= YES|NO - fetch the authdomain used in database connections.
|
tables
|
||||||
Set to NO to improve runtimes in larger environments, as there can be a
|
@param [in] getauth= (YES) Fetch the authdomain used in database connections.
|
||||||
performance hit on the `metadata_getattr(domainuri, "Name", AuthDomain)` call.
|
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"
|
@returns outds dataset containing all groups in a column named "metagroup"
|
||||||
(defaults to work.mm_getlibs). The following columns are provided:
|
(defaults to work.mm_getlibs). The following columns are provided:
|
||||||
@@ -40,8 +42,8 @@ data &outds;
|
|||||||
libdesc $200 libref engine $8 IsDBMSLibname $1
|
libdesc $200 libref engine $8 IsDBMSLibname $1
|
||||||
tablename $50 /* metadata table names can be longer than $32 */
|
tablename $50 /* metadata table names can be longer than $32 */
|
||||||
;
|
;
|
||||||
keep libname libdesc libref engine ServerContext path_schema AuthDomain tableuri
|
keep libname libdesc libref engine ServerContext path_schema AuthDomain
|
||||||
tablename IsPreassigned IsDBMSLibname id;
|
tableuri tablename IsPreassigned IsDBMSLibname id;
|
||||||
call missing (of _all_);
|
call missing (of _all_);
|
||||||
|
|
||||||
uri=symget('uri');
|
uri=symget('uri');
|
||||||
|
|||||||
@@ -155,6 +155,7 @@
|
|||||||
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
|
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
|
||||||
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
||||||
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
||||||
|
put ",""SYSSCPL"" : ""&sysscpl"" ";
|
||||||
put ",""SYSSITE"" : ""&syssite"" ";
|
put ",""SYSSITE"" : ""&syssite"" ";
|
||||||
sysvlong=quote(trim(symget('sysvlong')));
|
sysvlong=quote(trim(symget('sysvlong')));
|
||||||
put ',"SYSVLONG" : ' sysvlong;
|
put ',"SYSVLONG" : ' sysvlong;
|
||||||
|
|||||||
695
package-lock.json
generated
695
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -27,12 +27,12 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "sasjs cbd -t viya",
|
"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",
|
"test": "sasjs test -t viya",
|
||||||
"lint": "sasjs lint",
|
"lint": "sasjs lint",
|
||||||
"prepare": "git rev-parse --git-dir && git config core.hooksPath ./.git-hooks || true"
|
"prepare": "git rev-parse --git-dir && git config core.hooksPath ./.git-hooks || true"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sasjs/cli": "2.33.3"
|
"@sasjs/cli": "^2.37.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
"$schema": "https://cli.sasjs.io/sasjsconfig-schema.json",
|
"$schema": "https://cli.sasjs.io/sasjsconfig-schema.json",
|
||||||
"macroFolders": [
|
"macroFolders": [
|
||||||
"base",
|
"base",
|
||||||
|
"fcmp",
|
||||||
"meta",
|
"meta",
|
||||||
"metax",
|
"metax",
|
||||||
"viya",
|
"viya",
|
||||||
"lua",
|
"lua",
|
||||||
"tests/base"
|
"tests/crossplatform"
|
||||||
],
|
],
|
||||||
"docConfig": {
|
"docConfig": {
|
||||||
"displayMacroCore": false,
|
"displayMacroCore": false,
|
||||||
@@ -33,7 +34,7 @@
|
|||||||
"allowInsecureRequests": false,
|
"allowInsecureRequests": false,
|
||||||
"appLoc": "/Public/temp/macrocore",
|
"appLoc": "/Public/temp/macrocore",
|
||||||
"macroFolders": [
|
"macroFolders": [
|
||||||
"tests/viya"
|
"tests/viyaonly"
|
||||||
],
|
],
|
||||||
"programFolders": [],
|
"programFolders": [],
|
||||||
"deployConfig": {
|
"deployConfig": {
|
||||||
@@ -48,7 +49,15 @@
|
|||||||
"serverType": "SAS9",
|
"serverType": "SAS9",
|
||||||
"appLoc": "/Shared Data/temp/macrocore",
|
"appLoc": "/Shared Data/temp/macrocore",
|
||||||
"macroFolders": [
|
"macroFolders": [
|
||||||
"tests/meta"
|
"tests/sas9only"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "docsonly",
|
||||||
|
"serverType": "SAS9",
|
||||||
|
"macroFolders": [
|
||||||
|
"tests/sas9only",
|
||||||
|
"tests/viyaonly"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
39
tests/crossplatform/mcf_stpsrv_header.test.sas
Normal file
39
tests/crossplatform/mcf_stpsrv_header.test.sas
Normal 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
|
||||||
|
)
|
||||||
52
tests/crossplatform/mcf_string2file.test.sas
Normal file
52
tests/crossplatform/mcf_string2file.test.sas
Normal 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
|
||||||
|
)
|
||||||
35
tests/crossplatform/mf_existfileref.test.sas
Normal file
35
tests/crossplatform/mf_existfileref.test.sas
Normal 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
|
||||||
|
)
|
||||||
22
tests/crossplatform/mf_existfunction.test.sas
Normal file
22
tests/crossplatform/mf_existfunction.test.sas
Normal 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
|
||||||
|
)
|
||||||
|
|
||||||
49
tests/crossplatform/mf_getapploc.test.sas
Normal file
49
tests/crossplatform/mf_getapploc.test.sas
Normal 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
|
||||||
|
)
|
||||||
36
tests/crossplatform/mf_getuniquefileref.test.sas
Normal file
36
tests/crossplatform/mf_getuniquefileref.test.sas
Normal 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
|
||||||
|
)
|
||||||
41
tests/crossplatform/mp_appendfile.test.sas
Normal file
41
tests/crossplatform/mp_appendfile.test.sas
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Testing mp_appendfile.sas macro
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mp_appendfile.sas
|
||||||
|
@li mp_assert.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
|
||||||
|
filename tmp1 temp;
|
||||||
|
filename tmp2 temp;
|
||||||
|
filename tmp3 temp;
|
||||||
|
data _null_; file tmp1; put 'base file';
|
||||||
|
data _null_; file tmp2; put 'append1';
|
||||||
|
data _null_; file tmp3; put 'append2';
|
||||||
|
run;
|
||||||
|
%mp_appendfile(baseref=tmp1, appendrefs=tmp2 tmp3)
|
||||||
|
data _null_;
|
||||||
|
infile tmp1;
|
||||||
|
input;
|
||||||
|
put _infile_;
|
||||||
|
call symputx(cats('check',_n_),_infile_);
|
||||||
|
run;
|
||||||
|
%global check1 check2 check3;
|
||||||
|
%mp_assert(
|
||||||
|
iftrue=("&check1"="base file"),
|
||||||
|
desc=Line 1 of file tmp1 is correct,
|
||||||
|
outds=work.test_results
|
||||||
|
)
|
||||||
|
%mp_assert(
|
||||||
|
iftrue=("&check2"="append1"),
|
||||||
|
desc=Line 2 of file tmp1 is correct,
|
||||||
|
outds=work.test_results
|
||||||
|
)
|
||||||
|
%mp_assert(
|
||||||
|
iftrue=("&check3"="append2"),
|
||||||
|
desc=Line 3 of file tmp1 is correct,
|
||||||
|
outds=work.test_results
|
||||||
|
)
|
||||||
67
tests/crossplatform/mp_base64copy.test.sas
Normal file
67
tests/crossplatform/mp_base64copy.test.sas
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Testing mp_base64copy.sas macro
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mp_base64copy.sas
|
||||||
|
@li mp_assert.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
|
||||||
|
/* TEST 1 - regular base64 decode */
|
||||||
|
|
||||||
|
%let string1=base ik ally;
|
||||||
|
filename tmp temp;
|
||||||
|
data _null_;
|
||||||
|
file tmp;
|
||||||
|
put "&string1";
|
||||||
|
run;
|
||||||
|
%mp_base64copy(inref=tmp, outref=myref, action=ENCODE)
|
||||||
|
|
||||||
|
data _null_;
|
||||||
|
infile myref;
|
||||||
|
input;
|
||||||
|
put _infile_;
|
||||||
|
run;
|
||||||
|
%mp_base64copy(inref=myref, outref=mynewref, action=DECODE)
|
||||||
|
data _null_;
|
||||||
|
infile mynewref lrecl=5000;
|
||||||
|
input;
|
||||||
|
put _infile_;
|
||||||
|
call symputx('string1_check',_infile_);
|
||||||
|
stop;
|
||||||
|
run;
|
||||||
|
%mp_assert(
|
||||||
|
iftrue=("&string1"="&string1_check"),
|
||||||
|
desc=Basic String Compare,
|
||||||
|
outds=work.test_results
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
/* multibyte string check */
|
||||||
|
|
||||||
|
filename tmp2 temp;
|
||||||
|
data _null_;
|
||||||
|
file tmp2;
|
||||||
|
put "'╤', '╔', '╗', '═', '╧', '╚', '╝', '║', '╟', '─', '┼', '║', '╢', '│'";
|
||||||
|
run;
|
||||||
|
%mp_base64copy(inref=tmp2, outref=myref2, action=ENCODE)
|
||||||
|
|
||||||
|
%mp_base64copy(inref=myref2, outref=newref2, action=DECODE)
|
||||||
|
data _null_;
|
||||||
|
infile newref2 lrecl=5000;
|
||||||
|
input;
|
||||||
|
list;
|
||||||
|
/* do not print the string to the log else viya 3.5 throws exception */
|
||||||
|
if trim(_infile_)=
|
||||||
|
"'╤', '╔', '╗', '═', '╧', '╚', '╝', '║', '╟', '─', '┼', '║', '╢', '│'"
|
||||||
|
then call symputx('check2',1);
|
||||||
|
else call symputx('check2',0);
|
||||||
|
stop;
|
||||||
|
run;
|
||||||
|
%mp_assert(
|
||||||
|
iftrue=("&check2"="1"),
|
||||||
|
desc=Double Byte String Compare,
|
||||||
|
outds=work.test_results
|
||||||
|
)
|
||||||
99
tests/crossplatform/mp_binarycopy.test.sas
Normal file
99
tests/crossplatform/mp_binarycopy.test.sas
Normal 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
|
||||||
|
)
|
||||||
60
tests/crossplatform/mp_ds2cards.test.sas
Normal file
60
tests/crossplatform/mp_ds2cards.test.sas
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Testing mp_ds2cards.sas macro
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mp_ds2cards.sas
|
||||||
|
@li mp_assert.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test 1 - rebuild an existing dataset
|
||||||
|
* Cars is a great dataset - it contains leading spaces, and formatted numerics
|
||||||
|
*/
|
||||||
|
|
||||||
|
%mp_ds2cards(base_ds=sashelp.cars
|
||||||
|
, tgt_ds=work.test
|
||||||
|
, cards_file= "%sysfunc(pathname(work))/cars.sas"
|
||||||
|
, showlog=NO
|
||||||
|
)
|
||||||
|
%inc "%sysfunc(pathname(work))/cars.sas"/source2;
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test 2 - binary data compare
|
||||||
|
*/
|
||||||
|
data work.binarybase;
|
||||||
|
format bin $hex500. z $hex.;
|
||||||
|
do x=1 to 250;
|
||||||
|
z=byte(x);
|
||||||
|
bin=trim(bin)!!z;
|
||||||
|
output;
|
||||||
|
end;
|
||||||
|
run;
|
||||||
|
|
||||||
|
%mp_ds2cards(base_ds=work.binarybase
|
||||||
|
, showlog=YES
|
||||||
|
, cards_file="%sysfunc(pathname(work))/c2.sas"
|
||||||
|
, tgt_ds=work.binarycompare
|
||||||
|
, append=
|
||||||
|
)
|
||||||
|
|
||||||
|
%inc "%sysfunc(pathname(work))/c2.sas"/source2;
|
||||||
|
|
||||||
|
proc compare base=work.binarybase compare=work.binarycompare;
|
||||||
|
run;
|
||||||
|
|
||||||
|
%mp_assert(
|
||||||
|
iftrue=(&sysinfo=0),
|
||||||
|
desc=work.binarybase dataset is identical,
|
||||||
|
outds=work.test_results
|
||||||
|
)
|
||||||
31
tests/crossplatform/mp_ds2inserts.test.sas
Normal file
31
tests/crossplatform/mp_ds2inserts.test.sas
Normal 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
|
||||||
|
)
|
||||||
33
tests/crossplatform/mp_getcols.test.sas
Normal file
33
tests/crossplatform/mp_getcols.test.sas
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Testing mp_getcols macro
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mp_getcols.sas
|
||||||
|
@li mp_assertcolvals.sas
|
||||||
|
@li mp_assertdsobs.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
|
||||||
|
/* valid filter */
|
||||||
|
%mp_getcols(sashelp.airline,outds=work.info)
|
||||||
|
|
||||||
|
|
||||||
|
%mp_assertdsobs(work.info,
|
||||||
|
desc=Has 3 records,
|
||||||
|
test=EQUALS 3,
|
||||||
|
outds=work.test_results
|
||||||
|
)
|
||||||
|
|
||||||
|
data work.check;
|
||||||
|
length val $10;
|
||||||
|
do val='NUMERIC','DATE','CHARACTER';
|
||||||
|
output;
|
||||||
|
end;
|
||||||
|
run;
|
||||||
|
%mp_assertcolvals(work.info.ddtype,
|
||||||
|
checkvals=work.check.val,
|
||||||
|
desc=All values have a match,
|
||||||
|
test=ALLVALS
|
||||||
|
)
|
||||||
23
tests/crossplatform/mp_getddl.test.sas
Normal file
23
tests/crossplatform/mp_getddl.test.sas
Normal 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
|
||||||
|
)
|
||||||
66
tests/crossplatform/mp_gsubfile.test.sas
Normal file
66
tests/crossplatform/mp_gsubfile.test.sas
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Testing mp_gsubfile.sas macro
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mp_gsubfile.sas
|
||||||
|
@li mp_assert.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test 1 - simple replace
|
||||||
|
*/
|
||||||
|
%global str1;
|
||||||
|
%let file=%sysfunc(pathname(work))/file.txt;
|
||||||
|
%let pat=replace/me;
|
||||||
|
%let str=with/this;
|
||||||
|
data _null_;
|
||||||
|
file "&file";
|
||||||
|
put "&pat";
|
||||||
|
run;
|
||||||
|
%mp_gsubfile(file=&file, patternvar=pat, replacevar=str)
|
||||||
|
data _null_;
|
||||||
|
infile "&file";
|
||||||
|
input;
|
||||||
|
call symputx('str1',_infile_);
|
||||||
|
run;
|
||||||
|
|
||||||
|
%mp_assert(
|
||||||
|
iftrue=("&str1"="&str"),
|
||||||
|
desc=Check that simple replacement was successful,
|
||||||
|
outds=work.test_results
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test 2 - replace from additional line
|
||||||
|
*/
|
||||||
|
%global str2 strcheck2 strcheck2b;
|
||||||
|
%let file2=%sysfunc(pathname(work))/file2.txt;
|
||||||
|
%let pat2=replace/me;
|
||||||
|
%let str2=with/this;
|
||||||
|
data _null_;
|
||||||
|
file "&file2";
|
||||||
|
put 'line1';output;
|
||||||
|
put "&pat2";output;
|
||||||
|
put "&pat2";output;
|
||||||
|
run;
|
||||||
|
%mp_gsubfile(file=&file2, patternvar=pat2, replacevar=str2)
|
||||||
|
data _null_;
|
||||||
|
infile "&file2";
|
||||||
|
input;
|
||||||
|
if _n_=2 then call symputx('strcheck2',_infile_);
|
||||||
|
if _n_=3 then call symputx('strcheck2b',_infile_);
|
||||||
|
putlog _infile_;
|
||||||
|
run;
|
||||||
|
|
||||||
|
%mp_assert(
|
||||||
|
iftrue=("&strcheck2"="&str2"),
|
||||||
|
desc=Check that multi line replacement was successful (line2),
|
||||||
|
outds=work.test_results
|
||||||
|
)
|
||||||
|
%mp_assert(
|
||||||
|
iftrue=("&strcheck2b"="&str2"),
|
||||||
|
desc=Check that multi line replacement was successful (line3),
|
||||||
|
outds=work.test_results
|
||||||
|
)
|
||||||
42
tests/crossplatform/mp_lib2inserts.test.sas
Normal file
42
tests/crossplatform/mp_lib2inserts.test.sas
Normal 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
|
||||||
|
)
|
||||||
39
tests/crossplatform/mp_webin.test.sas
Normal file
39
tests/crossplatform/mp_webin.test.sas
Normal 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
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
24
tests/viyaonly/mv_getfoldermembers.test.sas
Normal file
24
tests/viyaonly/mv_getfoldermembers.test.sas
Normal 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
|
||||||
|
)
|
||||||
@@ -590,6 +590,7 @@ data _null_;
|
|||||||
put ' put ",""SYSCC"" : ""&syscc"" "; ';
|
put ' put ",""SYSCC"" : ""&syscc"" "; ';
|
||||||
put ' put ",""SYSERRORTEXT"" : ""&syserrortext"" "; ';
|
put ' put ",""SYSERRORTEXT"" : ""&syserrortext"" "; ';
|
||||||
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
|
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
|
||||||
|
put ' put ",""SYSSCPL"" : ""&sysscpl"" "; ';
|
||||||
put ' put ",""SYSSITE"" : ""&syssite"" "; ';
|
put ' put ",""SYSSITE"" : ""&syssite"" "; ';
|
||||||
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
|
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
|
||||||
put ' put '',"SYSVLONG" : '' sysvlong; ';
|
put ' put '',"SYSVLONG" : '' sysvlong; ';
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
/**
|
/**
|
||||||
@file mv_getfoldermembers.sas
|
@file
|
||||||
@brief Gets a list of folders (and ids) for a given root
|
@brief Gets a list of folder members (and ids) for a given root
|
||||||
@details Works for both root level and below, oauth or password. Default is
|
@details Returns all members for a particular Viya folder. Works at both root
|
||||||
oauth, and the token is expected in a global ACCESS_TOKEN variable.
|
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 [in] 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 [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|
|
|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|
|
|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 [in] access_token_var= (ACCESS_TOKEN) The global macro variable to
|
||||||
@param grant_type= valid values are "password" or "authorization_code" (unquoted).
|
contain the access token
|
||||||
The default is authorization_code.
|
@param [in] grant_type= (sas_services) Valid values are:
|
||||||
|
@li password
|
||||||
|
@li authorization_code
|
||||||
|
@li detect
|
||||||
|
@li sas_services
|
||||||
|
|
||||||
@version VIYA V.03.04
|
@version VIYA V.03.04
|
||||||
@author Allan Bowe, source: https://github.com/sasjs/core
|
@author Allan Bowe, source: https://github.com/sasjs/core
|
||||||
@@ -28,6 +31,12 @@
|
|||||||
@li mf_getuniquelibref.sas
|
@li mf_getuniquelibref.sas
|
||||||
@li mf_isblank.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=/
|
%macro mv_getfoldermembers(root=/
|
||||||
@@ -67,7 +76,7 @@ options noquotelenmax;
|
|||||||
%if "&root"="/" %then %do;
|
%if "&root"="/" %then %do;
|
||||||
/* if root just list root folders */
|
/* if root just list root folders */
|
||||||
proc http method='GET' out=&fname1 &oauth_bearer
|
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;
|
%if &grant_type=authorization_code %then %do;
|
||||||
headers "Authorization"="Bearer &&&access_token_var";
|
headers "Authorization"="Bearer &&&access_token_var";
|
||||||
%end;
|
%end;
|
||||||
@@ -88,13 +97,17 @@ options noquotelenmax;
|
|||||||
/*data _null_;infile &fname1;input;putlog _infile_;run;*/
|
/*data _null_;infile &fname1;input;putlog _infile_;run;*/
|
||||||
libname &libref1 JSON fileref=&fname1;
|
libname &libref1 JSON fileref=&fname1;
|
||||||
/* now get the followon link to list members */
|
/* now get the followon link to list members */
|
||||||
%local href;
|
%local href cnt;
|
||||||
%let href=0;
|
%let cnt=0;
|
||||||
data _null_;
|
data _null_;
|
||||||
set &libref1..links;
|
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;
|
run;
|
||||||
%if &href=0 %then %do;
|
%if &cnt=0 %then %do;
|
||||||
%put NOTE:;%put NOTE- No members found in &root!!;%put NOTE-;
|
%put NOTE:;%put NOTE- No members found in &root!!;%put NOTE-;
|
||||||
%return;
|
%return;
|
||||||
%end;
|
%end;
|
||||||
@@ -109,6 +122,7 @@ options noquotelenmax;
|
|||||||
run;
|
run;
|
||||||
libname &libref2 JSON fileref=&fname2;
|
libname &libref2 JSON fileref=&fname2;
|
||||||
data &outds;
|
data &outds;
|
||||||
|
length id $36 name $128 uri $64 type $32 description $256;
|
||||||
set &libref2..items;
|
set &libref2..items;
|
||||||
run;
|
run;
|
||||||
filename &fname2 clear;
|
filename &fname2 clear;
|
||||||
|
|||||||
@@ -215,6 +215,7 @@
|
|||||||
put ",""SYSCC"" : ""&syscc"" ";
|
put ",""SYSCC"" : ""&syscc"" ";
|
||||||
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
|
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
|
||||||
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
||||||
|
put ",""SYSSCPL"" : ""&sysscpl"" ";
|
||||||
put ",""SYSSITE"" : ""&syssite"" ";
|
put ",""SYSSITE"" : ""&syssite"" ";
|
||||||
sysvlong=quote(trim(symget('sysvlong')));
|
sysvlong=quote(trim(symget('sysvlong')));
|
||||||
put ',"SYSVLONG" : ' sysvlong;
|
put ',"SYSVLONG" : ' sysvlong;
|
||||||
|
|||||||
Reference in New Issue
Block a user